คำนวณความแตกต่างในคีย์ที่มีอยู่ในพจนานุกรม Python สองพจนานุกรม


171

สมมติว่าฉันมีสองพจนานุกรมหลาม - และdictA dictBฉันต้องการตรวจสอบว่ามีกุญแจใดบ้างที่อยู่ข้างในหรือdictBไม่dictAแต่ไม่ได้อยู่ในวิธีที่เร็วที่สุดที่จะไปเกี่ยวกับมันคืออะไร?

ฉันควรแปลงคีย์พจนานุกรมเป็นชุดและดำเนินการต่อไปหรือไม่

สนใจที่จะรู้ความคิดของคุณ ...


ขอบคุณสำหรับคำตอบของคุณ

ขออภัยที่ไม่ได้ระบุคำถามของฉันอย่างถูกต้อง สถานการณ์ของฉันเป็นเช่นนี้ - ฉันมีdictAที่สามารถเหมือนกันdictBหรืออาจมีบางคีย์ที่ขาดหายไปเมื่อเทียบกับdictBหรืออื่น ๆ ค่าของคีย์บางอย่างอาจแตกต่างกันซึ่งจะต้องมีการตั้งค่าที่dictAเป็นมูลค่าของคีย์

ปัญหาคือพจนานุกรมไม่มีมาตรฐานและสามารถมีค่าที่สามารถเขียนตามคำบอก

พูด

dictA={'key1':a, 'key2':b, 'key3':{'key11':cc, 'key12':dd}, 'key4':{'key111':{....}}}
dictB={'key1':a, 'key2:':newb, 'key3':{'key11':cc, 'key12':newdd, 'key13':ee}.......

ดังนั้นจึงต้องรีเซ็ตค่า 'key2' เป็นค่าใหม่และต้องเพิ่ม 'key13' ภายใน dict ค่าคีย์ไม่มีรูปแบบคงที่ มันอาจเป็นค่าอย่างง่ายหรือ dict หรือ dict ของ dict

คำตอบ:


234

คุณสามารถใช้การตั้งค่าบนปุ่ม:

diff = set(dictb.keys()) - set(dicta.keys())

นี่คือคลาสเพื่อค้นหาความเป็นไปได้ทั้งหมด: สิ่งที่ถูกเพิ่มสิ่งที่ถูกลบซึ่งคู่คีย์ - ค่าเดียวกันและคู่คีย์ - ค่าใดเปลี่ยนแปลง

class DictDiffer(object):
    """
    Calculate the difference between two dictionaries as:
    (1) items added
    (2) items removed
    (3) keys same in both but changed values
    (4) keys same in both and unchanged values
    """
    def __init__(self, current_dict, past_dict):
        self.current_dict, self.past_dict = current_dict, past_dict
        self.set_current, self.set_past = set(current_dict.keys()), set(past_dict.keys())
        self.intersect = self.set_current.intersection(self.set_past)
    def added(self):
        return self.set_current - self.intersect 
    def removed(self):
        return self.set_past - self.intersect 
    def changed(self):
        return set(o for o in self.intersect if self.past_dict[o] != self.current_dict[o])
    def unchanged(self):
        return set(o for o in self.intersect if self.past_dict[o] == self.current_dict[o])

นี่คือตัวอย่างผลลัพธ์:

>>> a = {'a': 1, 'b': 1, 'c': 0}
>>> b = {'a': 1, 'b': 2, 'd': 0}
>>> d = DictDiffer(b, a)
>>> print "Added:", d.added()
Added: set(['d'])
>>> print "Removed:", d.removed()
Removed: set(['c'])
>>> print "Changed:", d.changed()
Changed: set(['b'])
>>> print "Unchanged:", d.unchanged()
Unchanged: set(['a'])

มีให้ใช้เป็น repo github: https://github.com/hughdbrown/dictdiffer


3
สมาร์ทโซลูชั่นขอบคุณ! ฉันทำให้มันทำงานกับ dicts ที่ซ้อนกันโดยการตรวจสอบว่าค่าที่เปลี่ยนแปลงหรือไม่เปลี่ยนแปลงนั้นเป็นอินสแตนซ์ dict และเรียกใช้ฟังก์ชันเรียกซ้ำเพื่อตรวจสอบพวกเขาอีกครั้งโดยใช้คลาสของคุณ
AJJ

1
@JJ ฉันชอบที่จะเห็นการใช้งานที่
urschrei

1
วิธีการเกี่ยวกับdef update(self, new_dict): self.__init__(new_dict, self.current_dict)หรือชอบเพื่อให้คุณสามารถทำเปรียบเทียบกลิ้ง
นิค T

หมายเหตุ: DictDifferคลาสเป็นคลาสไร้สัญชาติและอาจเป็นฟังก์ชัน changedและunchangedค่าอาจจะมีการคำนวณในวงเดียวกัน ฟังก์ชั่นนี้ทั้งสองสามารถคืนค่า a listแทนsetซึ่งเป็นราคาที่ไม่แพงอย่างแน่นอน สำหรับการเปรียบเทียบแบบลึกคุณสามารถดูกรอบการทดสอบหน่วย: docs.python.org/2/library/unittest.htmlเพียงทำตามassertDictEqualวิธีในซอร์สโค้ด
Laurent LAPORTE

1
FWIW, อาจจะดีกว่าที่เป็นset(dictb) set(dictb.keys())
mgilson

60

ในกรณีที่คุณต้องการความแตกต่างซ้ำฉันเขียนแพคเกจสำหรับหลาม: https://github.com/seperman/deepdiff

การติดตั้ง

ติดตั้งจาก PyPi:

pip install deepdiff

ตัวอย่างการใช้งาน

การนำเข้า

>>> from deepdiff import DeepDiff
>>> from pprint import pprint
>>> from __future__ import print_function # In case running on Python 2

วัตถุเดียวกันส่งคืนค่าว่าง

>>> t1 = {1:1, 2:2, 3:3}
>>> t2 = t1
>>> print(DeepDiff(t1, t2))
{}

ประเภทของรายการมีการเปลี่ยนแปลง

>>> t1 = {1:1, 2:2, 3:3}
>>> t2 = {1:1, 2:"2", 3:3}
>>> pprint(DeepDiff(t1, t2), indent=2)
{ 'type_changes': { 'root[2]': { 'newtype': <class 'str'>,
                                 'newvalue': '2',
                                 'oldtype': <class 'int'>,
                                 'oldvalue': 2}}}

มูลค่าของรายการมีการเปลี่ยนแปลง

>>> t1 = {1:1, 2:2, 3:3}
>>> t2 = {1:1, 2:4, 3:3}
>>> pprint(DeepDiff(t1, t2), indent=2)
{'values_changed': {'root[2]': {'newvalue': 4, 'oldvalue': 2}}}

เพิ่มรายการและ / หรือลบออก

>>> t1 = {1:1, 2:2, 3:3, 4:4}
>>> t2 = {1:1, 2:4, 3:3, 5:5, 6:6}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff)
{'dic_item_added': ['root[5]', 'root[6]'],
 'dic_item_removed': ['root[4]'],
 'values_changed': {'root[2]': {'newvalue': 4, 'oldvalue': 2}}}

ความแตกต่างของสตริง

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world"}}
>>> t2 = {1:1, 2:4, 3:3, 4:{"a":"hello", "b":"world!"}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff, indent = 2)
{ 'values_changed': { 'root[2]': {'newvalue': 4, 'oldvalue': 2},
                      "root[4]['b']": { 'newvalue': 'world!',
                                        'oldvalue': 'world'}}}

ความแตกต่างของสตริง 2

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world!\nGoodbye!\n1\n2\nEnd"}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world\n1\n2\nEnd"}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff, indent = 2)
{ 'values_changed': { "root[4]['b']": { 'diff': '--- \n'
                                                '+++ \n'
                                                '@@ -1,5 +1,4 @@\n'
                                                '-world!\n'
                                                '-Goodbye!\n'
                                                '+world\n'
                                                ' 1\n'
                                                ' 2\n'
                                                ' End',
                                        'newvalue': 'world\n1\n2\nEnd',
                                        'oldvalue': 'world!\n'
                                                    'Goodbye!\n'
                                                    '1\n'
                                                    '2\n'
                                                    'End'}}}

>>> 
>>> print (ddiff['values_changed']["root[4]['b']"]["diff"])
--- 
+++ 
@@ -1,5 +1,4 @@
-world!
-Goodbye!
+world
 1
 2
 End

เปลี่ยนประเภท

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, 3]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world\n\n\nEnd"}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff, indent = 2)
{ 'type_changes': { "root[4]['b']": { 'newtype': <class 'str'>,
                                      'newvalue': 'world\n\n\nEnd',
                                      'oldtype': <class 'list'>,
                                      'oldvalue': [1, 2, 3]}}}

ความแตกต่างของรายการ

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, 3, 4]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2]}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff, indent = 2)
{'iterable_item_removed': {"root[4]['b'][2]": 3, "root[4]['b'][3]": 4}}

ความแตกต่างของรายการ 2:

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, 3]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 3, 2, 3]}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff, indent = 2)
{ 'iterable_item_added': {"root[4]['b'][3]": 3},
  'values_changed': { "root[4]['b'][1]": {'newvalue': 3, 'oldvalue': 2},
                      "root[4]['b'][2]": {'newvalue': 2, 'oldvalue': 3}}}

แสดงรายการผลต่างของคำสั่งที่ไม่สนใจหรือซ้ำ: (มีพจนานุกรมเดียวกันกับข้างบน)

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, 3]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 3, 2, 3]}}
>>> ddiff = DeepDiff(t1, t2, ignore_order=True)
>>> print (ddiff)
{}

รายการที่มีพจนานุกรม:

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, {1:1, 2:2}]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, {1:3}]}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff, indent = 2)
{ 'dic_item_removed': ["root[4]['b'][2][2]"],
  'values_changed': {"root[4]['b'][2][1]": {'newvalue': 3, 'oldvalue': 1}}}

ชุด:

>>> t1 = {1, 2, 8}
>>> t2 = {1, 2, 3, 5}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (DeepDiff(t1, t2))
{'set_item_added': ['root[3]', 'root[5]'], 'set_item_removed': ['root[8]']}

Tuples ที่มีชื่อ:

>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y'])
>>> t1 = Point(x=11, y=22)
>>> t2 = Point(x=11, y=23)
>>> pprint (DeepDiff(t1, t2))
{'values_changed': {'root.y': {'newvalue': 23, 'oldvalue': 22}}}

วัตถุที่กำหนดเอง:

>>> class ClassA(object):
...     a = 1
...     def __init__(self, b):
...         self.b = b
... 
>>> t1 = ClassA(1)
>>> t2 = ClassA(2)
>>> 
>>> pprint(DeepDiff(t1, t2))
{'values_changed': {'root.b': {'newvalue': 2, 'oldvalue': 1}}}

เพิ่มแอตทริบิวต์ของวัตถุ:

>>> t2.c = "new attribute"
>>> pprint(DeepDiff(t1, t2))
{'attribute_added': ['root.c'],
 'values_changed': {'root.b': {'newvalue': 2, 'oldvalue': 1}}}

ขอบคุณสำหรับสิ่งนี้! เพิ่งใช้งานในโครงการของฉันใช้งานได้ดี!
gtalarico

1
@gtalarico ยินดีช่วยเหลือ! ขอบคุณสำหรับคำพูดดี ๆ !
Seperman

มีตัวเลือกในการเพิกเฉยต่อความแตกต่างของรายการคำสั่งหรือไม่? เพราะใบสมัครของฉันไม่สนใจเลย
Lei Yang

โครงการที่ดีทำงานทั้งหมดด้วยความพยายามขั้นต่ำจากด้านข้างของฉัน ขอบคุณ!
Stanislav Tsepa

@LeiYang ignore_order=Trueใช่คุณสามารถตั้งค่า คุณสามารถค้นหาเอกสารได้ที่deepdiff.readthedocs.io/en/latest/diff.html
Seperman

18

ไม่แน่ใจว่ามัน "เร็ว" หรือไม่ แต่โดยปกติเราสามารถทำได้

dicta = {"a":1,"b":2,"c":3,"d":4}
dictb = {"a":1,"d":2}
for key in dicta.keys():
    if not key in dictb:
        print key

คุณต้องสลับdictaและdictbเนื่องจากเขาต้องการที่จะรู้ว่ากุญแจเหล่านั้นdictbไม่ได้อยู่ในdictaนั้น
Gumbo

2
for key in dicta.keys():=>for key in dicta:
Jean-François Fabre

15

อย่างที่ Alex Martelli เขียนถ้าคุณเพียงแค่ต้องการตรวจสอบว่าคีย์ใด ๆ ใน B ไม่ได้อยู่ใน A any(True for k in dictB if k not in dictA)จะเป็นวิธีที่จะไป

วิธีค้นหากุญแจที่หายไป:

diff = set(dictB)-set(dictA) #sets

C:\Dokumente und Einstellungen\thc>python -m timeit -s "dictA =    
dict(zip(range(1000),range
(1000))); dictB = dict(zip(range(0,2000,2),range(1000)))" "diff=set(dictB)-set(dictA)"
10000 loops, best of 3: 107 usec per loop

diff = [ k for k in dictB if k not in dictA ] #lc

C:\Dokumente und Einstellungen\thc>python -m timeit -s "dictA = 
dict(zip(range(1000),range
(1000))); dictB = dict(zip(range(0,2000,2),range(1000)))" "diff=[ k for k in dictB if
k not in dictA ]"
10000 loops, best of 3: 95.9 usec per loop

ดังนั้นวิธีแก้ปัญหาทั้งสองนั้นจึงมีความเร็วเท่ากัน


8
ทำให้เหมาะสมมากขึ้น:any(k not in dictA for k in dictB)
hughdbrown

13

หากคุณหมายถึงสิ่งที่คุณพูดอย่างแท้จริง (คุณเพียงแค่ต้องค้นหาว่า "มีปุ่มใด ๆ " ใน B และไม่ได้อยู่ใน A ไม่ใช่ว่าใครจะเป็นถ้ามี) วิธีที่เร็วที่สุดควรเป็น:

if any(True for k in dictB if k not in dictA): ...

หากคุณต้องการค้นหาว่ามีคีย์ใดบ้างที่อยู่ใน B และไม่ใช่ A และไม่ใช่แค่ "IF" มีคีย์ดังกล่าวคำตอบที่มีอยู่นั้นค่อนข้างเหมาะสม (แต่ฉันขอแนะนำให้มีความแม่นยำมากขึ้นสำหรับคำถามในอนาคตหากเป็นเช่นนั้น แน่นอนสิ่งที่คุณหมายถึง ;-)



8

คำตอบยอดนิยมโดย hughdbrownแนะนำให้ใช้ความแตกต่างชุดซึ่งเป็นวิธีที่ดีที่สุด:

diff = set(dictb.keys()) - set(dicta.keys())

ปัญหาของรหัสนี้คือมันสร้างสองรายการเพื่อสร้างสองชุดดังนั้นจึงเสียเวลา 4N และพื้นที่ 2N นอกจากนี้ยังมีความซับซ้อนเกินกว่าที่จะเป็น

โดยปกตินี่ไม่ใช่เรื่องใหญ่ แต่ถ้าเป็น:

diff = dictb.keys() - dicta
  • คุณไม่จำเป็นต้องแปลงคำพูดที่ถูกต้องไปเป็นชุด ความแตกต่างของชุดจะทำซ้ำได้ใด ๆ (และ dict เป็น iterable ของคีย์ของมัน)
  • นอกจากนี้คุณยังไม่จำเป็นต้องแปลง Dict ซ้ายไปยังชุดเพราะอะไรที่สอดคล้องกับcollections.abc.MappingมีKeysViewSetที่ทำหน้าที่เหมือน

Python 2

ในหลาม 2 กลับรายการของปุ่มที่ไม่ได้เป็นkeys() KeysViewดังนั้นคุณต้องขอviewkeys()โดยตรง

diff = dictb.viewkeys() - dicta

สำหรับโค้ด 2.7 / 3.x แบบดูอัลคุณหวังว่าจะใช้งานsixหรือสิ่งที่คล้ายกันดังนั้นคุณจึงสามารถใช้six.viewkeys(dictb):

diff = six.viewkeys(dictb) - dicta

ใน 2.4-2.6 KeysViewไม่มี แต่อย่างน้อยคุณสามารถลดค่าใช้จ่ายจาก 4N เป็น N โดยการสร้างชุดซ้ายของคุณโดยตรงจากตัววนซ้ำแทนที่จะสร้างรายการก่อน:

diff = set(dictb) - dicta

รายการ

ฉันมี dictA ซึ่งสามารถเหมือนกับ dictB หรืออาจมีบางคีย์หายไปเมื่อเทียบกับ dictB หรือค่าของบางคีย์อาจแตกต่างกัน

ดังนั้นคุณไม่จำเป็นต้องเปรียบเทียบคีย์ แต่เป็นรายการ ItemsViewเป็นเพียงSetถ้าค่าเป็น hashable เช่นสตริง ถ้าเป็นเช่นนั้นมันง่าย:

diff = dictb.items() - dicta.items()

ส่วนต่างแบบวนซ้ำ

แม้ว่าคำถามจะไม่ถามถึงความแตกต่างแบบเรียกซ้ำโดยตรง แต่ค่าตัวอย่างบางส่วนนั้น dicts และปรากฏว่าผลลัพธ์ที่คาดหวังนั้นแตกต่างกันไป มีคำตอบหลายคำตอบอยู่ที่นี่แล้วซึ่งแสดงวิธีการดังกล่าว


คำตอบที่ชัดเจนจากปี 2018
Jean-François Fabre

@ Jean-FrançoisFabreแน่นอนว่า Python 2.4-2.6 stuff นั้นค่อนข้างไม่เกี่ยวข้องแล้วในปี 2018 …
abarnert

บางคนติดอยู่กับ 2.6
Jean-François Fabre


3

นี่คือวิธีที่ใช้งานได้ช่วยให้มีคีย์ที่ประเมินFalseและยังคงใช้นิพจน์ตัวสร้างเพื่อแยกออกก่อนถ้าเป็นไปได้ มันไม่ได้สวยล้ำ

any(map(lambda x: True, (k for k in b if k not in a)))

แก้ไข:

THC4k โพสต์คำตอบความคิดเห็นของฉันในคำตอบอื่น นี่เป็นวิธีที่ดีกว่าและสวยงามกว่าในการทำด้านบน:

any(True for k in b if k not in a)

ไม่แน่ใจว่าวิธีที่ไม่เคยใจของฉัน ...


นี่เป็นคำตอบเดียวกับ Alex Martelli ก่อนหน้านี้คำตอบ
Jean-François Fabre

มันคือตอนนี้ เมื่อฉันโพสต์มัน (เก้าปีที่แล้วฮ่า ๆ ) คำตอบก่อนหน้านี้คือany(k for k in dictB if k not in dictA)สิ่งที่ไม่เหมือนกัน (สำหรับคีย์ปลอม) ตรวจสอบประวัติการแก้ไข / การประทับเวลา
Steve Losh

3

นี่เป็นคำถามเก่าและถามน้อยกว่าสิ่งที่ฉันต้องการเล็กน้อยดังนั้นคำตอบนี้จะแก้ปัญหาได้มากกว่าคำถามนี้ คำตอบในคำถามนี้ช่วยฉันแก้ปัญหาต่อไปนี้:

  1. (ถาม) บันทึกความแตกต่างระหว่างพจนานุกรมสองเล่ม
  2. รวมความแตกต่างจาก # 1 ลงในพจนานุกรมฐาน
  3. (ถาม) ผสานความแตกต่างระหว่างพจนานุกรมสองเล่ม (ถือว่าพจนานุกรม # 2 ราวกับว่าเป็นพจนานุกรมที่ต่างกัน)
  4. ลองตรวจจับการเคลื่อนไหวของไอเท็มรวมถึงการเปลี่ยนแปลง
  5. (ถาม) ทำทั้งหมดนี้ซ้ำ

ทั้งหมดนี้รวมกับ JSON ทำให้การสนับสนุนการจัดเก็บการกำหนดค่าที่มีประสิทธิภาพสวย

วิธีการแก้ปัญหา ( ยังอยู่ใน GitHub ):

from collections import OrderedDict
from pprint import pprint


class izipDestinationMatching(object):
    __slots__ = ("attr", "value", "index")

    def __init__(self, attr, value, index):
        self.attr, self.value, self.index = attr, value, index

    def __repr__(self):
        return "izip_destination_matching: found match by '%s' = '%s' @ %d" % (self.attr, self.value, self.index)


def izip_destination(a, b, attrs, addMarker=True):
    """
    Returns zipped lists, but final size is equal to b with (if shorter) a padded with nulls
    Additionally also tries to find item reallocations by searching child dicts (if they are dicts) for attribute, listed in attrs)
    When addMarker == False (patching), final size will be the longer of a, b
    """
    for idx, item in enumerate(b):
        try:
            attr = next((x for x in attrs if x in item), None)  # See if the item has any of the ID attributes
            match, matchIdx = next(((orgItm, idx) for idx, orgItm in enumerate(a) if attr in orgItm and orgItm[attr] == item[attr]), (None, None)) if attr else (None, None)
            if match and matchIdx != idx and addMarker: item[izipDestinationMatching] = izipDestinationMatching(attr, item[attr], matchIdx)
        except:
            match = None
        yield (match if match else a[idx] if len(a) > idx else None), item
    if not addMarker and len(a) > len(b):
        for item in a[len(b) - len(a):]:
            yield item, item


def dictdiff(a, b, searchAttrs=[]):
    """
    returns a dictionary which represents difference from a to b
    the return dict is as short as possible:
      equal items are removed
      added / changed items are listed
      removed items are listed with value=None
    Also processes list values where the resulting list size will match that of b.
    It can also search said list items (that are dicts) for identity values to detect changed positions.
      In case such identity value is found, it is kept so that it can be re-found during the merge phase
    @param a: original dict
    @param b: new dict
    @param searchAttrs: list of strings (keys to search for in sub-dicts)
    @return: dict / list / whatever input is
    """
    if not (isinstance(a, dict) and isinstance(b, dict)):
        if isinstance(a, list) and isinstance(b, list):
            return [dictdiff(v1, v2, searchAttrs) for v1, v2 in izip_destination(a, b, searchAttrs)]
        return b
    res = OrderedDict()
    if izipDestinationMatching in b:
        keepKey = b[izipDestinationMatching].attr
        del b[izipDestinationMatching]
    else:
        keepKey = izipDestinationMatching
    for key in sorted(set(a.keys() + b.keys())):
        v1 = a.get(key, None)
        v2 = b.get(key, None)
        if keepKey == key or v1 != v2: res[key] = dictdiff(v1, v2, searchAttrs)
    if len(res) <= 1: res = dict(res)  # This is only here for pretty print (OrderedDict doesn't pprint nicely)
    return res


def dictmerge(a, b, searchAttrs=[]):
    """
    Returns a dictionary which merges differences recorded in b to base dictionary a
    Also processes list values where the resulting list size will match that of a
    It can also search said list items (that are dicts) for identity values to detect changed positions
    @param a: original dict
    @param b: diff dict to patch into a
    @param searchAttrs: list of strings (keys to search for in sub-dicts)
    @return: dict / list / whatever input is
    """
    if not (isinstance(a, dict) and isinstance(b, dict)):
        if isinstance(a, list) and isinstance(b, list):
            return [dictmerge(v1, v2, searchAttrs) for v1, v2 in izip_destination(a, b, searchAttrs, False)]
        return b
    res = OrderedDict()
    for key in sorted(set(a.keys() + b.keys())):
        v1 = a.get(key, None)
        v2 = b.get(key, None)
        #print "processing", key, v1, v2, key not in b, dictmerge(v1, v2)
        if v2 is not None: res[key] = dictmerge(v1, v2, searchAttrs)
        elif key not in b: res[key] = v1
    if len(res) <= 1: res = dict(res)  # This is only here for pretty print (OrderedDict doesn't pprint nicely)
    return res

2

สิ่งที่เกี่ยวกับรูปแบบ (เปรียบเทียบวัตถุเต็ม)

PyDev-> PyDev Module-> Module: unittest

import unittest


class Test(unittest.TestCase):


    def testName(self):
        obj1 = {1:1, 2:2}
        obj2 = {1:1, 2:2}
        self.maxDiff = None # sometimes is usefull
        self.assertDictEqual(d1, d2)

if __name__ == "__main__":
    #import sys;sys.argv = ['', 'Test.testName']

    unittest.main()

นี่เป็นสิ่งที่ยอดเยี่ยมถ้าคุณมีพจนานุกรมที่ซ้อนกันขนาดใหญ่และคุณต้องการเปรียบเทียบทุกสิ่งภายในและดูความแตกต่าง ขอบคุณ!
Matthew Moisen

2

ถ้าบน Python ≥ 2.7:

# update different values in dictB
# I would assume only dictA should be updated,
# but the question specifies otherwise

for k in dictA.viewkeys() & dictB.viewkeys():
    if dictA[k] != dictB[k]:
        dictB[k]= dictA[k]

# add missing keys to dictA

dictA.update( (k,dictB[k]) for k in dictB.viewkeys() - dictA.viewkeys() )

1

นี่คือทางออกสำหรับการเปรียบเทียบพจนานุกรม 2 คีย์อย่างลึกซึ้ง:

def compareDictKeys(dict1, dict2):
  if type(dict1) != dict or type(dict2) != dict:
      return False

  keys1, keys2 = dict1.keys(), dict2.keys()
  diff = set(keys1) - set(keys2) or set(keys2) - set(keys1)

  if not diff:
      for key in keys1:
          if (type(dict1[key]) == dict or type(dict2[key]) == dict) and not compareDictKeys(dict1[key], dict2[key]):
              diff = True
              break

  return not diff

1

นี่เป็นวิธีการแก้ปัญหาที่สามารถเปรียบเทียบมากกว่าสอง dicts:

def diff_dict(dicts, default=None):
    diff_dict = {}
    # add 'list()' around 'd.keys()' for python 3 compatibility
    for k in set(sum([d.keys() for d in dicts], [])):
        # we can just use "values = [d.get(k, default) ..." below if 
        # we don't care that d1[k]=default and d2[k]=missing will
        # be treated as equal
        if any(k not in d for d in dicts):
            diff_dict[k] = [d.get(k, default) for d in dicts]
        else:
            values = [d[k] for d in dicts]
            if any(v != values[0] for v in values):
                diff_dict[k] = values
    return diff_dict

ตัวอย่างการใช้งาน:

import matplotlib.pyplot as plt
diff_dict([plt.rcParams, plt.rcParamsDefault, plt.matplotlib.rcParamsOrig])

1

สูตรความแตกต่างสมมาตรของฉันระหว่างพจนานุกรมสองเล่ม:

def find_dict_diffs(dict1, dict2):
    unequal_keys = []
    unequal_keys.extend(set(dict1.keys()).symmetric_difference(set(dict2.keys())))
    for k in dict1.keys():
        if dict1.get(k, 'N\A') != dict2.get(k, 'N\A'):
            unequal_keys.append(k)
    if unequal_keys:
        print 'param', 'dict1\t', 'dict2'
        for k in set(unequal_keys):
            print str(k)+'\t'+dict1.get(k, 'N\A')+'\t '+dict2.get(k, 'N\A')
    else:
        print 'Dicts are equal'

dict1 = {1:'a', 2:'b', 3:'c', 4:'d', 5:'e'}
dict2 = {1:'b', 2:'a', 3:'c', 4:'d', 6:'f'}

find_dict_diffs(dict1, dict2)

และผลลัพธ์คือ:

param   dict1   dict2
1       a       b
2       b       a
5       e       N\A
6       N\A     f

1

ดังที่กล่าวไว้ในคำตอบอื่น ๆ unittest สร้างผลลัพธ์ที่ดีบางอย่างสำหรับการเปรียบเทียบ dicts แต่ในตัวอย่างนี้เราไม่ต้องการที่จะสร้างการทดสอบทั้งหมดก่อน

การคัดลอกแหล่งที่มาที่ไม่เหมือนใครดูเหมือนว่าคุณจะได้คำตอบที่เป็นธรรมด้วยสิ่งนี้:

import difflib
import pprint

def diff_dicts(a, b):
    if a == b:
        return ''
    return '\n'.join(
        difflib.ndiff(pprint.pformat(a, width=30).splitlines(),
                      pprint.pformat(b, width=30).splitlines())
    )

ดังนั้น

dictA = dict(zip(range(7), map(ord, 'python')))
dictB = {0: 112, 1: 'spam', 2: [1,2,3], 3: 104, 4: 111}
print diff_dicts(dictA, dictB)

ผลลัพธ์ใน:

{0: 112,
-  1: 121,
-  2: 116,
+  1: 'spam',
+  2: [1, 2, 3],
   3: 104,
-  4: 111,
?        ^

+  4: 111}
?        ^

-  5: 110}

ที่ไหน:

  • '-' หมายถึงคีย์ / ค่าใน dict แรก แต่ไม่ใช่ dict ที่สอง
  • '+' หมายถึงคีย์ / ค่าในวินาที แต่ไม่ใช่ dict แรก

เช่นเดียวกับ unittest ข้อแม้เพียงข้อเดียวคือการทำแผนที่ขั้นสุดท้ายสามารถคิดได้ว่าต่างกันเนื่องจากเครื่องหมายจุลภาค / วงเล็บท้าย


1

@ Maxx มีคำตอบที่ยอดเยี่ยมใช้unittestเครื่องมือที่จัดทำโดย Python:

import unittest


class Test(unittest.TestCase):
    def runTest(self):
        pass

    def testDict(self, d1, d2, maxDiff=None):
        self.maxDiff = maxDiff
        self.assertDictEqual(d1, d2)

จากนั้นทุกที่ในรหัสของคุณคุณสามารถโทร:

try:
    Test().testDict(dict1, dict2)
except Exception, e:
    print e

ส่งผลให้การส่งออกมีลักษณะเหมือนเอาท์พุทจากdiffสวยพิมพ์พจนานุกรมด้วย+หรือ-prepending แต่ละบรรทัดที่แตกต่าง


0

ไม่แน่ใจว่ามันยังเกี่ยวข้องหรือไม่ แต่ฉันเจอปัญหานี้สถานการณ์ของฉันฉันต้องส่งคืนพจนานุกรมของการเปลี่ยนแปลงสำหรับพจนานุกรมที่ซ้อนกันทั้งหมด ฯลฯ ไม่สามารถหาทางออกที่ดีได้ แต่ฉันจบลงด้วยการเขียนฟังก์ชั่นง่าย ๆ การทำเช่นนี้ หวังว่าจะช่วยได้


2
มันจะดีกว่าที่จะมีจำนวนน้อยที่สุดของรหัสที่แก้ไขปัญหาของ OP จริง ๆ ในคำตอบแทนการเชื่อมโยง หากลิงก์ตายหรือย้ายคำตอบของคุณจะไร้ประโยชน์
George Stocker

0

หากคุณต้องการโซลูชันในตัวสำหรับการเปรียบเทียบแบบสมบูรณ์กับโครงสร้าง dict ตามอำเภอใจคำตอบของ @ Maxx คือการเริ่มต้นที่ดี

import unittest

test = unittest.TestCase()
test.assertEqual(dictA, dictB)

เห็นได้ชัดว่าคุณไม่สามารถยกระดับการทดสอบเช่นนั้นซึ่งแย่เกินไป
Ben Liyanage

0

ตามคำตอบของ ghostdog74

dicta = {"a":1,"d":2}
dictb = {"a":5,"d":2}

for value in dicta.values():
    if not value in dictb.values():
        print value

จะพิมพ์ค่า dicta ที่แตกต่างกัน


0

ลองใช้วิธีนี้เพื่อค้นหาจุดตัด, คีย์ที่อยู่ในทั้งสองพจนานุกรมถ้าคุณต้องการคีย์ไม่พบในดิกชันนารีที่สองเพียงใช้ไม่ได้ใน ...

intersect = filter(lambda x, dictB=dictB.keys(): x in dictB, dictA.keys())
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.