Truy cập các khóa dict như một thuộc tính?


303

Tôi thấy thuận tiện hơn khi truy cập các khóa dict obj.foothay vì obj['foo'], vì vậy tôi đã viết đoạn trích này:

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

Tuy nhiên, tôi cho rằng phải có một số lý do khiến Python không cung cấp chức năng này. Điều gì sẽ là sự cẩn thận và cạm bẫy của việc truy cập các khóa dict theo cách này?


16
Nếu bạn đang truy cập các khóa được mã hóa cứng từ một bộ giới hạn kích thước cố định ở mọi nơi, bạn có thể nên tạo các đối tượng chứa những khóa này. collections.namedtuplelà rất hữu ích cho việc này.

6
stackoverflow.com/questions/3031219/ có một giải pháp tương tự nhưng tiến thêm một bước
keflavich

1
Tìm thấy một mô-đun cho điều này tại github.com/bcj/AttrDict . Tôi không biết làm thế nào nó so sánh với các giải pháp ở đây và trong các câu hỏi liên quan.
matt wilkie

Tôi cũng đã sử dụng các bản hack tương tự, bây giờ tôi sử dụngeasydict.EasyDict
muon

Nhiều cách hơn để truy cập các thành viên từ điển với một '.' : stackoverflow.com/questions/2352181/ Mạnh
Pale Blue Dot

Câu trả lời:


304

Cách tốt nhất để làm điều này là:

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

Một số ưu điểm:

  • Nó thực sự hoạt động!
  • Không có phương thức lớp từ điển nào bị che khuất (ví dụ: .keys()hoạt động tốt. Trừ khi - tất nhiên - bạn gán một số giá trị cho chúng, xem bên dưới)
  • Các thuộc tính và vật phẩm luôn đồng bộ
  • Cố gắng truy cập khóa không tồn tại khi một thuộc tính tăng chính xác AttributeErrorthay vìKeyError

Nhược điểm:

  • Các phương thức như .keys()sẽ không hoạt động tốt nếu chúng bị ghi đè bởi dữ liệu đến
  • Gây rò rỉ bộ nhớ trong Python <2.7.4 / Python3 <3.2.3
  • Pylint đi chuối với E1123(unexpected-keyword-arg)E1103(maybe-no-member)
  • Đối với những người không quen biết nó có vẻ như ma thuật thuần túy.

Một lời giải thích ngắn về cách thức này hoạt động

  • Tất cả các đối tượng python lưu trữ nội bộ thuộc tính của chúng trong một từ điển được đặt tên __dict__.
  • Không có yêu cầu rằng từ điển nội bộ __dict__sẽ cần phải "chỉ là một lệnh chính tả", vì vậy chúng ta có thể gán bất kỳ lớp con nào dict()cho từ điển nội bộ.
  • Trong trường hợp của chúng tôi, chúng tôi chỉ cần gán AttrDict()thể hiện mà chúng tôi đang khởi tạo (như chúng tôi đang ở __init__).
  • Bằng cách gọi super()của __init__()phương pháp chúng tôi chắc chắn rằng nó (đã) cư xử giống hệt như một cuốn từ điển, vì chức năng mà các cuộc gọi tất cả các từ điển instantiation mã.

Một lý do tại sao Python không cung cấp chức năng này ra khỏi hộp

Như đã lưu ý trong danh sách "khuyết điểm", điều này kết hợp không gian tên của các khóa được lưu trữ (có thể đến từ dữ liệu tùy ý và / hoặc không tin cậy!) Với không gian tên của các thuộc tính phương thức dựng sẵn. Ví dụ:

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

1
Bạn có nghĩ rằng rò rỉ lo lắng sẽ xảy ra với một đối tượng đơn giản như: >>> class MyD (object): ... def init __ (self, d): ... self .__ dict = d
Rafe

Gây ra rò rỉ ngay cả trong 2.7
pi.

1
Làm cho <= 2.7.3, như những gì tôi đang sử dụng.
pi.

1
Trong ghi chú phát hành 2.7.4 họ đã đề cập đến nó đã được sửa (không phải trước đó).
Robert Siemer

1
@viveksinghggits chỉ vì bạn đang truy cập mọi thứ thông qua ., bạn không thể phá vỡ các quy tắc của ngôn ngữ :) Và tôi sẽ không muốn AttrDicttự động chuyển đổi các trường chứa không gian thành một thứ khác.
Yurik

125

Bạn có thể có tất cả các ký tự chuỗi hợp pháp như một phần của khóa nếu bạn sử dụng ký hiệu mảng. Ví dụ,obj['!#$%^&*()_']


1
@Izkata vâng. Điều thú vị về SE là thường có một 'câu hỏi hàng đầu' tức là. tiêu đề và một "câu hỏi dưới cùng", có lẽ vì SE không thích nghe "tiêu đề nói lên tất cả"; 'hãy cẩn thận' là người dưới cùng ở đây.
n611x007

2
Không phải JavaScript là một ví dụ đặc biệt tốt về ngôn ngữ lập trình, nhưng các đối tượng trong JS hỗ trợ cả truy cập thuộc tính và ký hiệu mảng, cho phép thuận tiện cho trường hợp chung dự phòng chung cho các ký hiệu không có tên thuộc tính hợp pháp.
André Caron

@Izkata Làm thế nào điều này trả lời câu hỏi. Câu trả lời này chỉ nói rằng các phím có thể có bất kỳ tên nào.
Melab

4
@Melab Câu hỏi là What would be the caveats and pitfalls of accessing dict keys in this manner?(dưới dạng thuộc tính) và câu trả lời là hầu hết các ký tự hiển thị ở đây sẽ không thể sử dụng được.
Izkata

83

Từ câu hỏi SO khác này có một ví dụ triển khai tuyệt vời giúp đơn giản hóa mã hiện tại của bạn. Làm thế nào về:

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

Ngắn gọn hơn nhiều và không chừa bất kỳ phòng nào để có thêm hành trình vào trong __getattr__và các __setattr__chức năng của bạn trong tương lai.


Bạn có thể gọi AttributionDict.update hoặc AttributionDict.get bằng phương thức này không?
Dor

13
Bạn phải nhớ rằng nếu bạn thêm các thuộc tính mới trong thời gian chạy thì chúng không được thêm vào chính dict mà là thuộc tính dict . Ví dụ d = AttributeDict(foo=1). d.bar = 1thuộc tính bar được lưu trữ bên trong thuộc tính dict nhưng không có trong chính dict. in ấn dchỉ hiển thị các mục foo.
P3trus

7
+1 vì nó hoạt động hoàn hảo như tôi có thể nói. @GringoSuave, @Izkata, @ P3trus Tôi yêu cầu bất kỳ ai tuyên bố rằng nó không hiển thị một ví dụ không hoạt động d = AttributeDict(foo=1);d.bar = 1;print d=> {'foo': 1, 'bar': 1}Hoạt động với tôi!
Dave Abrahams

4
@DaveAbrahams Đọc toàn bộ câu hỏi và xem câu trả lời của Hery, Ryan và TheCransistDuck. Đó không phải là hỏi về cách làm điều này, mà là về những vấn đề có thể phát sinh .
Izkata

6
Bạn nên cung cấp một __getattr__phương thức tăng AttributeErrornếu thuộc tính đã cho không tồn tại, nếu không thì những thứ như getattr(obj, attr, default_value)không hoạt động (nghĩa là không trả về default_valuenếu attrkhông tồn tại obj)
jcdude

83

Trong đó tôi trả lời câu hỏi đã được hỏi

Tại sao Python không cung cấp nó ra khỏi hộp?

Tôi nghi ngờ rằng nó phải liên quan đến Zen của Python : "Nên có một - và tốt nhất là chỉ có một - cách rõ ràng để làm điều đó". Điều này sẽ tạo ra hai cách rõ ràng để truy cập các giá trị từ từ điển: obj['key']obj.key .

Hãy cẩn thận và cạm bẫy

Chúng bao gồm có thể thiếu rõ ràng và nhầm lẫn trong mã. tức là, những điều sau đây có thể gây nhầm lẫn cho ai đó khác sẽ duy trì mã của bạn vào một ngày sau đó, hoặc thậm chí với bạn, nếu bạn không quay lại trong một thời gian. Một lần nữa, từ Zen : "Tính dễ đọc!"

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

Nếu dđược khởi tạo hoặc KEY được xác định hoặc d[KEY] được chỉ định ở xad.spam đang được sử dụng, nó có thể dễ dàng dẫn đến nhầm lẫn về những gì đang được thực hiện, vì đây không phải là một thành ngữ thường được sử dụng. Tôi biết nó sẽ có khả năng làm tôi bối rối.

Ngoài ra, nếu bạn thay đổi giá trị của KEY như sau (nhưng bỏ lỡ thay đổi d.spam), bây giờ bạn sẽ nhận được:

>>> 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, không đáng nỗ lực.

Các mặt hàng khác

Như những người khác đã lưu ý, bạn có thể sử dụng bất kỳ đối tượng có thể băm nào (không chỉ là một chuỗi) làm khóa chính tả. Ví dụ,

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

là hợp pháp, nhưng

>>> 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
>>> 

không phải. Điều này cho phép bạn truy cập vào toàn bộ phạm vi các ký tự có thể in hoặc các đối tượng có thể băm khác cho các khóa từ điển mà bạn không có khi truy cập thuộc tính đối tượng. Điều này làm cho phép thuật như một siêu dữ liệu đối tượng được lưu trong bộ nhớ cache, giống như công thức từ Python Cookbook (Ch. 9) .

Trong đó tôi biên tập

Tôi thích tính thẩm mỹ spam.eggshơn spam['eggs'](tôi nghĩ rằng nó trông sạch hơn), và tôi thực sự bắt đầu thèm chức năng này khi tôi gặp namedtuple. Nhưng sự tiện lợi của việc có thể làm những điều sau đây hơn hẳn.

>>> 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}
>>>

Đây là một ví dụ đơn giản, nhưng tôi thường xuyên thấy mình sử dụng dicts trong các tình huống khác nhau hơn là tôi sử dụng obj.key ký hiệu (nghĩa là khi tôi cần đọc prefs trong từ tệp XML). Trong các trường hợp khác, khi tôi muốn khởi tạo một lớp động và tát một số thuộc tính vào nó vì lý do thẩm mỹ, tôi tiếp tục sử dụng một lệnh cho tính nhất quán để tăng cường khả năng đọc.

Tôi chắc chắn rằng OP từ lâu đã giải quyết điều này với sự hài lòng của anh ấy, nhưng nếu anh ấy vẫn muốn chức năng này, thì tôi khuyên anh ấy nên tải xuống một trong các gói từ pypi cung cấp nó:

  • Bunch là người tôi quen thuộc hơn. Phân lớp củadict, vì vậy bạn có tất cả các chức năng đó.
  • AttrDict cũng có vẻ như nó cũng khá tốt, nhưng tôi không quen thuộc với nó và đã không xem qua nguồn chi tiết nhiều như tôi có Bunch .
  • Con nghiện tích cực duy trì và cung cấp quyền truy cập giống như attr và hơn thế nữa.
  • Theo ghi nhận trong các bình luận của Rotareti, Bunch đã không được chấp nhận, nhưng có một ngã ba hoạt động được gọi là Munch .

Tuy nhiên, để cải thiện khả năng đọc mã của anh ấy, tôi thực sự khuyên anh ấy không nên trộn lẫn các kiểu ký hiệu của mình. Nếu anh ta thích ký hiệu này thì anh ta chỉ cần khởi tạo một đối tượng động, thêm các thuộc tính mong muốn của mình vào đó và gọi nó là một ngày:

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


Trong đó tôi cập nhật, để trả lời câu hỏi tiếp theo trong phần Nhận xét

Trong các bình luận (bên dưới), Elmo hỏi:

Nếu bạn muốn đi sâu hơn thì sao? (tham khảo loại (...))

Mặc dù tôi chưa bao giờ sử dụng trường hợp sử dụng này (một lần nữa, tôi có xu hướng sử dụng lồng nhau dict, để thống nhất), đoạn mã sau hoạt động:

>>> 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
Bunch không được dùng nữa, nhưng có một nhánh hoạt động của nó: github.com/Infinidat/munch
Rotareti

@Rotareti - Cảm ơn bạn đã ủng hộ! Đây không phải là chức năng tôi sử dụng, vì vậy tôi không biết điều đó.
Doug R.

Nếu bạn muốn đi sâu hơn thì sao? (tham khảo loại (...))
Ole Aldric

6
Python giống như một chiếc ô đảo ngược được giữ trong mưa lớn. Tất cả có vẻ thông minh và thú vị khi bắt đầu, sau một thời gian nó bắt đầu trở nên nặng nề, rồi đột nhiên, bạn đọc một số công cụ guru tích hợp trên SE và toàn bộ mọi thứ trở lại với toàn bộ trọng tải xuống vai của bạn. Trong khi vẫn còn ướt bạn cảm thấy nhẹ hơn và mọi thứ rất rõ ràng và được làm mới.
Ole Aldric


19

Bạn có thể kéo một lớp container thuận tiện từ thư viện chuẩn:

from argparse import Namespace

để tránh phải sao chép xung quanh các bit mã. Không có quyền truy cập từ điển tiêu chuẩn, nhưng dễ dàng lấy lại nếu bạn thực sự muốn nó. Mã trong argparse rất đơn giản,

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
PLUS 1 để tham khảo một thư viện tiêu chuẩn, trong đó giải quyết nhận xét đầu tiên của OP.
Gordon Bean

4
Python bao gồm một lớp nhanh hơn (được triển khai trong C) cho trường hợp đó: types.SimpleNamespace docs.python.org/dev/l Library / type.html
Nuno André

18

Điều gì nếu bạn muốn một khóa là một phương thức, chẳng hạn như __eq__hoặc __getattr__?

Và bạn sẽ không thể có một mục không bắt đầu bằng một chữ cái, vì vậy sử dụng 0343853làm khóa là hết.

Và nếu bạn không muốn sử dụng một chuỗi thì sao?


Thật vậy, hoặc ví dụ các đối tượng khác làm chìa khóa. Tuy nhiên, tôi sẽ phân loại lỗi từ đó là 'hành vi dự kiến' - với câu hỏi của tôi, tôi đang hướng đến sự bất ngờ hơn.
Izz ad-Din Ruhulessin

pickle.dumpsử dụng__getstate__
Cees Timmerman

12

bộ dữ liệu có thể được sử dụng các phím dict. Làm thế nào bạn sẽ truy cập tuple trong cấu trúc của bạn?

Ngoài ra, nametuple là một cấu trúc thuận tiện có thể cung cấp các giá trị thông qua truy cập thuộc tính.


7
Hạn chế của nametuples là chúng bất biến.
Izz ad-Din Ruhulessin

10
Một số người sẽ nói rằng bất biến không phải là một lỗi mà là một tính năng của bộ dữ liệu.
ben tác giả

9

Thế còn Prodict , lớp Python nhỏ mà tôi đã viết để cai trị tất cả :)

Thêm vào đó, bạn có được hoàn thành mã tự động , tức thời đối tượng đệ quychuyển đổi loại tự động !

Bạn có thể làm chính xác những gì bạn yêu cầu:

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

Ví dụ 1: Gợi ý

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

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

mã tự động hoàn tất

Ví dụ 2: Chuyển đổi loại tự động

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
cài đặt trên python2 qua pip, nhưng không hoạt động trên python2
Ant6n

2
@ Ant6n yêu cầu python 3.6+ vì chú thích loại
Ramazan Polat

8

Nó không hoạt động trong tổng quát. Không phải tất cả các khóa chính tả hợp lệ đều tạo các thuộc tính có thể đánh địa chỉ ("khóa"). Vì vậy, bạn sẽ cần phải cẩn thận.

Các đối tượng Python về cơ bản là từ điển. Vì vậy, tôi nghi ngờ có nhiều hiệu suất hoặc hình phạt khác.


8

Điều này không giải quyết câu hỏi ban đầu, nhưng sẽ hữu ích cho những người, như tôi, kết thúc ở đây khi tìm kiếm một lib cung cấp chức năng này.

Nghiện nó là một lib tuyệt vời cho điều này: https://github.com/mewwts/addict nó quan tâm đến nhiều mối quan tâm được đề cập trong các câu trả lời trước.

Một ví dụ từ các tài liệu:

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

Với người nghiện:

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

8

Tôi thấy mình băn khoăn về tình trạng hiện tại của "các phím chính tả như attr" trong hệ sinh thái trăn. Như một số bình luận đã chỉ ra, đây có lẽ không phải là thứ bạn muốn tự mình làm lại từ đầu , vì có một số cạm bẫy và súng ngắn, một số trong số chúng rất tinh tế. Ngoài ra, tôi không khuyên bạn nên sử dụngNamespace làm lớp cơ sở, tôi đã từng đi trên con đường đó, nó không đẹp lắm.

May mắn thay, có một số gói nguồn mở cung cấp chức năng này, sẵn sàng để cài đặt pip! Thật không may, có một số gói. Dưới đây là tóm tắt, kể từ tháng 12 năm 2019.

Ứng cử viên (gần đây nhất cam kết làm chủ | #commits | #contribs | bảo hiểm%):

Không còn duy trì hoặc bảo trì dưới:

  • treedict (2014 / 03-28 | 95 | 2 |?%)
  • (2012-03-12 | 20 | 2 |?%)
  • NeoBunch

Tôi hiện đang khuyên bạn nên munch hoặc nghiện . Họ có nhiều cam kết, người đóng góp và phát hành nhất, đề xuất một cơ sở mã nguồn mở lành mạnh cho mỗi cơ sở. Họ có readme.md sạch nhất, độ bao phủ 100% và bộ thử nghiệm ưa nhìn.

Tôi không có một con chó trong cuộc đua này (bây giờ!), Bên cạnh việc cuộn mã dict / attr của riêng tôi và lãng phí rất nhiều thời gian vì tôi không nhận thức được tất cả các tùy chọn này :). Tôi có thể đóng góp cho người nghiện / munch trong tương lai vì tôi thà thấy một gói rắn hơn là một loạt các mảnh vỡ. Nếu bạn thích chúng, hãy đóng góp! Cụ thể, có vẻ như munch có thể sử dụng huy hiệu codecov và người nghiện có thể sử dụng huy hiệu phiên bản python.

người nghiện

  • khởi tạo đệ quy (foo.abc = 'bar'), các đối số giống như dict trở thành nghiện.

khuyết điểm

  • bóng tối typing.Dictnếu bạnfrom addict import Dict
  • Không kiểm tra chìa khóa. Do cho phép init đệ quy, nếu bạn viết sai một khóa, bạn chỉ cần tạo một thuộc tính mới, thay vì KeyError (cảm ơn AljoSt)

munch ưu:

  • đặt tên độc đáo
  • các hàm ser / de tích hợp cho JSON và YAML

khuyết điểm munch:

  • không có init đệ quy / chỉ có thể init một attr tại một thời điểm

Trong đó tôi biên tập

Nhiều mặt trăng trước đây, khi tôi sử dụng trình soạn thảo văn bản để viết python, trên các dự án chỉ có bản thân tôi hoặc một nhà phát triển khác, tôi thích phong cách của dict-attrs, khả năng chèn khóa bằng cách chỉ cần khai báo foo.bar.spam = eggs. Bây giờ tôi làm việc theo nhóm và sử dụng IDE cho mọi thứ và tôi đã tránh xa các loại cấu trúc dữ liệu và gõ động nói chung, thiên về phân tích tĩnh, kỹ thuật chức năng và gợi ý gõ. Tôi đã bắt đầu thử nghiệm kỹ thuật này, phân lớp Pstruct với các đối tượng theo thiết kế của riêng tôi:

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']

Điều này cung cấp cho bạn một đối tượng vẫn hoạt động như một dict, nhưng cũng cho phép bạn truy cập các khóa như các thuộc tính, theo cách cứng nhắc hơn nhiều. Lợi thế ở đây là tôi (hoặc những người tiêu dùng không có mã của bạn) biết chính xác những trường nào có thể và không thể tồn tại và IDE có thể tự động hoàn thành các trường. Ngoài ra phân lớp vanilla dictcó nghĩa là tuần tự json là dễ dàng. Tôi nghĩ rằng sự phát triển tiếp theo trong ý tưởng này sẽ là một trình tạo protobuf tùy chỉnh phát ra các giao diện này, và một điều tuyệt vời là bạn có được cấu trúc dữ liệu ngôn ngữ chéo và IPC thông qua gRPC gần như miễn phí.

Nếu bạn quyết định đi cùng với các chuyên gia, điều cần thiết là ghi lại những lĩnh vực nào được mong đợi, cho sự tỉnh táo của chính bạn (và của đồng đội).

Hãy chỉnh sửa / cập nhật bài đăng này để giữ cho nó gần đây!


2
một nhược điểm lớn addictlà nó sẽ không đưa ra ngoại lệ khi bạn viết sai một thuộc tính, vì nó sẽ trả về một cái mới Dict(điều này là cần thiết để foo.abc = 'bar' hoạt động).
AljoSt

5

Dưới đây là một ví dụ ngắn về các bản ghi bất biến sử dụng tích hợp collections.namedtuple:

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

và một ví dụ sử dụng:

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

print rec.loss(..)

5

Chỉ cần thêm một số loại cho câu trả lời, sci-kit learn đã thực hiện điều này như một 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                       

Tất cả những gì bạn cần là lấy setattrgetattrcác phương thức - getattrkiểm tra các khóa dict và chuyển sang kiểm tra các thuộc tính thực tế. Đây setstaetlà bản sửa lỗi để khắc phục / tháo gỡ các "bó" - nếu kiểm tra không kiểm tra https://github.com/scikit-learn/scikit-learn/issues/6196


3

Không cần phải viết riêng của bạn như setattr () và getattr () đã tồn tại.

Lợi thế của các đối tượng lớp có thể phát huy trong định nghĩa và kế thừa lớp.


3

Tôi đã tạo ra điều này dựa trên đầu vào từ chủ đề này. Tôi cần phải sử dụng odict, vì vậy tôi phải ghi đè get và đặt attr. Tôi nghĩ rằng điều này sẽ làm việc cho phần lớn các sử dụng đặc biệt.

Cách sử dụng trông như thế này:

# 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

Lớp:

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)

Đây là một mẫu khá thú vị đã được đề cập trong luồng, nhưng nếu bạn chỉ muốn lấy một lệnh và chuyển đổi nó thành một đối tượng hoạt động với tự động hoàn thành trong IDE, v.v .:

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

3

Rõ ràng hiện đã có một thư viện cho việc này - https://pypi.python.org/pypi/attrdict - nơi thực hiện chức năng chính xác này cộng với việc hợp nhất đệ quy và tải json. Có thể là một giá trị.


3

Đây là những gì tôi sử dụng

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)

Đây là một câu trả lời nhanh chóng và bẩn. Quan sát / nhận xét duy nhất của tôi là tôi nghĩ rằng nhà xây dựng có tên sẽ chấp nhận một danh sách các chuỗi, vì vậy giải pháp của bạn có thể được đơn giản hóa (tôi nghĩ) để:namedtuple('Args', list(args.keys()))(**args)
Dan Nguyen

2

Bạn có thể làm điều đó bằng cách sử dụng lớp này tôi vừa thực hiện. Với lớp này, bạn có thể sử dụng Mapđối tượng như một từ điển khác (bao gồm cả tuần tự hóa json) hoặc với ký hiệu dấu chấm. Tôi hy vọng giúp bạn:

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]

Ví dụ sử dụng:

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
Lưu ý rằng nó có thể tạo bóng cho dictcác phương thức, ví dụ: m=Map(); m["keys"] = 42; m.keys()give TypeError: 'int' object is not callable.
bfontaine

@bfontaine Ý tưởng là một loại field/attributevà không phải là một method, nhưng nếu bạn chỉ định một phương thức thay vì một số bạn có thể truy cập phương thức đó với m.method().
epool

2

Hãy để tôi đăng một triển khai khác, dựa trên câu trả lời của Kinvais, nhưng tích hợp các ý tưởng từ AttributionDict được đề xuất trong http://databio.org/posts/python_AttributionDict.html .

Ưu điểm của phiên bản này là nó cũng hoạt động cho các từ điển lồng nhau:

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

Giải pháp là:

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

Điều gì sẽ là sự cẩn thận và cạm bẫy của việc truy cập các khóa dict theo cách này?

Như @Henry gợi ý, một lý do truy cập rải rác có thể không được sử dụng trong các câu lệnh là nó giới hạn các tên khóa chính cho các biến hợp lệ python, do đó hạn chế tất cả các tên có thể.

Sau đây là các ví dụ về lý do tại sao truy cập chấm chấm sẽ không hữu ích nói chung, được đưa ra một lệnh , d:

Hiệu lực

Các thuộc tính sau sẽ không hợp lệ trong 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  

Phong cách

Các quy ước PEP8 sẽ áp đặt một ràng buộc mềm đối với việc đặt tên thuộc tính:

A. Tên từ khóa dành riêng (hoặc hàm dựng sẵn):

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

Nếu tên của một đối số hàm đụng độ với một từ khóa dành riêng, thì tốt hơn là nên nối thêm một dấu gạch dưới duy nhất ...

B. Quy tắc trường hợp về phương thứctên biến :

Tên biến theo cùng quy ước như tên hàm.

d.Firstname
d.Country

Sử dụng quy tắc đặt tên hàm: chữ thường với các từ được phân tách bằng dấu gạch dưới khi cần thiết để cải thiện khả năng đọc.


Đôi khi những lo ngại này được nêu ra trong các thư viện như gấu trúc , cho phép truy cập rải rác các cột DataFrame theo tên. Cơ chế mặc định để giải quyết các hạn chế đặt tên cũng là ký hiệu mảng - một chuỗi trong ngoặc.

Nếu các ràng buộc này không áp dụng cho trường hợp sử dụng của bạn, có một số tùy chọn trên cấu trúc dữ liệu truy cập chấm .


1

Bạn có thể sử dụng dict_to_obj https://pypi.org/project/dict-to-obj/ Nó thực hiện chính xác những gì bạn yêu cầu

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

1
Đây là hình thức tốt để đặt .ideavà bất kỳ tệp nào do người dùng hoặc IDE tạo ra trong của bạn .gitignore.
DeusXMachina

1

Đây không phải là một câu trả lời 'tốt', nhưng tôi nghĩ rằng điều này là tiện lợi (nó không xử lý các ký tự lồng nhau ở dạng hiện tại). Đơn giản chỉ cần bọc dict của bạn trong một chức năng:

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

Bây giờ bạn có cú pháp hơi khác nhau. Để gia nhập các mục dict như các thuộc tính làm f.key. Để truy cập các mục dict (và các phương thức dict khác) theo cách thông thường f()['key']và chúng ta có thể cập nhật thuận tiện cho dict bằng cách gọi f với các đối số từ khóa và / hoặc một từ điển

Thí dụ

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'}

Và nó đây Tôi sẽ rất vui nếu có ai đề xuất lợi ích và hạn chế của phương pháp này.


0

Theo ghi nhận của Doug, có một gói Bunch mà bạn có thể sử dụng để đạt được obj.keychức năng. Trên thực tế có một phiên bản mới hơn được gọi là

NeoBunch

Mặc dù nó có một tính năng tuyệt vời khi chuyển đổi lệnh của bạn sang đối tượng NeoBunch thông qua chức năng neobunchify của nó . Tôi sử dụng các mẫu Mako rất nhiều và truyền dữ liệu dưới dạng các đối tượng NeoBunch giúp chúng dễ đọc hơn rất nhiều, vì vậy nếu bạn tình cờ sử dụng một lệnh chính tả trong chương trình Python của mình nhưng muốn ký hiệu dấu chấm trong mẫu Mako bạn có thể sử dụng theo cách đó:

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)))

Và mẫu Mako có thể trông như sau:

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

Liên kết đến NeoBunch là 404
DeusXMachina

0

Cách dễ nhất là định nghĩa một lớp, hãy gọi nó là Không gian tên. trong đó sử dụng đối tượng dict .update () trên dict. Sau đó, dict sẽ được coi là một đối tượng.

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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.