dict을“완벽하게”재정의하는 방법은 무엇입니까?


218

가능한 한 dict 의 하위 클래스를 "완벽하게"만들려면 어떻게 해야합니까? 최종 목표는 단순한를 가지고있다 DICT 키가 소문자되는합니다.

이 작업을 수행하기 위해 재정의 할 수있는 몇 가지 작은 기본 요소가 있어야하는 것처럼 보이지만 모든 연구와 시도에 따르면 그렇지 않은 것처럼 보입니다.

  • 내가하면 오버라이드 (override) __getitem__/__setitem__ 다음 get/ set일을하지 않습니다. 어떻게 작동시킬 수 있습니까? 반드시 개별적으로 구현할 필요는 없습니까?

  • 피클 링이 작동하지 못하게하고 __setstate__있습니까? 등 을 구현해야 합니까?

  • 나는 마십시오 필요 repr, update그리고__init__ ?

  • 난 그냥해야 mutablemapping 사용 (이 사람이 사용해서는 안 보인다 UserDict 거나 DictMixin)? 그렇다면 어떻게? 문서가 정확하게 깨달은 것은 아닙니다.

여기에 첫 번째 시도가 있으며 get()작동하지 않으며 다른 많은 사소한 문제가 있습니다.

class arbitrary_dict(dict):
    """A dictionary that applies an arbitrary key-altering function
       before accessing the keys."""

    def __keytransform__(self, key):
        return key

    # Overridden methods. List from 
    # /programming/2390827/how-to-properly-subclass-dict

    def __init__(self, *args, **kwargs):
        self.update(*args, **kwargs)

    # Note: I'm using dict directly, since super(dict, self) doesn't work.
    # I'm not sure why, perhaps dict is not a new-style class.

    def __getitem__(self, key):
        return dict.__getitem__(self, self.__keytransform__(key))

    def __setitem__(self, key, value):
        return dict.__setitem__(self, self.__keytransform__(key), value)

    def __delitem__(self, key):
        return dict.__delitem__(self, self.__keytransform__(key))

    def __contains__(self, key):
        return dict.__contains__(self, self.__keytransform__(key))


class lcdict(arbitrary_dict):
    def __keytransform__(self, key):
        return str(key).lower()

__keytransform __ ()은 정적이어야한다고 생각합니다. 그래도 좋은 접근 방식입니다. (@staticmethod 앞에 추가)
Aiyion.Prime

답변:


229

모듈 에서 ABC (Abstract Base Classes)를 사용하여 dict매우 쉽게 동작하는 객체를 작성할 수 있습니다 . 방법을 놓친 경우에도 알려주므로 ABC를 종료하는 최소 버전은 다음과 같습니다.collections.abc

from collections.abc import MutableMapping


class TransformedDict(MutableMapping):
    """A dictionary that applies an arbitrary key-altering
       function before accessing the keys"""

    def __init__(self, *args, **kwargs):
        self.store = dict()
        self.update(dict(*args, **kwargs))  # use the free update to set keys

    def __getitem__(self, key):
        return self.store[self.__keytransform__(key)]

    def __setitem__(self, key, value):
        self.store[self.__keytransform__(key)] = value

    def __delitem__(self, key):
        del self.store[self.__keytransform__(key)]

    def __iter__(self):
        return iter(self.store)

    def __len__(self):
        return len(self.store)

    def __keytransform__(self, key):
        return key

ABC에서 몇 가지 무료 방법을 얻을 수 있습니다.

class MyTransformedDict(TransformedDict):

    def __keytransform__(self, key):
        return key.lower()


s = MyTransformedDict([('Test', 'test')])

assert s.get('TEST') is s['test']   # free get
assert 'TeSt' in s                  # free __contains__
                                    # free setdefault, __eq__, and so on

import pickle
# works too since we just use a normal dict
assert pickle.loads(pickle.dumps(s)) == s

나는 dict직접 하위 클래스 (또는 다른 내장)를 사용 하지 않을 것 입니다. 실제로 원하는 것은 의 인터페이스를 구현dict 하기 때문에 종종 의미가 없습니다 . 이것이 바로 ABC의 목적입니다.


46
설명 : 이름 지정 스타일 섹션 __keytransform__()의 끝에있는 "그러한 이름을 발명하지 말고 문서화 된 대로만 사용하십시오"라는 PEP 8 스타일 안내서를 위반하기 때문에 이름을 바꾸는 것이 좋습니다 .
martineau

1
질문-사용자 정의 유형 으로이 인터페이스를 구현하면 일반적으로 내장 유형을 사용하는 느린 dict-like 작업이 발생하지 않습니까?
twneale

2
isinstance (_, dict) == True을 수행하는 방법이 있습니까? 아니면 Mutable Mapping을 사용하여 하위 클래스를 구성하고 있습니까?
Andy Hayden

5
@AndyHayden : 작성해야합니다 if isinstance(t, collections.MutableMapping): print t, "can be used like a dict". 객체의 유형을 확인하지 말고 인터페이스를 확인하십시오.
Jochen Ritzel

2
@NeilG 불행히도 파이썬 표준 라이브러리에 JSONEncoder가 포함되어 있습니다 -github.com/python-git/python/blob/…
Andy Smith

97

가능한 한 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무료. 이를 위해서는 신중한 생각이 필요했지만 이것이 효과가 있다는 것은 사소한 일입니다.

( haskeyPython 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)하지만 구현하기가 더 복잡합니다.

어느 것이 더 완벽합니까? 그것은 당신의 완벽한 정의에 달려 있습니다.


받아 들여진 대답은 어떻게 중복 된 예언을 제거 할 것입니까?
Seanny123

1
즉시 염두에 두는 두 가지 방법은 상점 속성을 상점에 선언하거나 상점으로 __slots__재사용하는 __dict__것이지만 다른 잠재적 비판 점 인 의미를 혼합하는 것입니다.
Aaron Hall

1
메서드를 사용 ensure_lower하고 첫 번째 arguemtn (항상 열쇠 임)을 사용하는 데코레이터를 작성하는 것이 쉽지 않았 습니까? 그런 다음 동일한 수의 재정의가되지만 모두 형식이 __getitem__ = ensure_lower_decorator(super(LowerDict, self).__getitem__)됩니다.
Graipher

1
이것에 감사합니다-팝 및 fromkey에 대한 경고가 기본 클래스 메소드의 서명과 일치하지 않는다는 경고가 표시됩니다.
Mr_and_Mrs_D

1
@Mr_and_Mrs_D 구현을 추가했습니다 copy-그렇게해야한다고 생각합니까? 인터페이스를 테스트해야한다고 생각합니다. 예를 들어 pandas DataFrame 객체는 Mapping 인스턴스 (마지막 확인시)가 아니지만 항목 / 항목이 있습니다.
Aaron Hall

4

내 요구 사항은 조금 더 엄격했습니다.

  • 케이스 정보를 유지해야했습니다 (문자열은 사용자에게 표시되는 파일의 경로이지만 Windows 응용 프로그램이므로 내부적으로 모든 작업은 대소 문자를 구분하지 않아야 함)
  • 나는 (그것을 가능한 한 작게 할 키를 필요로 했다 (370)에서 1백10메가바이트 떨어져 다진 메모리 성능의 차이를 만들). 이는 소문자 버전의 키 캐싱이 옵션이 아님을 의미했습니다.
  • 가능한 빨리 데이터 구조를 만들어야했습니다 (이번에는 성능의 차이를 만들어 속도를 높였습니다). 나는 내장과 함께 가야했다

내 초기 생각은 대소 문자를 구분하지 않는 유니 코드 하위 클래스 대신 clunky Path 클래스를 대체하는 것이 었습니다.

  • 그것을 올바르게 얻기가 어렵다는 것을 알았습니다- 파이썬에서 대소 문자를 구분하지 않는 문자열 클래스를 참조하십시오
  • 명백한 dict 키 처리는 코드를 장황하고 어수선하게 만들고 오류가 발생하기 쉽습니다 (구조가 계속 전달되고 CIStr 인스턴스가 키 / 요소로 존재하는지 잊어 버릴 수 some_dict[CIstr(path)]있고 추악한 것이 확실하지 않음 )

그래서 나는 마침내 대소 문자를 구분하지 않는 dict을 적어야했습니다. @AaronHall의 코드 덕분에 10 배 더 쉬워졌습니다.

class CIstr(unicode):
    """See https://stackoverflow.com/a/43122305/281545, especially for inlines"""
    __slots__ = () # does make a difference in memory performance

    #--Hash/Compare
    def __hash__(self):
        return hash(self.lower())
    def __eq__(self, other):
        if isinstance(other, CIstr):
            return self.lower() == other.lower()
        return NotImplemented
    def __ne__(self, other):
        if isinstance(other, CIstr):
            return self.lower() != other.lower()
        return NotImplemented
    def __lt__(self, other):
        if isinstance(other, CIstr):
            return self.lower() < other.lower()
        return NotImplemented
    def __ge__(self, other):
        if isinstance(other, CIstr):
            return self.lower() >= other.lower()
        return NotImplemented
    def __gt__(self, other):
        if isinstance(other, CIstr):
            return self.lower() > other.lower()
        return NotImplemented
    def __le__(self, other):
        if isinstance(other, CIstr):
            return self.lower() <= other.lower()
        return NotImplemented
    #--repr
    def __repr__(self):
        return '{0}({1})'.format(type(self).__name__,
                                 super(CIstr, self).__repr__())

def _ci_str(maybe_str):
    """dict keys can be any hashable object - only call CIstr if str"""
    return CIstr(maybe_str) if isinstance(maybe_str, basestring) else maybe_str

class LowerDict(dict):
    """Dictionary that transforms its keys to CIstr instances.
    Adapted from: https://stackoverflow.com/a/39375731/281545
    """
    __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, 'iteritems'):
            mapping = getattr(mapping, 'iteritems')()
        return ((_ci_str(k), v) for k, v in
                chain(mapping, getattr(kwargs, 'iteritems')()))
    def __init__(self, mapping=(), **kwargs):
        # dicts take a mapping or iterable as their optional first argument
        super(LowerDict, self).__init__(self._process_args(mapping, **kwargs))
    def __getitem__(self, k):
        return super(LowerDict, self).__getitem__(_ci_str(k))
    def __setitem__(self, k, v):
        return super(LowerDict, self).__setitem__(_ci_str(k), v)
    def __delitem__(self, k):
        return super(LowerDict, self).__delitem__(_ci_str(k))
    def copy(self): # don't delegate w/ super - dict.copy() -> dict :(
        return type(self)(self)
    def get(self, k, default=None):
        return super(LowerDict, self).get(_ci_str(k), default)
    def setdefault(self, k, default=None):
        return super(LowerDict, self).setdefault(_ci_str(k), default)
    __no_default = object()
    def pop(self, k, v=__no_default):
        if v is LowerDict.__no_default:
            # super will raise KeyError if no default and key does not exist
            return super(LowerDict, self).pop(_ci_str(k))
        return super(LowerDict, self).pop(_ci_str(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__(_ci_str(k))
    @classmethod
    def fromkeys(cls, keys, v=None):
        return super(LowerDict, cls).fromkeys((_ci_str(k) for k in keys), v)
    def __repr__(self):
        return '{0}({1})'.format(type(self).__name__,
                                 super(LowerDict, self).__repr__())

암시 적 대 명시 적 문제는 여전히 문제이지만 일단 먼지가 정착되면 ci로 시작하도록 속성 / 변수의 이름을 바꾸면 ci가 대소 문자를 구분하지 않는다는 것을 설명하는 큰 뚱뚱한 의사 의견이 있습니다. 대소 문자를 구분하지 않는 기본 데이터 구조를 처리하고 있음을 충분히 알고 있어야합니다. 이렇게하면 버그를 재현하기 어려운 일부 버그를 수정하여 대소 문자를 구분할 수 있습니다.

의견 / 수정 환영 :)


CIstr __repr__은 부모 클래스를 사용하여 __repr__eval (repr (obj)) == obj test (지금은 그렇게 생각하지 않습니다)를 통과하고 의존하지 않아야합니다 __str__.
Aaron Hall

또한 total_ordering클래스 데코레이터를 확인하십시오 -그러면 유니 코드 하위 클래스에서 4 개의 메소드가 제거됩니다. 그러나 dict 서브 클래스는 매우 영리하게 구현되었습니다. : P
Aaron Hall

감사합니다 @AaronHall-구현 한 사람은 : P Re : total ordering-Raymond Hettinger가 조언 한대로 인라인 된 방법을 의도적으로 작성했습니다 : stackoverflow.com/a/43122305/281545 . 다시 : repr : 나는 (심지어 일부 핵심 개발자 IIRC에 의한) 주석을 읽는 것을 기억합니다. 실제로 테스트를 통과하려고 repr을 시도하고 번거롭게하는 것은 가치가 없습니다. (가능한 번거 롭습니다)
Mr_and_Mrs_D

나는 당신의 중복 비교 방법 (당신이 당신의 대답에 대한 메모를해야한다) 당신이 할 수 있습니다,하지만이 CIstr.__repr__귀하의 경우, 약간의 번거 로움과에 repr 테스트를 통과 할 수 있으며, 그것은 많은 더 좋은 디버깅을해야한다. 나는 또한 __repr__당신의 받아쓰기를 위해 추가 할 것 입니다. 나는 대답하기 위해 그것을 할 것이다.
Aaron Hall

@AaronHall : CIstr에 추가 __slots__했습니다-성능에 차이가 있습니다 (CIstr은 하위 클래스로 사용되거나 실제로 LowerDict 외부에서 사용되는 것이 아니며 정적 중첩 최종 클래스 여야합니다). 여전히 repr 문제를 우아하게 해결하는 방법을 잘 모릅니다 (sting에 따옴표 '"따옴표 가 포함될 수 있음 )
Mr_and_Mrs_D

4

당신이 할 일은

class BatchCollection(dict):
    def __init__(self, *args, **kwargs):
        dict.__init__(*args, **kwargs)

또는

class BatchCollection(dict):
    def __init__(self, inpt={}):
        super(BatchCollection, self).__init__(inpt)

개인적 사용을위한 샘플 사용법

### EXAMPLE
class BatchCollection(dict):
    def __init__(self, inpt={}):
        dict.__init__(*args, **kwargs)

    def __setitem__(self, key, item):
        if (isinstance(key, tuple) and len(key) == 2
                and isinstance(item, collections.Iterable)):
            # self.__dict__[key] = item
            super(BatchCollection, self).__setitem__(key, item)
        else:
            raise Exception(
                "Valid key should be a tuple (database_name, table_name) "
                "and value should be iterable")

참고 : python3에서만 테스트되었습니다.


3

상위 두 가지 제안을 모두 시도한 후 파이썬 2.7에 대한 그늘진 중간 경로를 정했습니다. 어쩌면 3은 더 신기하지만 나를 위해 :

class MyDict(MutableMapping):
   # ... the few __methods__ that mutablemapping requires
   # and then this monstrosity
   @property
   def __class__(self):
       return dict

정말 싫어하지만 내 요구에 맞는 것 같습니다.

  • 무시할 수있다 **my_dict
    • 당신이 상속하는 경우 dict, 이 코드를 무시합니다 . 사용해보십시오.
    • 이것은 파이썬 코드에서 매우 일반적 이기 때문에 # 2 를 항상 받아 들일 수 없게 만듭니다 .
  • 가장 무도회 isinstance(my_dict, dict)
    • MutableMapping 만 배제하므로 # 1 로는 충분하지 않습니다
    • 필요하지 않은 경우 # 1을 진심으로 추천합니다 . 간단하고 예측 가능합니다.
  • 완전히 제어 가능한 행동
    • 그래서 나는 상속받을 수 없다 dict

다른 사람들과 구별해야 할 필요가 있다면 개인적으로 다음과 같은 것을 사용합니다 (더 나은 이름을 추천하지만).

def __am_i_me(self):
  return True

@classmethod
def __is_it_me(cls, other):
  try:
    return other.__am_i_me()
  except Exception:
    return False

내부적으로 만 자신을 인식 해야하는 한,이 방법 __am_i_me으로 파이썬의 이름 문명으로 인해 실수로 호출하기가 더 어렵습니다 ( _MyDict__am_i_me이 클래스 외부에서 호출되는 이름으로 변경됨 ). _method실제로 나 문화적으로 s 보다 약간 더 사적인 것 .

지금까지 나는 진지해 보이는 __class__재정의를 제외하고는 불만이 없습니다 . 나는 거라고 흥분 다른 사람이 생각 발생할 것을, 나는 충분히 결과를 이해하지 않는 문제 듣고. 그러나 지금까지 나는 아무런 문제가 없었기 때문에 변경하지 않고도 많은 위치에서 많은 중간 품질 코드를 마이그레이션 할 수있었습니다.


증거로 : https://repl.it/repls/TraumaticToughCockatoo

기본적으로 현재 # 2 옵션을 복사 하고print 'method_name' 모든 메소드 에 행을 추가 한 후 시도하여 출력을보십시오.

d = LowerDict()  # prints "init", or whatever your print statement said
print '------'
splatted = dict(**d)  # note that there are no prints here

다른 시나리오에서도 비슷한 동작이 나타납니다. 가짜 dict가 다른 데이터 유형을 둘러싼 래퍼 라고 말하면 데이터를지지하는 데 사용할 합리적인 방법이 없습니다. **your_dict다른 모든 방법에 관계없이 비어 있습니다.

이것은에 대해 올바르게 작동 MutableMapping하지만 상속하자마자 dict제어 할 수 없게됩니다.


편집 : 업데이트로, 이것은 거의 2 년 동안 단일 문제없이 복잡하고 레거시 타기 된 파이썬 라인에서 수십만 라인에 실행되었습니다. 그래서 나는 그것에 매우 만족합니다 :)

편집 2 : 분명히 나는 ​​이것 또는 오래 전에 잘못 복사했습니다. 확인을 @classmethod __class__위해 작동하지 않습니다-https : //repl.it/repls/UnitedScientificSequenceisinstance@property __class__


정확히 무엇을 의미합니까 " **your_dict비어" (당신이에서 서브 클래스 경우 dict)? dict unpacking과 관련된 문제를 보지 못했습니다 ...
Matt P

LowerDict와 같이 실제로 부모 dict에 데이터를 넣으면 작동합니다. dict-stored 데이터를 얻을 수 있습니다. 그렇지 않으면 (즉, 읽을 때마다 채워지는 {access_count : "stack trace of access"}와 같이 즉시 데이터를 생성하려는 경우 ) **your_dict코드가 실행되지 않는 것을 알 수 있습니다. "특별한"어떤 것도 출력 할 수 없습니다. 예를 들어, "읽기"코드는 실행되지 않기 때문에 "읽기"를 계산할 수 없습니다. MutableMapping 이것을 위해 작동하지만 (가능한 경우 사용하십시오!)하지만 실패 isinstance(..., dict)하여 사용할 수 없었습니다. 예 레거시 소프트웨어.
Groxx

좋아, 지금 무슨 말인지 알 겠어. 나는 코드 실행을 기대하지 않았다고 생각 **your_dict하지만 MutableMapping그렇게하는 것이 매우 흥미 롭습니다 .
Matt P

네. 여러 가지 일이 필요합니다 (예 : 로컬 호출 읽기로 사용 된 RPC 호출을 쉬밍하고 Reasons ™에 대한 요청에 따라 수행해야 함). **some_dict상당히 일반적입니다. 최소한 당신이있는 경우에 그래서, 장식에 매우 자주 발생 하나를 당신이 그것을 고려하지 않는 경우, 당신은 겉으로는 불가능한 잘못된 행동의 위험에 즉시입니다.
Groxx

어쩌면 나는 뭔가를 놓치고 있지만 적어도 def __class__()트릭 서브 클래스로 abc.MutableMapping의 구현을 등록하는 방법에 대한 예제 코드의 경우 Python 2 또는 3 에서 트릭이 작동하지 않는 것 같습니다 . (두 버전에서 작동하도록 수정) isinstance(SpreadSheet(), dict)돌아가고 싶습니다 True.
martineau
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.