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
키로 사용 하는 것은 불가능합니다.
문자열을 사용하지 않으려면 어떻게해야합니까?