กำลังเข้าถึงคีย์ dict เช่นคุณลักษณะหรือไม่


303

ฉันคิดว่ามันสะดวกกว่าในการเข้าถึงคีย์ dict obj.fooแทนobj['foo']ดังนั้นฉันจึงเขียนตัวอย่างนี้:

class AttributeDict(dict):
    def __getattr__(self, attr):
        return self[attr]
    def __setattr__(self, attr, value):
        self[attr] = value

อย่างไรก็ตามฉันคิดว่าต้องมีเหตุผลบางอย่างที่ Python ไม่ได้ให้ฟังก์ชันการทำงานนี้นอกกรอบ สิ่งที่จะเป็น caveats และข้อผิดพลาดของการเข้าถึงคีย์ dict ในลักษณะนี้


16
หากคุณเข้าถึงคีย์ hardcoded จากชุด จำกัด ขนาดคงที่ทุกที่คุณอาจดีกว่าในการสร้างวัตถุที่เก็บสิ่งเหล่านี้ collections.namedtupleมีประโยชน์มากสำหรับสิ่งนี้

6
stackoverflow.com/questions/3031219/…มีโซลูชันที่คล้ายกัน แต่ไปอีกขั้นหนึ่ง
keflavich

1
พบโมดูลสำหรับเรื่องนี้ที่github.com/bcj/AttrDict ฉันไม่รู้ว่ามันเปรียบเทียบกับโซลูชั่นที่นี่และในคำถามที่เกี่ยวข้องได้อย่างไร
matt wilkie

ฉันใช้แฮ็คที่คล้ายกันด้วยตอนนี้ฉันใช้easydict.EasyDict
muon

วิธีเพิ่มเติมในการเข้าถึงสมาชิกพจนานุกรมด้วย '.' : stackoverflow.com/questions/2352181/…
Pale Blue Dot

คำตอบ:


304

วิธีที่ดีที่สุดในการทำเช่นนี้คือ:

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self

ข้อดีบางประการ:

  • ใช้งานได้จริง!
  • ไม่มีวิธีการเรียนในพจนานุกรมที่มีเงา (เช่น.keys()ทำงานได้ดียกเว้นว่า - แน่นอน - คุณกำหนดค่าบางอย่างให้กับพวกเขาดูด้านล่าง)
  • คุณสมบัติและรายการมีการซิงค์อยู่เสมอ
  • พยายามเข้าถึงคีย์ที่ไม่มีอยู่เนื่องจากแอททริบิวต์นั้นถูกต้องAttributeErrorแทนKeyError

จุดด้อย:

  • วิธีการเช่น.keys()นี้จะไม่ทำงานได้ดีหากพวกเขาถูกเขียนทับโดยข้อมูลที่เข้ามา
  • ทำให้หน่วยความจำรั่วใน Python <2.7.4 / Python3 <3.2.3
  • ไพลินไปกล้วยด้วยE1123(unexpected-keyword-arg)และE1103(maybe-no-member)
  • สำหรับมือใหม่ดูเหมือนว่าเวทมนต์บริสุทธิ์

คำอธิบายสั้น ๆ เกี่ยวกับวิธีการทำงาน

  • วัตถุหลามทั้งหมดจัดเก็บคุณลักษณะภายในไว้ในพจนานุกรมที่มีชื่ออยู่__dict__ภายใน
  • ไม่มีข้อกำหนดว่าพจนานุกรมภายใน__dict__จะต้องเป็น "แค่พจน์ธรรมดา" ดังนั้นเราจึงสามารถกำหนดคลาสย่อยใด ๆdict()ให้กับพจนานุกรมภายในได้
  • ในกรณีของเราเราเพียงกำหนดAttrDict()อินสแตนซ์ที่เรากำลังอินสแตนซ์ (ตามที่เรามี__init__)
  • โดยการเรียกsuper()'s __init__()วิธีการที่เราจะทำให้แน่ใจว่ามัน (แล้ว) มีลักษณะการทำงานเหมือนพจนานุกรมตั้งแต่ฟังก์ชั่นที่เรียกร้องทั้งหมดinstantiation พจนานุกรมรหัส

เหตุผลหนึ่งที่ทำให้ Python ไม่ได้มีฟังก์ชั่นนี้นอกกรอบ

ดังที่ระบุไว้ในรายการ "ข้อเสีย" ซึ่งรวมถึงเนมสเปซของคีย์ที่เก็บไว้ (ซึ่งอาจมาจากข้อมูลที่สุ่มและ / หรือไม่น่าเชื่อถือ!) กับเนมสเปซของแอตทริบิวต์เมธอด builtin dict ตัวอย่างเช่น:

d = AttrDict()
d.update({'items':["jacket", "necktie", "trousers"]})
for k, v in d.items():    # TypeError: 'list' object is not callable
    print "Never reached!"

1
คุณคิดว่าการรั่วไหลของ memorry จะเกิดขึ้นกับวัตถุง่าย ๆ เช่น: >>> class MyD (object): ... def init __ (ตัวเอง, d): ... self .__ dict = d
Rafe

ทำให้เกิดการรั่วไหลแม้ใน 2.7
pi

1
ทำให้ <= 2.7.3 เหมือนที่ฉันกำลังใช้อยู่
ปี่

1
ในบันทึกประจำรุ่น 2.7.4 พวกเขาพูดถึงการแก้ไข (ไม่ใช่ก่อนหน้านี้)
Robert Siemer

1
@viveksinghggits เพียงเพราะคุณเข้าถึงสิ่งต่าง ๆ ผ่านทาง.คุณไม่สามารถทำลายกฎของภาษา :) และฉันไม่ต้องการAttrDictแปลงเขตข้อมูลที่มีช่องว่างให้เป็นสิ่งที่แตกต่างกันโดยอัตโนมัติ
Yurik

125

คุณสามารถมีอักขระสตริงที่ถูกกฎหมายทั้งหมดเป็นส่วนหนึ่งของคีย์หากคุณใช้รูปแบบอาร์เรย์ ตัวอย่างเช่น,obj['!#$%^&*()_']


1
@Izkata ใช่ สิ่งที่ตลกเกี่ยวกับ SE ที่มักจะมีคำถามยอดนิยม ชื่อและ 'คำถามล่าง' อาจเป็นเพราะ SE ไม่ชอบที่จะได้ยิน "ชื่อกล่าวมันทั้งหมด"; 'caveats' เป็นส่วนล่างที่นี่
n611x007

2
ไม่ใช่ว่า JavaScript เป็นตัวอย่างที่ดีของภาษาการเขียนโปรแกรม แต่วัตถุใน JS สนับสนุนทั้งการเข้าถึงคุณลักษณะและสัญกรณ์อาเรย์ซึ่งช่วยให้สะดวกสำหรับกรณีทั่วไปและทางเลือกทั่วไปสำหรับสัญลักษณ์ที่ไม่ใช่ชื่อแอตทริบิวต์ทางกฎหมาย
André Caron

@Izkata วิธีนี้ตอบคำถาม คำตอบนี้เพิ่งบอกว่าคีย์สามารถมีชื่อใด ๆ
Melab

4
@Melab คำถามคือWhat would be the caveats and pitfalls of accessing dict keys in this manner?(เป็นคุณลักษณะ) และคำตอบคืออักขระส่วนใหญ่ที่แสดงที่นี่จะไม่สามารถใช้งานได้
Izkata

83

จากคำถาม SO อื่น ๆ นี้มีตัวอย่างการใช้งานที่ยอดเยี่ยมที่ทำให้โค้ดที่มีอยู่ของคุณง่ายขึ้น เกี่ยวกับ:

class AttributeDict(dict): 
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__

รัดกุมมากขึ้นและไม่ออกจากห้องใด ๆ เพื่อให้ cruft พิเศษเข้ามา__getattr__และ__setattr__ทำงานในอนาคต


คุณจะสามารถเรียกใช้ AttributeDict.update หรือ AttributeDict.get โดยใช้วิธีนี้ได้หรือไม่?
Dor

13
คุณต้องจำไว้ว่าถ้าคุณเพิ่มคุณสมบัติใหม่ ณ รันไทม์พวกเขาจะไม่ถูกเพิ่มใน dict แต่เพิ่มไปยังแอตทริบิวต์dict d = AttributeDict(foo=1)เช่น d.bar = 1แอตทริบิวต์ bar จะถูกเก็บไว้ในแอตทริบิวต์dictแต่ไม่ใช่ใน dict การพิมพ์dแสดงเฉพาะรายการ foo
P3trus

7
+1 เพราะมันทำงานได้อย่างสมบูรณ์แบบที่สุดเท่าที่ฉันจะบอกได้ @GringoSuave, @Izkata, @ P3trus ฉันขอให้ทุกคนที่อ้างว่าล้มเหลวแสดงตัวอย่างที่ใช้งานไม่ได้d = AttributeDict(foo=1);d.bar = 1;print d=> ได้{'foo': 1, 'bar': 1}ผลสำหรับฉัน!
Dave Abrahams

4
@DaveAbrahams อ่านคำถามเต็มและดูคำตอบโดย Hery, Ryan และ TheCommunistDuck มันไม่ได้ถามเกี่ยวกับวิธีการที่จะทำเช่นนี้ แต่เกี่ยวกับปัญหาที่อาจจะเกิดขึ้น
Izkata

6
คุณควรมี__getattr__วิธีการที่ทำให้เกิดAttributeErrorถ้ามีคุณสมบัติที่ระบุไม่ได้อยู่มิฉะนั้นสิ่งที่ชอบgetattr(obj, attr, default_value)จะไม่ได้ทำงาน (เช่นไม่กลับdefault_valueถ้าattrไม่มีอยู่บนobj)
jcdude

83

ในกรณีที่ฉันตอบคำถามที่ถูกถาม

ทำไมไพ ธ อนไม่เสนอมันนอกกรอบ?

ฉันสงสัยว่ามันเกี่ยวข้องกับZen of Python : "ควรมีอย่างใดอย่างหนึ่ง - และโดยเฉพาะอย่างยิ่งวิธีเดียวเท่านั้น - ชัดเจน" นี้จะสร้างสองวิธีที่เห็นได้ชัดกับค่าการเข้าถึงจากพจนานุกรม: และobj['key']obj.key

Caveats และหลุมพราง

สิ่งเหล่านี้รวมถึงการขาดความชัดเจนและความสับสนในโค้ด กล่าวคือสิ่งต่อไปนี้อาจสร้างความสับสนให้กับบุคคลอื่นที่กำลังจะรักษารหัสของคุณในภายหลังหรือแม้แต่กับคุณหากคุณไม่ได้กลับไปใช้อีกสักพัก อีกครั้งจากZen : "ความสามารถในการอ่านมีค่า!"

>>> KEY = 'spam'
>>> d[KEY] = 1
>>> # Several lines of miscellaneous code here...
... assert d.spam == 1

หากdมีการสร้างอินสแตนซ์หรือ KEYถูกกำหนดหรือ d[KEY]ถูกกำหนดให้ห่างไกลจากจุดที่d.spamมีการใช้งานมันสามารถนำไปสู่ความสับสนเกี่ยวกับสิ่งที่กำลังทำอยู่ได้อย่างง่ายดายเนื่องจากนี่ไม่ใช่สำนวนที่ใช้กันทั่วไป ฉันรู้ว่ามันจะมีศักยภาพที่จะทำให้ฉันสับสน

นอกจากนี้หากคุณเปลี่ยนค่าKEYเป็นดังนี้ (แต่พลาดการเปลี่ยนแปลงd.spam) ตอนนี้คุณจะได้รับ:

>>> KEY = 'foo'
>>> d[KEY] = 1
>>> # Several lines of miscellaneous code here...
... assert d.spam == 1
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: 'C' object has no attribute 'spam'

IMO ไม่คุ้มกับความพยายาม

รายการอื่น ๆ

ดังที่คนอื่น ๆ ระบุไว้คุณสามารถใช้วัตถุที่แฮชได้ (ไม่ใช่แค่สตริง) เป็นคีย์ dict ตัวอย่างเช่น,

>>> d = {(2, 3): True,}
>>> assert d[(2, 3)] is True
>>> 

ถูกกฎหมาย แต่

>>> C = type('C', (object,), {(2, 3): True})
>>> d = C()
>>> assert d.(2, 3) is True
  File "<stdin>", line 1
  d.(2, 3)
    ^
SyntaxError: invalid syntax
>>> getattr(d, (2, 3))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: getattr(): attribute name must be string
>>> 

ไม่ใช่. สิ่งนี้ช่วยให้คุณสามารถเข้าถึงตัวอักษรที่พิมพ์ได้ทั้งหมดหรือวัตถุที่แฮชได้อื่น ๆ สำหรับปุ่มพจนานุกรมของคุณซึ่งคุณไม่มีเมื่อเข้าถึงแอตทริบิวต์ของวัตถุ นี้จะทำให้มายากลดังกล่าวเป็นไปได้เป็นวัตถุ metaclass แคชเช่นสูตรจากที่งูหลามตำรา (Ch. 9)

ประเด็นที่ฉันแก้ไข

ฉันชอบความสวยงามของspam.eggsกว่าspam['eggs'](ผมคิดว่ามันดูสะอาด) namedtupleและผมเริ่มอยากทำงานนี้เมื่อฉันได้พบกับ แต่ความสะดวกสบายของความสามารถในการทำดังต่อไปนี้สำคัญกว่า

>>> KEYS = 'spam eggs ham'
>>> VALS = [1, 2, 3]
>>> d = {k: v for k, v in zip(KEYS.split(' '), VALS)}
>>> assert d == {'spam': 1, 'eggs': 2, 'ham': 3}
>>>

นี่เป็นตัวอย่างง่ายๆ แต่บ่อยครั้งที่ฉันพบว่าตัวเองใช้ dicts ในสถานการณ์ที่แตกต่างจากที่ฉันใช้obj.keyสัญกรณ์ (เช่นเมื่อฉันต้องอ่าน prefs จากไฟล์ XML) ในกรณีอื่น ๆ ที่ฉันอยากจะยกระดับคลาสแบบไดนามิกและตบคุณลักษณะบางอย่างเพื่อเหตุผลด้านสุนทรียภาพฉันยังคงใช้คำสั่งเพื่อความสอดคล้องเพื่อเพิ่มความสามารถในการอ่าน

ฉันแน่ใจว่า OP ได้รับการแก้ไขมาเป็นเวลานานเพื่อความพึงพอใจของเขา แต่ถ้าเขายังต้องการฟังก์ชั่นนี้ฉันแนะนำให้เขาดาวน์โหลดหนึ่งในแพ็คเกจจาก pypi ที่มี

  • พวงเป็นสิ่งที่ฉันคุ้นเคยมากกว่า ซับคลาสของdictดังนั้นคุณจึงมีฟังก์ชันทั้งหมดนั้น
  • AttrDictยังดูเหมือนว่ามันยังสวยดี แต่ฉันไม่เป็นที่คุ้นเคยกับมันและไม่ได้มองผ่านแหล่งที่มาเป็นรายละเอียดมากที่สุดเท่าที่ฉันมีพวง
  • ติดยาเสพติดมีการบำรุงรักษาอย่างแข็งขันและให้การเข้าถึงเหมือน attr และอื่น ๆ
  • ตามที่ระบุไว้ในการแสดงความคิดเห็นโดย Rotareti, พวงได้รับการคัดค้าน แต่มีการใช้งานที่เรียกว่าส้อมMunch

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

>>> C = type('C', (object,), {})
>>> d = C()
>>> d.spam = 1
>>> d.eggs = 2
>>> d.ham = 3
>>> assert d.__dict__ == {'spam': 1, 'eggs': 2, 'ham': 3}


ในกรณีที่ฉันอัพเดตเพื่อตอบคำถามติดตามในความคิดเห็น

ในความคิดเห็น (ด้านล่าง) Elmoถาม:

ถ้าคุณต้องการที่จะลึกลงไปหนึ่งหรือไม่ (หมายถึงประเภท (... ))

ในขณะที่ฉันไม่เคยใช้กรณีการใช้งานนี้ (อีกครั้งฉันมักจะใช้ซ้อนกันdictเพื่อความมั่นคง) รหัสต่อไปนี้ใช้งานได้:

>>> C = type('C', (object,), {})
>>> d = C()
>>> for x in 'spam eggs ham'.split():
...     setattr(d, x, C())
...     i = 1
...     for y in 'one two three'.split():
...         setattr(getattr(d, x), y, i)
...         i += 1
...
>>> assert d.spam.__dict__ == {'one': 1, 'two': 2, 'three': 3}

1
พวงจะเลิก แต่มีส้อมที่ใช้งานของมัน github.com/Infinidat/munch
Rotareti

@Rotareti - ขอบคุณสำหรับ heads-up! นี่ไม่ใช่ฟังก์ชั่นที่ฉันใช้ดังนั้นฉันจึงไม่รู้ตัว
Doug R.

ถ้าคุณต้องการที่จะลึกลงไปหนึ่งหรือไม่ (หมายถึงประเภท (... ))
Ole Aldric

6
Python เปรียบเสมือนร่มกลับด้านที่มีฝนตกหนัก ทุกอย่างดูดีและขี้ขลาดเริ่มต้นหลังจากเวลาเริ่มหนักแล้วทันใดนั้นคุณอ่านสิ่งที่กูรูในตัวใน SE และสิ่งทั้งหมดย้อนกลับด้วยน้ำหนักบรรทุกทั้งหมดลงไหล่ของคุณ ในขณะที่ยังเปียกโชกคุณรู้สึกเบาและทุกอย่างชัดเจนและสดชื่น
Ole Aldric

21

Caveat emptor: ด้วยเหตุผลบางอย่างคลาสนี้ดูเหมือนว่าจะทำลายแพ็คเกจมัลติโพรเซสเซอร์ ฉันเพิ่งต่อสู้กับข้อผิดพลาดนี้สักครู่ก่อนค้นหา SO: การ ค้นหาข้อยกเว้นในการประมวลผลแบบหลาม


19

คุณสามารถดึงคลาส container ที่สะดวกได้จากไลบรารี่มาตรฐาน:

from argparse import Namespace

เพื่อหลีกเลี่ยงการคัดลอกโค้ดบิต ไม่มีการเข้าถึงพจนานุกรมมาตรฐาน แต่ง่ายต่อการนำกลับมาใช้ถ้าคุณต้องการ รหัสในการโต้แย้งง่าย ๆ

class Namespace(_AttributeHolder):
    """Simple object for storing attributes.

    Implements equality by attribute names and values, and provides a simple
    string representation.
    """

    def __init__(self, **kwargs):
        for name in kwargs:
            setattr(self, name, kwargs[name])

    __hash__ = None

    def __eq__(self, other):
        return vars(self) == vars(other)

    def __ne__(self, other):
        return not (self == other)

    def __contains__(self, key):
        return key in self.__dict__

2
บวก 1 สำหรับการอ้างอิงไลบรารีมาตรฐานซึ่งระบุถึงข้อคิดเห็นแรกโดย OP
Gordon Bean

4
Python รวมคลาสที่เร็วกว่า (ใช้งานใน C) สำหรับกรณีนั้น: types.SimpleNamespace docs.python.org/dev/library/types.html#types.SimpleNamespace
Nuno André

18

ถ้าคุณต้องการที่สำคัญซึ่งเป็นวิธีการเช่น__eq__หรือ__getattr__?

และคุณจะไม่สามารถมีรายการที่ไม่ได้ขึ้นต้นด้วยตัวอักษรดังนั้นการใช้0343853เป็นคีย์จะไม่เกิดขึ้น

และถ้าคุณไม่ต้องการใช้สตริงล่ะ?


แน่นอนหรือตัวอย่างวัตถุอื่น ๆ เป็นกุญแจ อย่างไรก็ตามฉันจะจำแนกข้อผิดพลาดจากนั้นในฐานะ 'พฤติกรรมที่คาดหวัง' - ด้วยคำถามของฉันฉันมีเป้าหมายที่ไม่คาดคิดมากขึ้น
Izz ad-Din Ruhulessin

pickle.dumpการใช้งาน__getstate__
Cees Timmerman

12

tuples สามารถใช้คีย์ dict คุณจะเข้าถึงสิ่งอันดับในโครงสร้างของคุณได้อย่างไร

นอกจากนี้namedtupleเป็นโครงสร้างที่สะดวกซึ่งสามารถให้ค่าผ่านการเข้าถึงคุณลักษณะ


7
ข้อเสียเปรียบของชื่อทูเปิลคือพวกมันไม่เปลี่ยนรูป
Izz ad-Din Ruhulessin

10
บางคนก็บอกว่าการไม่เปลี่ยนรูปไม่ได้เป็นข้อผิดพลาด แต่เป็นคุณสมบัติของสิ่งอันดับ
ผู้เขียน

9

วิธีการเกี่ยวกับProdictชั้นหลามเล็ก ๆ น้อย ๆ ที่ผมเขียนกฎพวกเขาทั้งหมด :)

นอกจากนี้คุณยังจะได้รับอัตโนมัติเสร็จรหัส , instantiations วัตถุ recursiveและชนิดอัตโนมัติแปลง !

คุณสามารถทำสิ่งที่คุณต้องการ:

p = Prodict()
p.foo = 1
p.bar = "baz"

ตัวอย่างที่ 1: พิมพ์คำใบ้

class Country(Prodict):
    name: str
    population: int

turkey = Country()
turkey.name = 'Turkey'
turkey.population = 79814871

รหัสอัตโนมัติเสร็จสมบูรณ์

ตัวอย่างที่ 2: การแปลงชนิดอัตโนมัติ

germany = Country(name='Germany', population='82175700', flag_colors=['black', 'red', 'yellow'])

print(germany.population)  # 82175700
print(type(germany.population))  # <class 'int'>

print(germany.flag_colors)  # ['black', 'red', 'yellow']
print(type(germany.flag_colors))  # <class 'list'>

2
ติดตั้งบน python2 ผ่าน pip แต่ไม่สามารถใช้งานได้กับ python2
Ant6n

2
@ Ant6n ต้องการ python 3.6+ เนื่องจากมีการเพิ่มความคิดเห็นประเภท
Ramazan Polat

8

มันใช้งานไม่ได้ทั่วไป คีย์ dict ที่ไม่ถูกต้องทั้งหมดมีแอตทริบิวต์ที่สามารถกำหนดแอดเดรสได้ ดังนั้นคุณจะต้องระมัดระวัง

วัตถุ Python เป็นพจนานุกรมทั้งหมด ดังนั้นฉันสงสัยว่ามีประสิทธิภาพมากหรือการลงโทษอื่น ๆ


8

สิ่งนี้ไม่ได้ตอบคำถามเดิม แต่น่าจะมีประโยชน์สำหรับคนที่จบลงที่นี่เมื่อมองหา lib ที่มีฟังก์ชั่นนี้

ติดยาเสพติดมันเป็น lib ที่ดีสำหรับสิ่งนี้: https://github.com/mewwts/addictดูแลความกังวลมากมายที่กล่าวถึงในคำตอบก่อนหน้า

ตัวอย่างจากเอกสาร:

body = {
    'query': {
        'filtered': {
            'query': {
                'match': {'description': 'addictive'}
            },
            'filter': {
                'term': {'created_by': 'Mats'}
            }
        }
    }
}

ด้วยการเสพติด:

from addict import Dict
body = Dict()
body.query.filtered.query.match.description = 'addictive'
body.query.filtered.filter.term.created_by = 'Mats'

8

ฉันพบว่าตัวเองสงสัยว่าสถานะปัจจุบันของ "คีย์ dict เป็น attr" ในระบบนิเวศหลาม ในฐานะที่เป็นผู้แสดงความคิดเห็นหลายคนชี้ให้เห็นว่านี่อาจไม่ใช่สิ่งที่คุณต้องการกลิ้งออกไปจากตัวเองเนื่องจากมีข้อผิดพลาดหลายอย่างและปืนสั้นบางคนก็บอบบางมาก นอกจากนี้ฉันจะไม่แนะนำให้ใช้Namespaceเป็นชั้นฐานฉันได้รับลงที่ถนนมันไม่สวย

โชคดีที่มีแพ็คเกจโอเพนซอร์สหลายตัวที่มีฟังก์ชั่นนี้พร้อมที่จะติดตั้ง pip! น่าเสียดายที่มีหลายแพ็คเกจ นี่คือบทสรุปของเดือนธันวาคม 2019

ผู้จัดทำ (การคอมมิทล่าสุดถึง master | #commits | #contribs | ความคุ้มครอง%):

ไม่ได้รับการบำรุงรักษาอีกต่อไปหรือไม่ได้รับการดูแล:

ฉันกำลังแนะนำให้แทะเล็มหรือติดยาเสพติด พวกเขามีความมุ่งมั่นผู้มีส่วนร่วมและเผยแพร่มากที่สุดแนะนำให้มี codebase โอเพนซอร์สที่ดีต่อสุขภาพสำหรับแต่ละคน พวกเขามี readme.md ที่ดูสะอาดตาครอบคลุม 100% และชุดทดสอบที่ดูดี

ฉันไม่ได้มีสุนัขในการแข่งขันนี้ (ตอนนี้!) นอกเหนือจากการรีดรหัส dict / attr ของฉันและเสียเวลาไปมากเพราะฉันไม่รู้ตัวเลือกเหล่านี้ทั้งหมด :) ฉันอาจมีส่วนร่วมในการติดยาเสพติด / แทะเล็มในอนาคตเพราะฉันอยากจะเห็นแพคเกจที่เป็นของแข็งมากกว่าที่จะกระจัดกระจาย ถ้าคุณชอบพวกเขามีส่วนร่วม! โดยเฉพาะอย่างยิ่งดูเหมือนว่าแทะเล็มสามารถใช้ตรา codecov และติดยาเสพติดสามารถใช้ตรางูหลาม

ข้อดีติดยาเสพติด:

  • การเริ่มต้นแบบเรียกซ้ำ (foo.abc = 'bar') ข้อโต้แย้งคล้าย dict กลายเป็น addict.Dict

ข้อเสียติดยา:

  • เงาtyping.Dictถ้าคุณfrom addict import Dict
  • ไม่มีการตรวจสอบคีย์ เนื่องจากอนุญาตให้เรียกใช้แบบเรียกซ้ำหากคุณสะกดคีย์คุณเพียงสร้างแอตทริบิวต์ใหม่แทน KeyError (ขอบคุณ AljoSt)

ข้อดีแทะเล็ม:

  • การตั้งชื่อที่ไม่ซ้ำ
  • ฟังก์ชัน ser / de ในตัวสำหรับ JSON และ YAML

ข้อเสียแทะเล็ม:

  • ไม่มีการเริ่มต้นซ้ำ / สามารถเริ่มต้นครั้งละหนึ่ง attr เท่านั้น

ประเด็นที่ฉันแก้ไข

ดวงจันทร์หลายที่ผ่านมาเมื่อฉันใช้การแก้ไขข้อความในการเขียนหลามในโครงการมีเพียงตัวเองหรือคนอื่น ๆ dev ผมชอบรูปแบบของ Dict-attrs foo.bar.spam = eggsความสามารถที่จะแทรกคีย์โดยเพียงแค่การประกาศ ตอนนี้ฉันทำงานเป็นทีมและใช้ IDE สำหรับทุกสิ่งและฉันได้เบี่ยงเบนไปจากโครงสร้างข้อมูลประเภทต่างๆและการพิมพ์แบบไดนามิกโดยทั่วไปเพื่อการวิเคราะห์เชิงสถิตเทคนิคการทำงานและคำแนะนำประเภท ฉันได้เริ่มต้นการทดสอบด้วยเทคนิคนี้การทำคลาสย่อย Pstruct กับวัตถุที่ออกแบบเอง:

class  BasePstruct(dict):
    def __getattr__(self, name):
        if name in self.__slots__:
            return self[name]
        return self.__getattribute__(name)

    def __setattr__(self, key, value):
        if key in self.__slots__:
            self[key] = value
            return
        if key in type(self).__dict__:
            self[key] = value
            return
        raise AttributeError(
            "type object '{}' has no attribute '{}'".format(type(self).__name__, key))


class FooPstruct(BasePstruct):
    __slots__ = ['foo', 'bar']

สิ่งนี้จะช่วยให้คุณมีวัตถุที่ยังคงทำงานเหมือน dict แต่ยังช่วยให้คุณสามารถเข้าถึงคีย์เช่นคุณสมบัติได้ในแบบที่เข้มงวดมากขึ้น ข้อได้เปรียบที่นี่คือฉัน (หรือผู้บริโภคที่ไม่มีรหัสของคุณ) รู้ว่าเขตข้อมูลใดที่สามารถและไม่มีอยู่และ IDE สามารถเติมข้อมูลในฟิลด์ได้โดยอัตโนมัติ การทำ subclassing vanilla dictหมายถึงการทำให้เป็นอนุกรม json นั้นง่าย ฉันคิดว่าวิวัฒนาการต่อไปในความคิดนี้จะเป็นเครื่องกำเนิด protobuf แบบกำหนดเองซึ่งปล่อยอินเทอร์เฟซเหล่านี้และสิ่งที่น่าสนใจคือคุณได้รับโครงสร้างข้อมูลข้ามภาษาและ IPC ผ่าน gRPC ฟรี

หากคุณตัดสินใจที่จะไปด้วย Attr-dicts มันเป็นสิ่งสำคัญในการบันทึกสิ่งที่คาดว่าเขตข้อมูลสำหรับสติของคุณเอง (และเพื่อนร่วมทีมของคุณ)

รู้สึกอิสระที่จะแก้ไข / อัพเดทโพสต์นี้เพื่อให้ล่าสุด!


2
con ใหญ่สำหรับaddictคือมันจะไม่เพิ่มข้อยกเว้นเมื่อคุณสะกดคำแอตทริบิวต์เนื่องจากมันจะส่งกลับใหม่Dict(นี่เป็นสิ่งจำเป็นสำหรับ foo.abc = 'bar' เพื่อทำงาน)
AljoSt

5

นี่เป็นตัวอย่างสั้น ๆ ของการบันทึกไม่เปลี่ยนรูปโดยใช้ในตัวcollections.namedtuple:

def record(name, d):
    return namedtuple(name, d.keys())(**d)

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

rec = record('Model', {
    'train_op': train_op,
    'loss': loss,
})

print rec.loss(..)

5

เพียงเพิ่มความหลากหลายให้กับคำตอบsci-kit learnได้นำสิ่งนี้ไปใช้เป็นBunch:

class Bunch(dict):                                                              
    """ Scikit Learn's container object                                         

    Dictionary-like object that exposes its keys as attributes.                 
    >>> b = Bunch(a=1, b=2)                                                     
    >>> b['b']                                                                  
    2                                                                           
    >>> b.b                                                                     
    2                                                                           
    >>> b.c = 6                                                                 
    >>> b['c']                                                                  
    6                                                                           
    """                                                                         

    def __init__(self, **kwargs):                                               
        super(Bunch, self).__init__(kwargs)                                     

    def __setattr__(self, key, value):                                          
        self[key] = value                                                       

    def __dir__(self):                                                          
        return self.keys()                                                      

    def __getattr__(self, key):                                                 
        try:                                                                    
            return self[key]                                                    
        except KeyError:                                                        
            raise AttributeError(key)                                           

    def __setstate__(self, state):                                              
        pass                       

สิ่งที่คุณต้องมีก็คือการรับsetattrและgetattrวิธีการ - การgetattrตรวจสอบคีย์ dict และการย้ายไปยังการตรวจสอบคุณลักษณะที่แท้จริง นี่setstaetคือการแก้ไขสำหรับการแก้ไขสำหรับการดอง / unpickling "ช่อ" - ถ้าตรวจสอบ inerested https://github.com/scikit-learn/scikit-learn/issues/6196


3

ไม่จำเป็นต้องเขียนชื่อคุณเองในฐานะ setattr ()และ getattr () แล้ว

ข้อดีของคลาสอ็อบเจ็กต์อาจมาจากการเล่นในการกำหนดคลาสและการสืบทอด


3

ฉันสร้างสิ่งนี้ตามอินพุตจากเธรดนี้ ฉันต้องใช้คำว่า odict ดังนั้นฉันต้องลบล้างและตั้งค่า attr ฉันคิดว่านี่น่าจะใช้ได้กับการใช้งานส่วนใหญ่เป็นพิเศษ

การใช้งานมีลักษณะเช่นนี้:

# Create an ordered dict normally...
>>> od = OrderedAttrDict()
>>> od["a"] = 1
>>> od["b"] = 2
>>> od
OrderedAttrDict([('a', 1), ('b', 2)])

# Get and set data using attribute access...
>>> od.a
1
>>> od.b = 20
>>> od
OrderedAttrDict([('a', 1), ('b', 20)])

# Setting a NEW attribute only creates it on the instance, not the dict...
>>> od.c = 8
>>> od
OrderedAttrDict([('a', 1), ('b', 20)])
>>> od.c
8

ห้องเรียน:

class OrderedAttrDict(odict.OrderedDict):
    """
    Constructs an odict.OrderedDict with attribute access to data.

    Setting a NEW attribute only creates it on the instance, not the dict.
    Setting an attribute that is a key in the data will set the dict data but 
    will not create a new instance attribute
    """
    def __getattr__(self, attr):
        """
        Try to get the data. If attr is not a key, fall-back and get the attr
        """
        if self.has_key(attr):
            return super(OrderedAttrDict, self).__getitem__(attr)
        else:
            return super(OrderedAttrDict, self).__getattr__(attr)


    def __setattr__(self, attr, value):
        """
        Try to set the data. If attr is not a key, fall-back and set the attr
        """
        if self.has_key(attr):
            super(OrderedAttrDict, self).__setitem__(attr, value)
        else:
            super(OrderedAttrDict, self).__setattr__(attr, value)

นี่เป็นรูปแบบสวย ๆ ที่ได้กล่าวถึงแล้วในเธรด แต่ถ้าคุณต้องการใช้ dict และแปลงเป็นวัตถุที่ทำงานกับ auto-complete ใน IDE ฯลฯ :

class ObjectFromDict(object):
    def __init__(self, d):
        self.__dict__ = d

3

เห็นได้ชัดว่าขณะนี้มีห้องสมุดสำหรับเรื่องนี้ - https://pypi.python.org/pypi/attrdict - ซึ่งใช้ฟังก์ชั่นที่แน่นอนนี้รวมถึงการรวมซ้ำและการโหลด json อาจจะคุ้มค่าดู


3

นี่คือสิ่งที่ฉันใช้

args = {
        'batch_size': 32,
        'workers': 4,
        'train_dir': 'train',
        'val_dir': 'val',
        'lr': 1e-3,
        'momentum': 0.9,
        'weight_decay': 1e-4
    }
args = namedtuple('Args', ' '.join(list(args.keys())))(**args)

print (args.lr)

นี่เป็นคำตอบที่ดีและรวดเร็ว การสังเกต / แสดงความคิดเห็นเพียงอย่างเดียวของฉันคือฉันคิดว่าตัวสร้าง namedtuple จะยอมรับรายการของสตริงดังนั้นวิธีแก้ปัญหาของคุณจึงง่ายขึ้น (ฉันคิดว่า) ถึง:namedtuple('Args', list(args.keys()))(**args)
Dan Nguyen

2

คุณสามารถทำได้โดยใช้คลาสนี้ที่ฉันเพิ่งทำ ด้วยคลาสนี้คุณสามารถใช้Mapวัตถุเช่นพจนานุกรมอื่น (รวมถึงการทำให้เป็นอนุกรม json) หรือด้วยเครื่องหมายจุด ฉันหวังว่าจะช่วยคุณ:

class Map(dict):
    """
    Example:
    m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
    """
    def __init__(self, *args, **kwargs):
        super(Map, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.iteritems():
                    self[k] = v

        if kwargs:
            for k, v in kwargs.iteritems():
                self[k] = v

    def __getattr__(self, attr):
        return self.get(attr)

    def __setattr__(self, key, value):
        self.__setitem__(key, value)

    def __setitem__(self, key, value):
        super(Map, self).__setitem__(key, value)
        self.__dict__.update({key: value})

    def __delattr__(self, item):
        self.__delitem__(item)

    def __delitem__(self, key):
        super(Map, self).__delitem__(key)
        del self.__dict__[key]

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

m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
# Add new key
m.new_key = 'Hello world!'
print m.new_key
print m['new_key']
# Update values
m.new_key = 'Yay!'
# Or
m['new_key'] = 'Yay!'
# Delete key
del m.new_key
# Or
del m['new_key']

1
โปรดทราบว่าอาจเงาdictวิธีเช่นให้m=Map(); m["keys"] = 42; m.keys() TypeError: 'int' object is not callable
bfontaine

@bfontaine แนวคิดนี้เป็นแบบfield/attributeและไม่ใช่methodแต่ถ้าคุณกำหนดวิธีการแทนตัวเลขคุณสามารถเข้าถึงวิธีm.method()นั้นได้
epool

2

ให้ฉันโพสต์การดำเนินงานอื่นซึ่งสร้างเมื่อคำตอบของ Kinvais แต่ความคิดบูรณาการจาก AttributeDict เสนอในhttp://databio.org/posts/python_AttributeDict.html

ข้อดีของรุ่นนี้คือมันใช้ได้กับพจนานุกรมที่ซ้อนอยู่ด้วย:

class AttrDict(dict):
    """
    A class to convert a nested Dictionary into an object with key-values
    that are accessible using attribute notation (AttrDict.attribute) instead of
    key notation (Dict["key"]). This class recursively sets Dicts to objects,
    allowing you to recurse down nested dicts (like: AttrDict.attr.attr)
    """

    # Inspired by:
    # http://stackoverflow.com/a/14620633/1551810
    # http://databio.org/posts/python_AttributeDict.html

    def __init__(self, iterable, **kwargs):
        super(AttrDict, self).__init__(iterable, **kwargs)
        for key, value in iterable.items():
            if isinstance(value, dict):
                self.__dict__[key] = AttrDict(value)
            else:
                self.__dict__[key] = value

1
class AttrDict(dict):

     def __init__(self):
           self.__dict__ = self

if __name__ == '____main__':

     d = AttrDict()
     d['ray'] = 'hope'
     d.sun = 'shine'  >>> Now we can use this . notation
     print d['ray']
     print d.sun

1

ทางออกคือ:

DICT_RESERVED_KEYS = vars(dict).keys()


class SmartDict(dict):
    """
    A Dict which is accessible via attribute dot notation
    """
    def __init__(self, *args, **kwargs):
        """
        :param args: multiple dicts ({}, {}, ..)
        :param kwargs: arbitrary keys='value'

        If ``keyerror=False`` is passed then not found attributes will
        always return None.
        """
        super(SmartDict, self).__init__()
        self['__keyerror'] = kwargs.pop('keyerror', True)
        [self.update(arg) for arg in args if isinstance(arg, dict)]
        self.update(kwargs)

    def __getattr__(self, attr):
        if attr not in DICT_RESERVED_KEYS:
            if self['__keyerror']:
                return self[attr]
            else:
                return self.get(attr)
        return getattr(self, attr)

    def __setattr__(self, key, value):
        if key in DICT_RESERVED_KEYS:
            raise AttributeError("You cannot set a reserved name as attribute")
        self.__setitem__(key, value)

    def __copy__(self):
        return self.__class__(self)

    def copy(self):
        return self.__copy__()

1

สิ่งที่จะเป็น caveats และข้อผิดพลาดของการเข้าถึงคีย์ dict ในลักษณะนี้

ตามที่ @Henry แนะนำเหตุผลหนึ่งที่ไม่สามารถใช้การเข้าจุดใน dicts คือมัน จำกัด ชื่อคีย์ dict ให้เป็นตัวแปรที่ใช้ได้กับไพ ธ อนดังนั้นจึง จำกัด ชื่อที่เป็นไปได้ทั้งหมด

ต่อไปนี้เป็นตัวอย่างว่าทำไมประเข้าถึงจะไม่เป็นประโยชน์ในการทั่วไปได้รับ Dict ที่d:

ความถูกต้อง

แอตทริบิวต์ต่อไปนี้จะไม่ถูกต้องใน Python:

d.1_foo                           # enumerated names
d./bar                            # path names
d.21.7, d.12:30                   # decimals, time
d.""                              # empty strings
d.john doe, d.denny's             # spaces, misc punctuation 
d.3 * x                           # expressions  

สไตล์

อนุสัญญา PEP8 จะกำหนดข้อ จำกัด นุ่มนวลในการตั้งชื่อแอตทริบิวต์:

A. ชื่อคำสำคัญที่สงวนไว้(หรือฟังก์ชัน builtin):

d.in
d.False, d.True
d.max, d.min
d.sum
d.id

หากชื่อฟังก์ชันอาร์กิวเมนต์ขัดแย้งกับคำหลักที่สงวนไว้โดยทั่วไปจะเป็นการดีกว่าที่จะเพิ่มขีดล่างต่อท้าย ...

B. กฎกรณีเกี่ยวกับวิธีการและชื่อตัวแปร :

ชื่อตัวแปรปฏิบัติตามแบบแผนเดียวกับชื่อฟังก์ชั่น

d.Firstname
d.Country

ใช้กฎการตั้งชื่อฟังก์ชั่น: ตัวพิมพ์เล็กพร้อมคำคั่นด้วยเครื่องหมายขีดล่างตามความจำเป็นเพื่อปรับปรุงความสามารถในการอ่าน


บางครั้งความกังวลเหล่านี้เกิดขึ้นในห้องสมุดเช่นนุ่นซึ่งอนุญาตให้เข้าถึงจุด DataFrame คอลัมน์ตามชื่อ กลไกเริ่มต้นเพื่อแก้ไขข้อ จำกัด การตั้งชื่อยังเป็นสัญกรณ์อาเรย์ - สตริงภายในวงเล็บ

ถ้าข้อ จำกัด เหล่านี้ไม่ได้นำไปใช้กับกรณีการใช้งานของคุณมีหลายตัวเลือกในโครงสร้างข้อมูลประเข้าถึง


1

คุณสามารถใช้ dict_to_obj https://pypi.org/project/dict-to-obj/ มันทำสิ่งที่คุณขอ

From dict_to_obj import DictToObj
a = {
'foo': True
}
b = DictToObj(a)
b.foo
True

1
มันเป็นรูปแบบที่ดีที่จะนำ.ideaและใด ๆ ใช้งานที่เฉพาะเจาะจงหรือ IDE .gitignoreสร้างไฟล์ในของคุณ
DeusXMachina

1

นี่ไม่ใช่คำตอบที่ 'ดี' แต่ฉันคิดว่านี่เป็นสิ่งที่ดี (มันไม่ได้จัดการ dicts ที่ซ้อนกันในรูปแบบปัจจุบัน) เพียงห่อ Dict ของคุณในฟังก์ชั่น:

def make_funcdict(d=None, **kwargs)
    def funcdict(d=None, **kwargs):
        if d is not None:
            funcdict.__dict__.update(d)
        funcdict.__dict__.update(kwargs)
        return funcdict.__dict__
    funcdict(d, **kwargs)
    return funcdict

ตอนนี้คุณมีไวยากรณ์ที่แตกต่างกันเล็กน้อย เพื่อ acces รายการ Dict f.keyเป็นคุณลักษณะทำ ในการเข้าถึงรายการ dict (และวิธีการเขียนตามคำบอกอื่น ๆ ) ตามปกติให้ทำf()['key']และเราสามารถอัปเดตสิ่งอำนวยความสะดวกได้อย่างสะดวกสบายโดยการโทร f ด้วยอาร์กิวเมนต์คำหลักและ / หรือพจนานุกรม

ตัวอย่าง

d = {'name':'Henry', 'age':31}
d = make_funcdict(d)
>>> for key in d():
...     print key
... 
age
name
>>> print d.name
... Henry
>>> print d.age
... 31
>>> d({'Height':'5-11'}, Job='Carpenter')
... {'age': 31, 'name': 'Henry', 'Job': 'Carpenter', 'Height': '5-11'}

และนั่นก็คือ ฉันจะมีความสุขถ้าใครแนะนำประโยชน์และข้อเสียของวิธีนี้


0

ตามที่ระบุไว้โดยดั๊กมีแพคเกจพวงซึ่งคุณสามารถใช้เพื่อให้บรรลุการobj.keyทำงาน จริงๆแล้วมีเวอร์ชั่นใหม่ที่เรียกว่า

NeoBunch

มันมีคุณสมบัติที่ยอดเยี่ยมในการแปลง dict ของคุณเป็นวัตถุ NeoBunch ผ่านฟังก์ชั่นneobunchify ฉันใช้เทมเพลต Mako เป็นจำนวนมากและส่งผ่านข้อมูลเป็นวัตถุ NeoBunch ทำให้อ่านได้ง่ายขึ้นดังนั้นหากคุณจบลงด้วยการใช้ dict ปกติในโปรแกรม Python ของคุณ แต่ต้องการเครื่องหมายจุดในเทมเพลต Mako คุณก็สามารถใช้มันได้

from mako.template import Template
from neobunch import neobunchify

mako_template = Template(filename='mako.tmpl', strict_undefined=True)
data = {'tmpl_data': [{'key1': 'value1', 'key2': 'value2'}]}
with open('out.txt', 'w') as out_file:
    out_file.write(mako_template.render(**neobunchify(data)))

และเทมเพลต Mako อาจมีลักษณะดังนี้:

% for d in tmpl_data:
Column1     Column2
${d.key1}   ${d.key2}
% endfor

เชื่อมโยงไปยัง NeoBunch คือ 404
DeusXMachina

0

วิธีที่ง่ายที่สุดคือการกำหนดคลาสให้เรียกว่า Namespace ซึ่งใช้วัตถุdict .update () บน dict จากนั้น dict จะถูกปฏิบัติเหมือนเป็นวัตถุ

class Namespace(object):
    '''
    helps referencing object in a dictionary as dict.key instead of dict['key']
    '''
    def __init__(self, adict):
        self.__dict__.update(adict)



Person = Namespace({'name': 'ahmed',
                     'age': 30}) #--> added for edge_cls


print(Person.name)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.