[python] 속성과 같은 dict 키에 액세스합니까?

dict obj.foo대신 dict 키에 액세스하는 것이 더 편리하다는 것을 알았습니다 obj['foo']. 그래서이 스 니펫을 작성했습니다.

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

그러나 파이썬 이이 기능을 즉시 제공하지 않는 이유가 있다고 가정합니다. 이런 식으로 dict 키에 액세스 할 때의주의 사항과 함정은 무엇입니까?



답변

이를 수행하는 가장 좋은 방법은 다음과 같습니다.

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

일부 전문가 :

  • 실제로 작동합니다!
  • 사전 클래스 메소드가 음영 처리되지 않았습니다 (예 : .keys()제대로 작동합니다. 물론 값을 지정하지 않는 한 아래 참조)
  • 속성과 아이템은 항상 동기화
  • 존재하지 않는 키에 속성으로 액세스하려고하면 AttributeError대신에KeyError

단점 :

  • 들어오는 데이터로 덮어 쓰는 경우 와 같은 방법 .keys()제대로 작동 하지 않습니다.
  • 원인 메모리 누수 파이썬 <2.7.4 / Python3 <3.2.3
  • Pylint과 함께 바나나를 이동 E1123(unexpected-keyword-arg)하고E1103(maybe-no-member)
  • 처음에는 마치 순수한 마법처럼 보입니다.

작동 방식에 대한 간단한 설명

  • 모든 파이썬 객체는 내부적으로 속성을이라는 사전에 저장합니다 __dict__.
  • 내부 사전 __dict__이 “단순한 사전”일 필요는 없기 때문에 하위 dict()사전을 내부 사전에 할당 할 수 있습니다 .
  • 이 경우 AttrDict()인스턴스화하는 인스턴스를 (있는 그대로) 지정하면 __init__됩니다.
  • super()__init__()메소드 를 호출 하여 해당 함수가 모든 사전 인스턴스화 코드를 호출하기 때문에 (이미) 사전과 똑같이 동작하는지 확인했습니다 .

파이썬이이 기능을 즉시 제공하지 않는 한 가지 이유

“cons”목록에서 언급했듯이, 이것은 저장된 키의 네임 스페이스 (임의의 데이터 및 / 또는 신뢰할 수없는 데이터에서 올 수 있습니다!)를 내장 된 dict 메소드 속성의 네임 스페이스와 결합합니다. 예를 들면 다음과 같습니다.

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


답변

배열 표기법을 사용하는 경우 모든 유효한 문자열 문자를 키의 일부로 사용할 수 있습니다. 예를 들어obj['!#$%^&*()_']


답변

에서 이 다른 SO 질문 기존의 코드를 단순화 좋은 구현 예제가있다. 어때요 :

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

훨씬 더 간결은에 들어가기 추가 cruft에 대한 공간을 확보하지 않으며 __getattr____setattr__향후 기능.


답변

내가 묻는 질문에 대답

파이썬은 왜 그것을 즉시 제공하지 않습니까?

파이썬선과 관련 이 있다고 생각합니다 . “한 가지 확실한 방법이 있어야합니다.” 이 사전에서 액세스 값이 분명한 방법을 만들 것입니다 : obj['key']obj.key .

경고와 함정

여기에는 코드의 명확성 및 혼란 부족이 포함됩니다. 즉, 다음 에 나중에 코드를 유지 관리 하려는 다른 사람이나 잠시 동안 코드를 다시 사용하지 않으면 다른 사람 에게 혼란을 줄 수 있습니다 . 다시 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
>>> 

아니다. 이를 통해 객체 속성에 액세스 할 때 보유 할 수없는 사전 키에 대해 인쇄 가능한 모든 문자 또는 기타 해시 가능 객체에 액세스 할 수 있습니다. 이를 통해 Python Cookbook (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}
>>>

이것은 간단한 예이지만, obj.key표기법을 사용하는 것과 다른 상황에서 (예 : XML 파일에서 사전 설정을 읽어야 할 때) dicts를 자주 사용 합니다. 다른 경우에는 미학적 이유로 동적 클래스를 인스턴스화하고 그에 대한 일부 특성을 포기하려는 유혹을받는 경우 가독성을 높이기 위해 일관성에 대한 dict을 계속 사용합니다.

OP가 오랫동안 만족스럽게 해결 한 것으로 확신하지만 여전히이 기능을 원한다면 pypi에서 패키지 중 하나를 다운로드하여 제안합니다.

  • 무리 는 내가 더 친숙한 것입니다. 의 하위 클래스dict이므로 모든 기능이 있습니다.
  • AttrDict 도 꽤 좋은 것처럼 보이지만 나는 그것에 익숙하지 않고 Bunch 만큼 많은 소스를 보지 못했습니다.
  • Addict 적극적으로 유지 관리되며 attr와 같은 액세스 등을 제공합니다.
  • Rotareti의 의견에서 언급했듯이 Bunch는 더 이상 사용되지 않지만 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}


주석에서 후속 질문에 대답하기 위해 업데이트

엘모 는 다음 과 같은 의견에서 다음과 같이 묻습니다.

더 깊이 가고 싶다면 어떻게해야합니까? (type (…) 참조)

이 유스 케이스를 사용한 적이 없지만 ( dict일관 적으로 nested를 사용하는 경향이 있지만 ) 다음 코드가 작동합니다.

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


답변

주의 사항 : 어떤 이유로 이런 클래스는 멀티 프로세싱 패키지를 깨뜨리는 것 같습니다. 나는이 SO를 찾기 전에 잠시 동안이 버그로 고생했습니다 :
python multiprocessing에서 예외 찾기


답변

표준 라이브러리에서 편리한 컨테이너 클래스를 가져올 수 있습니다.

from argparse import Namespace

코드 비트를 복사하지 않아도됩니다. 표준 사전 액세스는 없지만 원하는 경우 쉽게 다시 가져올 수 있습니다. argparse의 코드는 간단합니다.

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__


답변

__eq__또는 같은 방법 인 키를 원한다면 어떻게해야 __getattr__합니까?

그리고 당신은 편지로 시작하지 않은 항목을 가질 수 없으므로 0343853키로 사용 하는 것은 불가능합니다.

문자열을 사용하지 않으려면 어떻게해야합니까?