가능한 한 dict의 하위 클래스를 "완벽하게"만들려면 어떻게해야합니까?
최종 목표는 키가 소문자로되어있는 간단한 설명을하는 것입니다.
__getitem__
/을 재정의하면 __setitem__
get / set이 작동하지 않습니다. 어떻게 작동하게합니까? 반드시 개별적으로 구현할 필요는 없습니까?
피클 링이 작동하지 못하게하고 __setstate__
있습니까? 등 을 구현해야
합니까?
repr, update 및가 필요 __init__
합니까?
방금 사용해야합니까 mutablemapping
( UserDict
또는 사용하지 않아야하는 것 같습니다 DictMixin
)? 그렇다면 어떻게? 문서가 정확하게 깨달은 것은 아닙니다.
허용되는 대답은 첫 번째 접근법이지만 문제가 있기 때문에 실제로 서브 클래스를 대체하는 대안을 아무도 다루지 않았으므로 dict
여기서 할 것입니다.
허용되는 답변에 어떤 문제가 있습니까?
이것은 나에게 다소 간단한 요청처럼 보입니다.
가능한 한 dict의 하위 클래스를 "완벽하게"만들려면 어떻게해야합니까? 최종 목표는 키가 소문자로되어있는 간단한 설명을하는 것입니다.
허용 된 답변은 실제로 서브 클래스가 아니며이 dict
테스트는 실패합니다.
>>> isinstance(MyTransformedDict([('Test', 'test')]), dict)
False
이상적으로 모든 유형 검사 코드는 예상되는 인터페이스 또는 추상 기본 클래스를 테스트하지만 데이터 객체가 테스트중인 함수에 전달되고 dict
해당 함수를 "수정"할 수없는 경우이 코드 실패합니다.
다른 퀴즈는 다음과 같습니다.
- 허용 된 답변에도 classmethod가 없습니다
fromkeys
.
허용 된 답변에는 중복이 __dict__
있으므로 메모리에서 더 많은 공간을 차지합니다.
>>> s.foo = 'bar'
>>> s.__dict__
{'foo': 'bar', 'store': {'test': 'test'}}
실제로 서브 클래 싱 dict
상속을 통해 dict 메소드를 재사용 할 수 있습니다. 키가 문자열 인 경우 딕셔너리로 키가 전달되도록하는 인터페이스 레이어를 작성하기 만하면됩니다.
__getitem__
/을 재정의하면 __setitem__
get / set이 작동하지 않습니다. 어떻게 작동하게합니까? 반드시 개별적으로 구현할 필요는 없습니까?
글쎄, 그것들을 각각 개별적으로 구현하는 것이이 접근법의 단점과 사용의 단점 MutableMapping
(허용 된 답변 참조)이지만 실제로 그렇게 많은 것은 아닙니다.
먼저 파이썬 2와 3의 차이점을 배제하고 싱글 톤 ( _RaiseKeyError
)을 만들어 실제로 인수를 얻는 지 dict.pop
확인하고 문자열 키가 소문자인지 확인하는 함수를 만들어 봅시다 .
from itertools import chain
try: # Python 2
str_base = basestring
items = 'iteritems'
except NameError: # Python 3
str_base = str, bytes, bytearray
items = 'items'
_RaiseKeyError = object() # singleton for no-default behavior
def ensure_lower(maybe_str):
"""dict keys can be any hashable object - only call lower if str"""
return maybe_str.lower() if isinstance(maybe_str, str_base) else maybe_str
이제 우리는 구현합니다-나는 super
이 코드가 파이썬 2와 3에서 작동하도록 전체 인수와 함께 사용 하고 있습니다
class LowerDict(dict): # dicts take a mapping or iterable as their optional first argument
__slots__ = () # no __dict__ - that would be redundant
@staticmethod # because this doesn't make sense as a global function.
def _process_args(mapping=(), **kwargs):
if hasattr(mapping, items):
mapping = getattr(mapping, items)()
return ((ensure_lower(k), v) for k, v in chain(mapping, getattr(kwargs, items)()))
def __init__(self, mapping=(), **kwargs):
super(LowerDict, self).__init__(self._process_args(mapping, **kwargs))
def __getitem__(self, k):
return super(LowerDict, self).__getitem__(ensure_lower(k))
def __setitem__(self, k, v):
return super(LowerDict, self).__setitem__(ensure_lower(k), v)
def __delitem__(self, k):
return super(LowerDict, self).__delitem__(ensure_lower(k))
def get(self, k, default=None):
return super(LowerDict, self).get(ensure_lower(k), default)
def setdefault(self, k, default=None):
return super(LowerDict, self).setdefault(ensure_lower(k), default)
def pop(self, k, v=_RaiseKeyError):
if v is _RaiseKeyError:
return super(LowerDict, self).pop(ensure_lower(k))
return super(LowerDict, self).pop(ensure_lower(k), v)
def update(self, mapping=(), **kwargs):
super(LowerDict, self).update(self._process_args(mapping, **kwargs))
def __contains__(self, k):
return super(LowerDict, self).__contains__(ensure_lower(k))
def copy(self): # don't delegate w/ super - dict.copy() -> dict :(
return type(self)(self)
@classmethod
def fromkeys(cls, keys, v=None):
return super(LowerDict, cls).fromkeys((ensure_lower(k) for k in keys), v)
def __repr__(self):
return '{0}({1})'.format(type(self).__name__, super(LowerDict, self).__repr__())
우리는 어떤 방법 또는 특별한 방법에 대한 거의 보일러 플레이트 방식을 사용 참조하는 핵심하지만, 그렇지 않으면 상속에 의해, 우리는 방법을 얻을 : len
, clear
, items
, keys
, popitem
, 및 values
무료. 이를 위해서는 신중한 생각이 필요했지만 이것이 효과가 있다는 것은 사소한 일입니다.
( haskey
Python 2에서는 더 이상 사용되지 않으며 Python 3에서는 제거되었습니다.)
사용법은 다음과 같습니다.
>>> ld = LowerDict(dict(foo='bar'))
>>> ld['FOO']
'bar'
>>> ld['foo']
'bar'
>>> ld.pop('FoO')
'bar'
>>> ld.setdefault('Foo')
>>> ld
{'foo': None}
>>> ld.get('Bar')
>>> ld.setdefault('Bar')
>>> ld
{'bar': None, 'foo': None}
>>> ld.popitem()
('bar', None)
피클 링이 작동하지 못하게하고 __setstate__
있습니까? 등 을 구현해야
합니까?
산세
그리고 dict 서브 클래스 피클은 잘 작동합니다.
>>> import pickle
>>> pickle.dumps(ld)
b'\x80\x03c__main__\nLowerDict\nq\x00)\x81q\x01X\x03\x00\x00\x00fooq\x02Ns.'
>>> pickle.loads(pickle.dumps(ld))
{'foo': None}
>>> type(pickle.loads(pickle.dumps(ld)))
<class '__main__.LowerDict'>
__repr__
repr, update 및가 필요 __init__
합니까?
우리는 update
및을 정의 __init__
했지만 __repr__
기본적으로 아름답 습니다.
>>> ld # without __repr__ defined for the class, we get this
{'foo': None}
그러나 __repr__
코드의 디버깅 가능성을 향상시키기 위해 를 작성하는 것이 좋습니다 . 이상적인 테스트는 eval(repr(obj)) == obj
입니다. 코드를 작성하기 쉬운 경우 다음을 강력히 권장합니다.
>>> ld = LowerDict({})
>>> eval(repr(ld)) == ld
True
>>> ld = LowerDict(dict(a=1, b=2, c=3))
>>> eval(repr(ld)) == ld
True
보시다시피, 이는 동등한 객체를 재생성하는 데 필요한 것입니다. 이것은 로그 나 역 추적에 나타날 수 있습니다.
>>> ld
LowerDict({'a': 1, 'c': 3, 'b': 2})
결론
방금 사용해야합니까 mutablemapping
( UserDict
또는 사용하지 않아야하는 것 같습니다 DictMixin
)? 그렇다면 어떻게? 문서가 정확하게 깨달은 것은 아닙니다.
예,이 코드는 몇 줄 더 있지만 포괄적으로 작성되었습니다. 내 첫 번째 성향은 받아 들여진 대답을 사용하는 것이며, 문제가 있으면 조금 더 복잡하고 내 인터페이스를 올바르게 얻는 데 도움이되는 ABC가 없기 때문에 내 대답을 살펴볼 것입니다.
조기 검색은 성능 검색의 복잡성을 증가시킵니다.
MutableMapping
더 간단합니다. 따라서 즉각적인 우위를 점할 수 있습니다. 그럼에도 불구하고 모든 차이점을 설명하기 위해 비교하고 대조합시다.
비슷한 사전을 collections
모듈 에 넣는 푸시가 있었지만 거부되었습니다 . 아마도 이것을 대신해야합니다.
my_dict[transform(key)]
훨씬 쉽게 디버깅 할 수 있어야합니다.
비교와 대조
MutableMapping
(누락 된 fromkeys
)으로 구현 된 6 개의 인터페이스 함수 와 dict
서브 클래스로 11 개의 인터페이스 함수가 있습니다 . 나는 구현할 필요가 없다 __iter__
거나 __len__
, 대신에 내가 구현해야한다 get
, setdefault
, pop
, update
, copy
, __contains__
, 그리고 fromkeys
내가 그 구현의 대부분을 상속을 사용할 수 있기 때문에, 그러나 이들은 매우 사소한 -.
이 MutableMapping
구현은 파이썬 dict
에서 C로 구현 하는 것을 구현하므로 dict
하위 클래스가 더 성능 이 좋을 것으로 기대합니다 .
우리 __eq__
는 두 가지 접근 방식에서 모두 자유 를 얻습니다 . 두 가지 접근 방식은 다른 dict가 모두 소문자 인 경우에만 평등을 가정하지만 dict
하위 클래스는 더 빨리 비교할 것이라고 생각합니다 .
요약:
MutableMapping
버그에 대한 기회는 적지 만 서브 클래 싱 은 더 간단하지만 더 느리며 더 많은 메모리를 필요로하며 (중복 된 dict 참조) 실패isinstance(x, dict)
- 서브 클래 싱
dict
은 더 빠르고 메모리를 덜 사용하며 통과 isinstance(x, dict)
하지만 구현하기가 더 복잡합니다.
어느 것이 더 완벽합니까? 그것은 당신의 완벽한 정의에 달려 있습니다.