سؤال

لدي قواميس بايثون، وأريد أن أكتب تعبيرًا واحدًا يُرجع هذين القواميس، مدمجين.ال update() ستكون الطريقة هي ما أحتاج إليه، إذا أعادت نتيجتها بدلاً من تعديل الإملاء في مكانه.

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = x.update(y)
>>> print(z)
None
>>> x
{'a': 1, 'b': 10, 'c': 11}

كيف يمكنني الحصول على هذا الإملاء المدمج النهائي z, ، لا x?

(لكي نكون أكثر وضوحًا، يفوز الأخير في التعامل مع الصراع dict.update() هو ما أبحث عنه أيضًا.)

هل كانت مفيدة؟

المحلول

كيف يمكنني دمج قواميس بايثون في تعبير واحد؟

للقواميس x و y, z يصبح قاموسًا مدمجًا بشكل سطحي يحتوي على قيم من y استبدال تلك من x.

  • في بايثون 3.5 أو أحدث:

    z = {**x, **y}
    
  • في Python 2 (أو 3.4 أو أقل) اكتب دالة:

    def merge_two_dicts(x, y):
        z = x.copy()   # start with x's keys and values
        z.update(y)    # modifies z with y's keys and values & returns None
        return z
    

    و الأن:

    z = merge_two_dicts(x, y)
    

توضيح

لنفترض أن لديك إملاءين وتريد دمجهما في إملاء جديد دون تغيير الإملاء الأصلي:

x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}

النتيجة المرجوة هي الحصول على قاموس جديد (z) مع دمج القيم، وتحل قيم الإملاء الثاني محل قيم الإملاء الأول.

>>> z
{'a': 1, 'b': 3, 'c': 4}

بناء جملة جديد لهذا المقترح في بيب 448 و متاح اعتبارًا من Python 3.5, ، يكون

z = {**x, **y}

وهو في الحقيقة تعبير واحد.

لاحظ أنه يمكننا الدمج مع التدوين الحرفي أيضًا:

z = {**x, 'foo': 1, 'bar': 2, **y}

و الأن:

>>> z
{'a': 1, 'b': 3, 'foo': 1, 'bar': 2, 'c': 4}

يتم عرضه الآن كما تم تنفيذه في الجدول الزمني لإصدار 3.5، PEP 478, ، وقد شق طريقه الآن إلى ما الجديد في بايثون 3.5 وثيقة.

ومع ذلك، نظرًا لأن العديد من المؤسسات لا تزال تستخدم Python 2، فقد ترغب في القيام بذلك بطريقة متوافقة مع الإصدارات السابقة.الطريقة البايثونية الكلاسيكية، المتوفرة في Python 2 وPython 3.0-3.4، هي القيام بذلك كعملية من خطوتين:

z = x.copy()
z.update(y) # which returns None since it mutates z

وفي كلا النهجين، y سوف يأتي في المرتبة الثانية وسوف تحل قيمه محل xالقيم، وبالتالي 'b' سوف أشير إلى 3 في النتيجة النهائية لدينا.

ليس بعد على Python 3.5، ولكنك تريد تعبير واحد

إذا لم تكن تستخدم Python 3.5 بعد، أو كنت بحاجة إلى كتابة تعليمات برمجية متوافقة مع الإصدارات السابقة، وتريد ذلك بطريقة تعبير واحد, ، الطريقة الأكثر أداءً والصحيحة هي وضعها في دالة:

def merge_two_dicts(x, y):
    """Given two dicts, merge them into a new dict as a shallow copy."""
    z = x.copy()
    z.update(y)
    return z

ثم لديك تعبير واحد:

z = merge_two_dicts(x, y)

يمكنك أيضًا إنشاء دالة لدمج عدد غير محدد من الإملاءات، من الصفر إلى عدد كبير جدًا:

def merge_dicts(*dict_args):
    """
    Given any number of dicts, shallow copy and merge into a new dict,
    precedence goes to key value pairs in latter dicts.
    """
    result = {}
    for dictionary in dict_args:
        result.update(dictionary)
    return result

ستعمل هذه الوظيفة في Python 2 و 3 لجميع الإملاءات.على سبيل المثالنظرا للاملاء a ل g:

z = merge_dicts(a, b, c, d, e, f, g) 

وأزواج القيمة الرئيسية في g سوف تكون لها الأسبقية على الإملاءات a ل f, ، وما إلى ذلك وهلم جرا.

انتقادات للإجابات الأخرى

لا تستخدم ما تراه في الإجابة المقبولة سابقًا:

z = dict(x.items() + y.items())

في Python 2، يمكنك إنشاء قائمتين في الذاكرة لكل إملاء، وإنشاء قائمة ثالثة في الذاكرة بطول يساوي طول القائمتين الأوليين معًا، ثم تجاهل القوائم الثلاث لإنشاء الإملاء. في بايثون 3، سوف يفشل هذا لأنك تضيف اثنين dict_items الكائنات معًا، وليس قائمتين -

>>> c = dict(a.items() + b.items())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'dict_items' and 'dict_items'

وسيتعين عليك إنشاؤها بشكل صريح كقوائم، على سبيل المثال. z = dict(list(x.items()) + list(y.items())).وهذا مضيعة للموارد والقوة الحسابية.

وبالمثل، أخذ اتحاد items() في بايثون 3 (viewitems() في Python 2.7) ستفشل أيضًا عندما تكون القيم كائنات غير قابلة للتجزئة (مثل القوائم، على سبيل المثال).حتى لو كانت قيمك قابلة للتجزئة، نظرًا لأن المجموعات غير مرتبة لغويًا، فإن السلوك غير محدد فيما يتعلق بالأسبقية.لذلك لا تفعل هذا:

>>> c = dict(a.items() | b.items())

يوضح هذا المثال ما يحدث عندما تكون القيم غير قابلة للتجزئة:

>>> x = {'a': []}
>>> y = {'b': []}
>>> dict(x.items() | y.items())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

فيما يلي مثال حيث يجب أن يكون لـ y الأسبقية، ولكن بدلاً من ذلك يتم الاحتفاظ بالقيمة من x بسبب الترتيب العشوائي للمجموعات:

>>> x = {'a': 2}
>>> y = {'a': 1}
>>> dict(x.items() | y.items())
{'a': 2}

اختراق آخر يجب ألا تستخدمه:

z = dict(x, **y)

يستخدم هذا dict منشئ، وهو سريع جدًا وفعال في الذاكرة (حتى أكثر بقليل من عمليتنا المكونة من خطوتين) ولكن ما لم تكن تعرف بالضبط ما يحدث هنا (أي، يتم تمرير الإملاء الثاني كوسيطات للكلمات الرئيسية إلى مُنشئ الإملاء)، من الصعب قراءتها، وليس هذا هو الاستخدام المقصود، وبالتالي فهي ليست بايثونية.

فيما يلي مثال على الاستخدام علاجها في جانغو.

تهدف الإملاءات إلى أخذ مفاتيح قابلة للتجزئة (على سبيل المثال.مجموعات مجمدة أو صفوف)، ولكن تفشل هذه الطريقة في Python 3 عندما لا تكون المفاتيح عبارة عن سلاسل.

>>> c = dict(a, **b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: keyword arguments must be strings

من القائمة البريدية, كتب جويدو فان روسوم مبتكر اللغة:

أنا بخير مع إعلان DICT ({} ، ** {1: 3}) غير قانوني ، لأنه بعد كل شيء هو إساءة استخدام آلية **.

و

يبدو أن Dict (x ، ** y) يدور حول "اختراق رائع" لـ "Call X.Update (y) و Return X".أنا شخصياً أجدها أكثر حزنًا من بارد.

إنه فهمي (وكذلك فهم خالق اللغة) أن الاستخدام المقصود ل dict(**y) مخصص لإنشاء الإملاءات لأغراض سهولة القراءة، على سبيل المثال:

dict(a=1, b=10, c=11)

بدلاً من

{'a': 1, 'b': 10, 'c': 11}

الرد على التعليقات

وعلى الرغم مما يقوله جويدو، dict(x, **y) يتماشى مع مواصفات الإملاء، والتي راجع للشغل.يعمل لكل من بايثون 2 و 3.إن حقيقة أن هذا يعمل فقط مع مفاتيح السلسلة هي نتيجة مباشرة لكيفية عمل معلمات الكلمات الرئيسية وليس مجرد إملاء.كما أن استخدام العامل ** في هذا المكان ليس إساءة استخدام للآلية، في الواقع ** تم تصميمه خصيصًا لتمرير الإملاءات ككلمات رئيسية.

مرة أخرى، لا يعمل الأمر لمدة 3 عندما تكون المفاتيح غير سلاسل.عقد الاستدعاء الضمني هو أن مساحات الأسماء تأخذ إملاءات عادية، بينما يجب على المستخدمين فقط تمرير وسيطات الكلمات الرئيسية التي هي سلاسل.جميع العناصر الأخرى القابلة للاستدعاء فرضتها. dict كسر هذا الاتساق في بيثون 2:

>>> foo(**{('a', 'b'): None})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() keywords must be strings
>>> dict(**{('a', 'b'): None})
{('a', 'b'): None}

كان هذا التناقض سيئًا نظرًا لتطبيقات Python الأخرى (Pypy، Jython، IronPython).وبالتالي تم إصلاحه في Python 3، حيث قد يكون هذا الاستخدام تغييرًا جذريًا.

أؤكد لك أنه من عدم الكفاءة الخبيثة أن تكتب عمدًا تعليمات برمجية تعمل فقط في إصدار واحد من اللغة أو تعمل فقط في ظل قيود تعسفية معينة.

المزيد من التعليقات:

dict(x.items() + y.items()) لا يزال الحل الأكثر قابلية للقراءة لـ Python 2.أهمية القراءة.

جوابي: merge_two_dicts(x, y) في الواقع يبدو الأمر أكثر وضوحًا بالنسبة لي، إذا كنا مهتمين حقًا بقابلية القراءة.وهو غير متوافق مع الإصدارات السابقة، حيث تم إهمال Python 2 بشكل متزايد.

{**x, **y} لا يبدو أنه يتعامل مع القواميس المتداخلة.تتم ببساطة الكتابة فوق محتويات المفاتيح المتداخلة، ولا يتم دمجها [...] انتهى بي الأمر بالاحتراق بسبب هذه الإجابات التي لا تندمج بشكل متكرر وتفاجأت بعدم ذكر أحد لذلك.في تفسيري لكلمة "دمج" تصف هذه الإجابات "تحديث إملاء بآخر" وليس الدمج.

نعم.يجب أن أحيلك مرة أخرى إلى السؤال، الذي يطلب أ أجوف دمج اثنين القواميس، مع استبدال القيم الأولى بقيم الثانية - في تعبير واحد.

بافتراض وجود قاموسين من القواميس، قد يتم دمجهما بشكل متكرر في وظيفة واحدة، ولكن يجب أن تكون حريصًا على عدم تعديل الإملاء من أي مصدر، وأضمن طريقة لتجنب ذلك هي عمل نسخة عند تعيين القيم.نظرًا لأن المفاتيح يجب أن تكون قابلة للتجزئة، وبالتالي تكون عادةً غير قابلة للتغيير، فمن غير المجدي نسخها:

from copy import deepcopy

def dict_of_dicts_merge(x, y):
    z = {}
    overlapping_keys = x.keys() & y.keys()
    for key in overlapping_keys:
        z[key] = dict_of_dicts_merge(x[key], y[key])
    for key in x.keys() - overlapping_keys:
        z[key] = deepcopy(x[key])
    for key in y.keys() - overlapping_keys:
        z[key] = deepcopy(y[key])
    return z

الاستخدام:

>>> x = {'a':{1:{}}, 'b': {2:{}}}
>>> y = {'b':{10:{}}, 'c': {11:{}}}
>>> dict_of_dicts_merge(x, y)
{'b': {2: {}, 10: {}}, 'a': {1: {}}, 'c': {11: {}}}

إن التوصل إلى حالات طارئة لأنواع القيم الأخرى هو أمر يتجاوز نطاق هذا السؤال بكثير، لذا سأوجهك إليه إجابتي على السؤال الأساسي حول "دمج قواميس القواميس".

أقل أداء ولكن المخصصات الصحيحة

هذه الأساليب أقل أداءً، لكنها ستوفر السلوك الصحيح.سيكونون اقل بكثير أداء من copy و update أو التفريغ الجديد لأنها تتكرر من خلال كل زوج من القيمة الرئيسية على مستوى أعلى من التجريد، لكنها يفعل احترام ترتيب الأسبقية (الإملاءات الأخيرة لها الأسبقية)

يمكنك أيضًا ربط الإملاءات يدويًا داخل فهم الإملاء:

{k: v for d in dicts for k, v in d.items()} # iteritems in Python 2.7

أو في python 2.6 (وربما في وقت مبكر من 2.4 عندما تم تقديم تعبيرات المولد):

dict((k, v) for d in dicts for k, v in d.items())

itertools.chain سيتم ربط التكرارات عبر أزواج القيمة الرئيسية بالترتيب الصحيح:

import itertools
z = dict(itertools.chain(x.iteritems(), y.iteritems()))

تحليل الأداء

سأقوم فقط بإجراء تحليل أداء الاستخدامات المعروفة بأنها تتصرف بشكل صحيح.

import timeit

يتم ما يلي على Ubuntu 14.04

في بايثون 2.7 (نظام بايثون):

>>> min(timeit.repeat(lambda: merge_two_dicts(x, y)))
0.5726828575134277
>>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} ))
1.163769006729126
>>> min(timeit.repeat(lambda: dict(itertools.chain(x.iteritems(), y.iteritems()))))
1.1614501476287842
>>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items())))
2.2345519065856934

في Python 3.5 (deadsnakes PPA):

>>> min(timeit.repeat(lambda: {**x, **y}))
0.4094954460160807
>>> min(timeit.repeat(lambda: merge_two_dicts(x, y)))
0.7881555100320838
>>> min(timeit.repeat(lambda: {k: v for d in (x, y) for k, v in d.items()} ))
1.4525277839857154
>>> min(timeit.repeat(lambda: dict(itertools.chain(x.items(), y.items()))))
2.3143140770262107
>>> min(timeit.repeat(lambda: dict((k, v) for d in (x, y) for k, v in d.items())))
3.2069112799945287

الموارد على القواميس

نصائح أخرى

في حالتك، ما يمكنك فعله هو:

z = dict(x.items() + y.items())

سيؤدي هذا، كما تريد، إلى وضع الإملاء النهائي z, ، وجعل قيمة المفتاح b يتم تجاوزها بشكل صحيح من قبل الثانية (y) قيمة الإملاء:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = dict(x.items() + y.items())
>>> z
{'a': 1, 'c': 11, 'b': 10}

إذا كنت تستخدم Python 3، فسيكون الأمر أكثر تعقيدًا قليلاً.لنصنع او لنبتكر z:

>>> z = dict(list(x.items()) + list(y.items()))
>>> z
{'a': 1, 'c': 11, 'b': 10}

بديل:

z = x.copy()
z.update(y)

خيار آخر أكثر إيجازًا:

z = dict(x, **y)

ملحوظة:لقد أصبحت هذه إجابة شائعة، ولكن من المهم الإشارة إلى أنه إذا y يحتوي على أي مفاتيح غير سلسلة، وحقيقة أن هذا يعمل على الإطلاق يعد إساءة استخدام لتفاصيل تنفيذ CPython، ولا يعمل في Python 3، أو في PyPy، أو IronPython، أو Jython.أيضًا، جويدو ليس من المعجبين.لذلك لا يمكنني أن أوصي بهذه التقنية للتعليمات البرمجية المحمولة المتوافقة مع التوجيه الأمامي أو التنفيذ المتبادل، مما يعني أنه يجب تجنبها تمامًا.

ربما لن تكون هذه إجابة شائعة، ولكن من المؤكد أنك لا تريد القيام بذلك.إذا كنت تريد نسخة مدمجة، فاستخدم نسخة (أو نسخة عميقة, ، اعتمادًا على ما تريد) ثم قم بالتحديث.إن سطرين من التعليمات البرمجية أكثر قابلية للقراءة - أكثر لغة بايثونية - من إنشاء سطر واحد باستخدام .items() + .items().الصريح أفضل من الضمني.

بالإضافة إلى ذلك، عند استخدام .items() (ما قبل Python 3.0)، فإنك تقوم بإنشاء قائمة جديدة تحتوي على العناصر من الإملاء.إذا كانت قواميسك كبيرة، فهذا يمثل قدرًا كبيرًا من النفقات العامة (قائمتان كبيرتان سيتم التخلص منهما بمجرد إنشاء الإملاء المدمج).يمكن أن يعمل التحديث () بكفاءة أكبر، لأنه يمكن تشغيله من خلال عنصر الإملاء الثاني لكل عنصر.

من ناحية وقت:

>>> timeit.Timer("dict(x, **y)", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
15.52571702003479
>>> timeit.Timer("temp = x.copy()\ntemp.update(y)", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
15.694622993469238
>>> timeit.Timer("dict(x.items() + y.items())", "x = dict(zip(range(1000), range(1000)))\ny=dict(zip(range(1000,2000), range(1000,2000)))").timeit(100000)
41.484580039978027

المنظمة البحرية الدولية (IMO) التباطؤ الصغير بين الأولين يستحق كل هذا العناء من أجل سهولة القراءة.بالإضافة إلى ذلك، تمت إضافة وسيطات الكلمات الرئيسية لإنشاء القاموس فقط في Python 2.3، في حين أن Copy() وupdate() سيعملان في الإصدارات الأقدم.

في إجابة المتابعة، سألت عن الأداء النسبي لهذين البديلين:

z1 = dict(x.items() + y.items())
z2 = dict(x, **y)

على جهازي، على الأقل (جهاز x86_64 عادي إلى حد ما يعمل بنظام Python 2.5.2)، بديل z2 ليس أقصر وأبسط فحسب، بل أسرع أيضًا.يمكنك التحقق من ذلك بنفسك باستخدام timeit الوحدة التي تأتي مع بايثون.

مثال 1:تقوم القواميس المتطابقة بتعيين 20 عددًا صحيحًا متتاليًا لنفسها:

% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z1=dict(x.items() + y.items())'
100000 loops, best of 3: 5.67 usec per loop
% python -m timeit -s 'x=y=dict((i,i) for i in range(20))' 'z2=dict(x, **y)' 
100000 loops, best of 3: 1.53 usec per loop

z2 يفوز بعامل 3.5 أو نحو ذلك.يبدو أن القواميس المختلفة تعطي نتائج مختلفة تماما، ولكن z2 يبدو دائما أن يخرج إلى الأمام.(إذا حصلت على نتائج غير متناسقة لـ نفس الاختبار، حاول المرور -r برقم أكبر من الافتراضي 3.)

مثال 2:تقوم القواميس غير المتداخلة بتعيين 252 سلسلة قصيرة إلى أعداد صحيحة والعكس:

% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z1=dict(x.items() + y.items())'
1000 loops, best of 3: 260 usec per loop
% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z2=dict(x, **y)'               
10000 loops, best of 3: 26.9 usec per loop

z2 يفوز بحوالي 10.هذا فوز كبير جدًا في كتابي!

بعد مقارنة هذين الاثنين، تساءلت عما إذا كان z1يمكن أن يعزى الأداء الضعيف لـ إلى النفقات العامة لإنشاء قائمتي العناصر، الأمر الذي دفعني بدوره إلى التساؤل عما إذا كان هذا الاختلاف قد يعمل بشكل أفضل:

from itertools import chain
z3 = dict(chain(x.iteritems(), y.iteritems()))

بعض الاختبارات السريعة، على سبيل المثال.

% python -m timeit -s 'from itertools import chain; from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z3=dict(chain(x.iteritems(), y.iteritems()))'
10000 loops, best of 3: 66 usec per loop

يقودني إلى استنتاج ذلك z3 أسرع إلى حد ما من z1, ، ولكن ليس بنفس السرعة تقريبًا z2.بالتأكيد لا يستحق كل الكتابة الإضافية.

ولا يزال هذا النقاش ينقصه شيء مهم، وهو مقارنة أداء هذه البدائل بالطريقة "الواضحة" المتمثلة في دمج القائمتين:باستخدام update طريقة.لمحاولة إبقاء الأمور على قدم المساواة مع التعبيرات، التي لا يقوم أي منها بتعديل x أو y، سأقوم بعمل نسخة من x بدلاً من تعديلها في مكانها، كما يلي:

z0 = dict(x)
z0.update(y)

نتيجة نموذجية:

% python -m timeit -s 'from htmlentitydefs import codepoint2name as x, name2codepoint as y' 'z0=dict(x); z0.update(y)'
10000 loops, best of 3: 26.9 usec per loop

بعبارة أخرى، z0 و z2 يبدو أن لديها أداء متطابق في الأساس.هل تعتقد أن هذا قد يكون صدفة؟أنا لا....

في الواقع، سأذهب إلى حد القول بأنه من المستحيل أن تقوم كود بايثون النقي بعمل أفضل من هذا.وإذا كان بإمكانك القيام بعمل أفضل بكثير في وحدة امتداد C، أتصور أن العاملين في Python قد يكونون مهتمين بدمج الكود الخاص بك (أو شكل مختلف من منهجك) في قلب Python.يستخدم بايثون dict في الكثير من الأماكن؛يعد تحسين عملياتها أمرًا كبيرًا.

يمكنك أيضًا كتابة هذا كـ

z0 = x.copy()
z0.update(y)

كما يفعل توني، ولكن (ليس من المستغرب) أن الاختلاف في التدوين ليس له أي تأثير قابل للقياس على الأداء.استخدم ما يبدو مناسبًا لك.بالطبع، كان محقًا تمامًا في الإشارة إلى أن النسخة المكونة من بيانين أسهل بكثير في الفهم.

كنت أرغب في شيء مماثل، ولكن مع القدرة على تحديد كيفية دمج القيم الموجودة على المفاتيح المكررة، لذلك قمت باختراق هذا (لكنني لم أختبره بشكل مكثف).من الواضح أن هذا ليس تعبيرًا واحدًا، ولكنه استدعاء دالة واحدة.

def merge(d1, d2, merge_fn=lambda x,y:y):
    """
    Merges two dictionaries, non-destructively, combining 
    values on duplicate keys as defined by the optional merge
    function.  The default behavior replaces the values in d1
    with corresponding values in d2.  (There is no other generally
    applicable merge strategy, but often you'll have homogeneous 
    types in your dicts, so specifying a merge technique can be 
    valuable.)

    Examples:

    >>> d1
    {'a': 1, 'c': 3, 'b': 2}
    >>> merge(d1, d1)
    {'a': 1, 'c': 3, 'b': 2}
    >>> merge(d1, d1, lambda x,y: x+y)
    {'a': 2, 'c': 6, 'b': 4}

    """
    result = dict(d1)
    for k,v in d2.iteritems():
        if k in result:
            result[k] = merge_fn(result[k], v)
        else:
            result[k] = v
    return result

في بايثون 3، يمكنك استخدام Collections.ChainMap الذي يجمع عدة إملاءات أو تعيينات أخرى معًا لإنشاء عرض واحد قابل للتحديث:

>>> from collections import ChainMap
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = ChainMap({}, y, x)
>>> for k, v in z.items():
        print(k, '-->', v)

a --> 1
b --> 10
c --> 11

بشكل متكرر/تحديث عميق للإملاء

def deepupdate(original, update):
    """
    Recursively update a dict.
    Subdict's won't be overwritten but also updated.
    """
    for key, value in original.iteritems(): 
        if key not in update:
            update[key] = value
        elif isinstance(value, dict):
            deepupdate(value, update[key]) 
    return update

توضيح:

pluto_original = {
    'name': 'Pluto',
    'details': {
        'tail': True,
        'color': 'orange'
    }
}

pluto_update = {
    'name': 'Pluutoo',
    'details': {
        'color': 'blue'
    }
}

print deepupdate(pluto_original, pluto_update)

النواتج:

{
    'name': 'Pluutoo',
    'details': {
        'color': 'blue',
        'tail': True
    }
}

شكرا ريدناو للتعديلات.

أفضل إصدار يمكن أن أفكر فيه أثناء عدم استخدام النسخة هو:

from itertools import chain
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
dict(chain(x.iteritems(), y.iteritems()))

إنه أسرع من dict(x.items() + y.items()) ولكن ليس بالسرعة التي n = copy(a); n.update(b), ، على الأقل على CPython.يعمل هذا الإصدار أيضًا في Python 3 إذا قمت بالتغيير iteritems() ل items(), ، والذي يتم إجراؤه تلقائيًا بواسطة أداة 2to3.

أنا شخصياً أحب هذا الإصدار أكثر لأنه يصف جيدًا ما أريده في بناء جملة وظيفي واحد.المشكلة البسيطة الوحيدة هي أنه ليس من الواضح تمامًا أن القيم من y لها الأسبقية على القيم من x، لكنني لا أعتقد أنه من الصعب معرفة ذلك.

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z = dict(x.items() + y.items())
print z

بالنسبة للعناصر التي تحتوي على مفاتيح في كلا القواميس ('b')، يمكنك التحكم في أي منها سينتهي في الإخراج عن طريق وضع هذا العنصر في الأخير.

يسمح Python 3.5 (PEP 448) بخيار بناء جملة أفضل:

x = {'a': 1, 'b': 1}
y = {'a': 2, 'c': 2}
final = {**x, **y} 
final
# {'a': 2, 'b': 1, 'c': 2}

او حتى

final = {'a': 1, 'b': 1, **x, **y}

على الرغم من أن السؤال قد تمت الإجابة عليه بالفعل عدة مرات ، إلا أن هذا الحل البسيط للمشكلة لم يتم سرده بعد.

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
z4 = {}
z4.update(x)
z4.update(y)

إنه سريع مثل z0 وz2 الشرير المذكور أعلاه، ولكن من السهل فهمه وتغييره.

def dict_merge(a, b):
  c = a.copy()
  c.update(b)
  return c

new = dict_merge(old, extras)

من بين هذه الإجابات المشبوهة والمشكوك فيها، هذا المثال الساطع هو الطريقة الوحيدة الجيدة لدمج الإملاءات في بايثون، والتي أقرها الدكتاتور مدى الحياة جويدو فان روسوم نفسه!اقترح شخص آخر نصف هذا، لكنه لم يضعه في وظيفة.

print dict_merge(
      {'color':'red', 'model':'Mini'},
      {'model':'Ferrari', 'owner':'Carl'})

يعطي:

{'color': 'red', 'owner': 'Carl', 'model': 'Ferrari'}

إذا كنت تعتقد أن لامدا شريرة، فلا داعي لمزيد من القراءة.كما هو مطلوب، يمكنك كتابة الحل السريع والموفر للذاكرة بتعبير واحد:

x = {'a':1, 'b':2}
y = {'b':10, 'c':11}
z = (lambda a, b: (lambda a_copy: a_copy.update(b) or a_copy)(a.copy()))(x, y)
print z
{'a': 1, 'c': 11, 'b': 10}
print x
{'a': 1, 'b': 2}

كما هو مقترح أعلاه، من المحتمل أن يكون استخدام سطرين أو كتابة دالة هو الطريقة الأفضل.

كن بيثوني.إستخدم فهم:

z={i:d[i] for d in [x,y] for i in d}

>>> print z
{'a': 1, 'c': 11, 'b': 10}

في بيثون3، items طريقة لم تعد تقوم بإرجاع القائمة, ، بل أ منظر, ، والتي تعمل كمجموعة.في هذه الحالة سوف تحتاج إلى أخذ الاتحاد المحدد منذ التسلسل مع + لن تعمل:

dict(x.items() | y.items())

بالنسبة للسلوك المشابه لـ python3 في الإصدار 2.7، فإن viewitems يجب أن تعمل الطريقة بدلاً من items:

dict(x.viewitems() | y.viewitems())

أفضّل هذا التدوين على أي حال لأنه يبدو من الطبيعي التفكير فيه كعملية اتحادية محددة بدلاً من التسلسل (كما يظهر العنوان).

يحرر:

بضع نقاط أخرى لبيثون 3.أولاً، لاحظ أن dict(x, **y) لن تعمل الخدعة في python 3 إلا إذا كانت المفاتيح موجودة y هي سلاسل.

وأيضًا خريطة السلسلة لريموند هيتنجر إجابة أنيق جدًا، لأنه يمكن أن يأخذ عددًا عشوائيًا من الإملاءات كحجج، ولكن من المستندات يبدو أنه يبحث بشكل تسلسلي في قائمة بجميع الإملاءات لكل عملية بحث:

تبحث عمليات البحث في التعيينات الأساسية على التوالي حتى يتم العثور على المفتاح.

يمكن أن يؤدي ذلك إلى إبطائك إذا كان لديك الكثير من عمليات البحث في تطبيقك:

In [1]: from collections import ChainMap
In [2]: from string import ascii_uppercase as up, ascii_lowercase as lo; x = dict(zip(lo, up)); y = dict(zip(up, lo))
In [3]: chainmap_dict = ChainMap(y, x)
In [4]: union_dict = dict(x.items() | y.items())
In [5]: timeit for k in union_dict: union_dict[k]
100000 loops, best of 3: 2.15 µs per loop
In [6]: timeit for k in chainmap_dict: chainmap_dict[k]
10000 loops, best of 3: 27.1 µs per loop

لذا فإن الأمر أبطأ من حيث الحجم بالنسبة لعمليات البحث.أنا من محبي Chainmap، ولكن يبدو أقل عملية حيث قد يكون هناك العديد من عمليات البحث.

سوء المعاملة يؤدي إلى حل تعبير واحد ل إجابة ماثيو:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = (lambda f=x.copy(): (f.update(y), f)[1])()
>>> z
{'a': 1, 'c': 11, 'b': 10}

لقد قلت أنك تريد تعبيرًا واحدًا، لذلك أساءت معاملتي lambda لربط اسم، والصفوف لتجاوز حد التعبير الواحد الخاص بـ lambda.لا تتردد في تذلل.

يمكنك أيضًا القيام بذلك بالطبع إذا كنت لا تهتم بنسخه:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> z = (x.update(y), x)[1]
>>> z
{'a': 1, 'b': 10, 'c': 11}

حل بسيط باستخدام itertools الذي يحافظ على النظام (الإملاءات الأخيرة لها الأسبقية)

import itertools as it
merge = lambda *args: dict(it.chain.from_iterable(it.imap(dict.iteritems, args)))

و استعماله:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> merge(x, y)
{'a': 1, 'b': 10, 'c': 11}

>>> z = {'c': 3, 'd': 4}
>>> merge(x, y, z)
{'a': 1, 'b': 10, 'c': 3, 'd': 4}

اثنين من القواميس

def union2(dict1, dict2):
    return dict(list(dict1.items()) + list(dict2.items()))

ن القواميس

def union(*dicts):
    return dict(itertools.chain.from_iterable(dct.items() for dct in dicts))

sum لديه أداء سيء.يرى https://mathieularose.com/how-not-to-flatten-a-list-of-lists-in-python/

على الرغم من أن الإجابات كانت جيدة لهذا الغرض أجوف القاموس، لا تقوم أي من الطرق المحددة هنا بدمج القاموس بشكل عميق.

الأمثلة التالية:

a = { 'one': { 'depth_2': True }, 'two': True }
b = { 'one': { 'extra': False } }
print dict(a.items() + b.items())

يتوقع المرء نتيجة لشيء مثل هذا:

{ 'one': { 'extra': False', 'depth_2': True }, 'two': True }

بدلا من ذلك، نحصل على هذا:

{'two': True, 'one': {'extra': False}}

يجب أن يحتوي الإدخال "الواحد" على "عمق_2" و"إضافي" كعناصر داخل القاموس الخاص به إذا كان دمجًا بالفعل.

استخدام السلسلة أيضًا لا يعمل:

from itertools import chain
print dict(chain(a.iteritems(), b.iteritems()))

النتائج في:

{'two': True, 'one': {'extra': False}}

يؤدي الدمج العميق الذي قدمه rcwesick أيضًا إلى نفس النتيجة.

نعم، سيتم العمل على دمج نماذج القواميس، لكن لا يشكل أي منها آلية عامة للدمج.سأقوم بتحديث هذا لاحقًا بمجرد أن أكتب طريقة تقوم بدمج حقيقي.

بالاعتماد على الأفكار هنا وفي أماكن أخرى، فهمت وظيفة:

def merge(*dicts, **kv): 
      return { k:v for d in list(dicts) + [kv] for k,v in d.items() }

الاستخدام (تم اختباره في بيثون 3):

assert (merge({1:11,'a':'aaa'},{1:99, 'b':'bbb'},foo='bar')==\
    {1: 99, 'foo': 'bar', 'b': 'bbb', 'a': 'aaa'})

assert (merge(foo='bar')=={'foo': 'bar'})

assert (merge({1:11},{1:99},foo='bar',baz='quux')==\
    {1: 99, 'foo': 'bar', 'baz':'quux'})

assert (merge({1:11},{1:99})=={1: 99})

يمكنك استخدام لامدا بدلا من ذلك.

المشكلة التي أواجهها مع الحلول المدرجة حتى الآن هي أن قيمة المفتاح "b" في القاموس المدمج هي 10، ولكن في طريقة تفكيري، يجب أن تكون 12.وفي ضوء ذلك فإنني أعرض ما يلي:

import timeit

n=100000
su = """
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
"""

def timeMerge(f,su,niter):
    print "{:4f} sec for: {:30s}".format(timeit.Timer(f,setup=su).timeit(n),f)

timeMerge("dict(x, **y)",su,n)
timeMerge("x.update(y)",su,n)
timeMerge("dict(x.items() + y.items())",su,n)
timeMerge("for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k] ",su,n)

#confirm for loop adds b entries together
x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}
for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k]
print "confirm b elements are added:",x

نتائج:

0.049465 sec for: dict(x, **y)
0.033729 sec for: x.update(y)                   
0.150380 sec for: dict(x.items() + y.items())   
0.083120 sec for: for k in y.keys(): x[k] = k in x and x[k]+y[k] or y[k]

confirm b elements are added: {'a': 1, 'c': 11, 'b': 12}
>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> x, z = dict(x), x.update(y) or x
>>> x
{'a': 1, 'b': 2}
>>> y
{'c': 11, 'b': 10}
>>> z
{'a': 1, 'c': 11, 'b': 10}

يمكن القيام بذلك من خلال فهم إملاء واحد:

>>> x = {'a':1, 'b': 2}
>>> y = {'b':10, 'c': 11}
>>> { key: y[key] if key in y else x[key]
      for key in set(x) + set(y)
    }

من وجهة نظري أفضل إجابة لجزء "التعبير الفردي" حيث لا توجد وظائف إضافية مطلوبة، وهي قصيرة.

from collections import Counter
dict1 = {'a':1, 'b': 2}
dict2 = {'b':10, 'c': 11}
result = dict(Counter(dict1) + Counter(dict2))

هذا يجب ان يحل مشكلتك.

(بالنسبة إلى Python2.7* فقط؛هناك حلول أبسط لـ Python3*.)

إذا لم تكن كارهًا لاستيراد وحدة مكتبة قياسية، فيمكنك القيام بذلك

from functools import reduce

def merge_dicts(*dicts):
    return reduce(lambda a, d: a.update(d) or a, dicts, {})

(ال or a قليلا في lambda ضروري لأن dict.update يعود دائما None على النجاح.)

انها سخيفة جدا ذلك .update لا يعود شيئا.
أنا فقط أستخدم وظيفة مساعدة بسيطة لحل المشكلة:

def merge(dict1,*dicts):
    for dict2 in dicts:
        dict1.update(dict2)
    return dict1

أمثلة:

merge(dict1,dict2)
merge(dict1,dict2,dict3)
merge(dict1,dict2,dict3,dict4)
merge({},dict1,dict2)  # this one returns a new copy

باستخدام فهم الإملاء، يمكنك ذلك

x = {'a':1, 'b': 2}
y = {'b':10, 'c': 11}

dc = {xi:(x[xi] if xi not in list(y.keys()) 
           else y[xi]) for xi in list(x.keys())+(list(y.keys()))}

يعطي

>>> dc
{'a': 1, 'c': 11, 'b': 10}

لاحظ بناء الجملة ل if else في الفهم

{ (some_key if condition else default_key):(something_if_true if condition 
          else something_if_false) for key, value in dict_.items() }

أعلم أن هذا لا يتناسب حقًا مع تفاصيل الأسئلة ("سطر واحد")، ولكن منذ ذلك الحين لا أحد من الإجابات المذكورة أعلاه ذهبت في هذا الاتجاه بينما تناولت الكثير والكثير من الإجابات مشكلة الأداء، شعرت أنه يجب علي المساهمة بأفكاري.

اعتمادًا على حالة الاستخدام، قد لا يكون من الضروري إنشاء قاموس مدمج "حقيقي" لقواميس الإدخال المحددة.أ منظر والذي يفعل ذلك قد يكون كافياً في كثير من الحالات، أي:ه.كائن الذي يعمل يحب سيتم استخدام القاموس المدمج بدون حسابه بالكامل.نسخة كسولة من القاموس المدمج، إذا جاز التعبير.

في بايثون، يعد هذا أمرًا بسيطًا إلى حد ما ويمكن القيام به باستخدام الكود الموضح في نهاية رسالتي.وعلى هذا، فإن الإجابة على السؤال الأصلي ستكون:

z = MergeDict(x, y)

عند استخدام هذا الكائن الجديد، فإنه سيعمل مثل القاموس المدمج ولكن سيكون له وقت إنشاء ثابت وبصمة ذاكرة ثابتة مع ترك القواميس الأصلية دون تغيير.إن إنشائه أرخص بكثير من الحلول الأخرى المقترحة.

بالطبع، إذا كنت تستخدم النتيجة كثيرًا، فسوف تصل في مرحلة ما إلى الحد الأقصى حيث سيكون إنشاء قاموس مدمج حقيقي هو الحل الأسرع.كما قلت، ذلك يعتمد على حالة الاستخدام الخاصة بك.

إذا شعرت يومًا أنك تفضل الحصول على دمج حقيقي dict, ، ثم الاتصال dict(z) ستنتجه (لكنه أكثر تكلفة من الحلول الأخرى بالطبع، لذلك تجدر الإشارة إلى ذلك).

يمكنك أيضًا استخدام هذا الفصل لإنشاء نوع من قاموس النسخ عند الكتابة:

a = { 'x': 3, 'y': 4 }
b = MergeDict(a)  # we merge just one dict
b['x'] = 5
print b  # will print {'x': 5, 'y': 4}
print a  # will print {'y': 4, 'x': 3}

إليك الرمز المباشر لـ MergeDict:

class MergeDict(object):
  def __init__(self, *originals):
    self.originals = ({},) + originals[::-1]  # reversed

  def __getitem__(self, key):
    for original in self.originals:
      try:
        return original[key]
      except KeyError:
        pass
    raise KeyError(key)

  def __setitem__(self, key, value):
    self.originals[0][key] = value

  def __iter__(self):
    return iter(self.keys())

  def __repr__(self):
    return '%s(%s)' % (
      self.__class__.__name__,
      ', '.join(repr(original)
          for original in reversed(self.originals)))

  def __str__(self):
    return '{%s}' % ', '.join(
        '%r: %r' % i for i in self.iteritems())

  def iteritems(self):
    found = set()
    for original in self.originals:
      for k, v in original.iteritems():
        if k not in found:
          yield k, v
          found.add(k)

  def items(self):
    return list(self.iteritems())

  def keys(self):
    return list(k for k, _ in self.iteritems())

  def values(self):
    return list(v for _, v in self.iteritems())
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top