[python] 파이썬에서 클래스 객체를 dict로 캐스팅하는 방법

파이썬으로 간단한 클래스가 있다고 가정 해 봅시다.

class Wharrgarbl(object):
    def __init__(self, a, b, c, sum, version='old'):
        self.a = a
        self.b = b
        self.c = c
        self.sum = 6
        self.version = version

    def __int__(self):
        return self.sum + 9000

    def __what_goes_here__(self):
        return {'a': self.a, 'b': self.b, 'c': self.c}

아주 쉽게 정수로 변환 할 수 있습니다.

>>> w = Wharrgarbl('one', 'two', 'three', 6)
>>> int(w)
9006

대단합니다! 하지만 이제 비슷한 방식으로 dict에 캐스팅하고 싶습니다.

>>> w = Wharrgarbl('one', 'two', 'three', 6)
>>> dict(w)
{'a': 'one', 'c': 'three', 'b': 'two'}

이것이 작동하려면 무엇을 정의해야합니까? 나는 모두 대체 시도 __dict__dict위해를 __what_goes_here__하지만, dict(w)A의 결과 TypeError: Wharrgarbl object is not iterable두 경우이다. 나는 단순히 클래스를 반복 가능하게 만드는 것이 문제를 해결할 것이라고 생각하지 않습니다. 나는 또한 내가 생각할 수있는 것처럼 “python cast object to dict”라는 다양한 표현으로 많은 구글을 ​​시도했지만 관련있는 것을 찾을 수 없었다 : {

또한! 호출 w.__dict__w.version및 을 포함 할 것이기 때문에 내가 원하는 작업을 수행하지 않는지 확인하십시오 w.sum. 를 사용하여 dict캐스트를 사용자 정의 할 수있는 것과 동일한 방식으로 캐스트를 사용자 정의하고 싶습니다 .intdef int(self)

난 그냥 이렇게 할 수 있다는 걸 알아

>>> w.__what_goes_here__()
{'a': 'one', 'c': 'three', 'b': 'two'}

하지만 만들 수있는 파이썬 방법이 가정하고 dict(w)이 같은 물건의 동일한 유형이기 때문에 일을 int(w)하거나 str(w). 더 비단뱀적인 방법이 없다면 그것도 괜찮습니다. 오! 중요하기 때문에 이것은 파이썬 2.7에 대한 것이지만 2.4 오래되고 파열 된 솔루션에 대한 슈퍼 보너스 포인트도 있습니다.

파이썬 클래스에서 __dict __ () 오버로딩하는 또 다른 질문 이 있습니다. 이것은 이것과 비슷하지만 중복이 아니라는 것을 보증하기에 충분히 다를 수 있습니다. 나는 OP가 그의 클래스 객체의 모든 데이터를 사전으로 캐스팅하는 방법을 요구하고 있다고 생각합니다. 에 __dict__의해 반환 된 사전 에 포함 된 모든 것을 원하지 않는다는 점에서보다 맞춤화 된 접근 방식을 찾고 있습니다 dict(). 공개 변수와 개인 변수와 같은 것이 내가 찾고있는 것을 설명하기에 충분할 수 있습니다. 객체는 계산에 사용되는 일부 값을 저장하므로 결과 사전에 표시 할 필요가 없습니다.

업데이트 : asdict제안 된 경로를 선택했지만 질문에 대한 답을 선택하는 것은 어려운 선택이었습니다. @RickTeachey와 @ jpmc26은 모두 내가 함께 할 대답을 제공했지만 전자는 더 많은 정보와 옵션을 가지고 있으며 동일한 결과에 도달했으며 더 많이 upvoted 그래서 함께 갔다. 하지만 모든 곳에서 찬성 투표를하고 도움을 주셔서 감사합니다. 나는 stackoverflow에 길고 열심히 숨어 있었고 물에 발가락을 더 많이 넣으려고 노력하고 있습니다.



답변

적어도 다섯 가지 방법이 있습니다. 선호하는 방법은 사용 사례에 따라 다릅니다.

옵션 1 : 간단히 asdict()방법을 추가합니다 .

문제 설명을 바탕으로 asdict다른 답변에서 제안한 작업 방법을 매우 많이 고려할 것입니다. 이것은 당신의 객체가 실제로 컬렉션의 많은 것 같지 않기 때문입니다.

class Wharrgarbl(object):

    ...

    def asdict(self):
        return {'a': self.a, 'b': self.b, 'c': self.c}

아래의 다른 옵션을 사용하면 어떤 객체 멤버가 반복되거나 키-값 쌍으로 지정 될지 여부가 명확하지 않은 경우 다른 옵션을 사용하면 혼동 될 수 있습니다.

옵션 1a : 클래스를 상속하고 'typing.NamedTuple'(또는 대부분 동등한 'collections.namedtuple') 제공된 _asdict메소드를 사용하십시오 .

from typing import NamedTuple

class Wharrgarbl(NamedTuple):
    a: str
    b: str
    c: str
    sum: int = 6
    version: str = 'old'

명명 된 튜플을 사용하는 것은 _asdict메서드를 포함 하여 최소한의 노력으로 클래스에 많은 기능을 추가하는 매우 편리한 방법 입니다. 상술 한 바와 같이 다소 한계가 인, 상기 NT는 포함 모든 그것의 부재 _asdict.

사전에 포함하지 않으려는 구성원이있는 경우 _asdict결과 를 수정해야합니다 .

from typing import NamedTuple

class Wharrgarbl(NamedTuple):
    a: str
    b: str
    c: str
    sum: int = 6
    version: str = 'old'

    def _asdict(self):
        d = super()._asdict()
        del d['sum']
        del d['version']
        return d

또 다른 제한은 NT가 읽기 전용이라는 것입니다. 이것은 바람직하거나 바람직하지 않을 수 있습니다.

옵션 2 : 구현 __iter__.

예를 들면 다음과 같습니다.

def __iter__(self):
    yield 'a', self.a
    yield 'b', self.b
    yield 'c', self.c

이제 다음을 수행 할 수 있습니다.

dict(my_object)

이는 생성자가 사전을 구성하기 위해 dict()반복 가능한 (key, value)쌍을 허용 하기 때문에 작동합니다 . 이 작업을 수행하기 전에 객체를 일련의 키, 값 쌍으로 반복하는 방식 (만들기에 편리하지만) dict이 실제로 다른 컨텍스트에서 놀라운 동작 일 수 있는지 스스로에게 질문하십시오 . 예를 들어, “의 행동은 무엇이어야 list(my_object)합니까?”라는 질문을 스스로에게 물어보십시오.

또한 항목 가져 오기 obj["a"]구문을 사용하여 값에 직접 액세스하는 것은 작동하지 않으며 키워드 인수 압축 해제도 작동하지 않습니다. 이를 위해서는 매핑 프로토콜을 구현해야합니다.

옵션 3 : 매핑 프로토콜을 구현 합니다 . 이를 통해 키별 액세스 동작을 허용하고 를 사용하지 않고로 캐스팅 할 수 있으며 모든 키가 문자열 ( ) 인 경우 압축 해제 동작 ( ) 및 키워드 압축 해제 동작 도 제공합니다 .dict__iter__{**my_obj}dict(**my_obj)

매핑 프로토콜을 사용하려면 (최소한) 두 가지 방법을 함께 제공해야합니다. keys()__getitem__.

class MyKwargUnpackable:
    def keys(self):
        return list("abc")
    def __getitem__(self, key):
        return dict(zip("abc", "one two three".split()))[key]

이제 다음과 같은 작업을 수행 할 수 있습니다.

>>> m=MyKwargUnpackable()
>>> m["a"]
'one'
>>> dict(m)  # cast to dict directly
{'a': 'one', 'b': 'two', 'c': 'three'}
>>> dict(**m)  # unpack as kwargs
{'a': 'one', 'b': 'two', 'c': 'three'}

위에서 언급했듯이 충분히 새로운 버전의 파이썬을 사용하는 경우 매핑-프로토콜 객체를 다음과 같이 사전 이해로 압축 해제 할 수도 있습니다 (이 경우 키가 문자열 일 필요는 없습니다).

>>> {**m}
{'a': 'one', 'b': 'two', 'c': 'three'}

매핑 프로토콜이 있습니다 보다 우선__iter__ A와 개체를 캐스팅 할 때 방법 dict(즉, kwarg 풀기를 사용하지 않고 직접 dict(m)). 따라서 객체가 반복 가능 (예 :)으로 사용될 list(m)때와 dict( dict(m))로 캐스트 될 때 다른 동작을 갖도록하는 것이 가능하고 때로는 편리합니다 .

강조 : 당신은 매핑 프로토콜을 사용할 수 있습니다해서, 당신은 것을 의미하지 않습니다 안된다 그렇게 . 객체가 키-값 쌍의 집합으로 전달되거나 키워드 인수 및 값으로 전달되는것이 실제로 의미 가 있습니까? 사전처럼 키로 액세스하는 것이 정말 의미가 있습니까?

이러한 질문에 대한 대답이 이면 다음 옵션을 고려하는 것이 좋습니다.

옵션 4 : 'collections.abc‘모듈 사용을 고려하십시오 .

클래스를 상속 'collections.abc.Mapping하거나 'collections.abc.MutableMapping다른 사용자에게 신호를 보내면 모든 인 텐트와 목적을 위해 클래스 가 매핑 *이며 그런 방식으로 작동 할 것으로 예상 할 수 있습니다.

원하는대로 개체를 캐스트 할 수 dict있지만 그렇게 할 이유가 거의 없을 것입니다. 덕 타이핑 때문에 매핑 객체를로 캐스팅 dict하는 것은 대부분의 경우 불필요한 추가 단계 일뿐입니다.

이 답변 도 도움 될 수 있습니다.

아래 주석에서 언급했듯이이 작업을 수행하면 abc 방식으로 기본적으로 객체 클래스가 dict유사 클래스 로 바뀐다는 점을 언급 할 가치가 있습니다 ( MutableMapping읽기 전용 Mapping기본 클래스 가 아니라 사용한다고 가정 ). 으로 할 수있는 모든 dict작업은 자신의 클래스 객체로 할 수 있습니다. 이것은 바람직하거나 바람직하지 않을 수 있습니다.

또한 numbers모듈 의 숫자 abc를 살펴보십시오 .

https://docs.python.org/3/library/numbers.html

또한 객체를으로 캐스팅하므로 캐스팅이 필요하지 않도록 int기본적으로 클래스를 본격적인 클래스로 바꾸는 것이 더 합리적 일 수 있습니다 int.

옵션 5 : 편리한 유틸리티 방법 이 포함 된 dataclasses모듈 (Python 3.7 만 해당) 사용을 살펴 봅니다 asdict().

from dataclasses import dataclass, asdict, field, InitVar

@dataclass
class Wharrgarbl(object):
    a: int
    b: int
    c: int
    sum: InitVar[int]  # note: InitVar will exclude this from the dict
    version: InitVar[str] = "old"

    def __post_init__(self, sum, version):
        self.sum = 6  # this looks like an OP mistake?
        self.version = str(version)

이제 다음과 같이 할 수 있습니다.

    >>> asdict(Wharrgarbl(1,2,3,4,"X"))
    {'a': 1, 'b': 2, 'c': 3}

옵션 6 : python 3.8typing.TypedDict추가 된을 사용 합니다.

참고 : 옵션 6은 가능성이 NOT 영업 이익, 또는이 질문의 제목에 따라 다른 독자가 찾고있는 것을. 아래의 추가 설명을 참조하십시오.

class Wharrgarbl(TypedDict):
    a: str
    b: str
    c: str

이 옵션을 사용하면 결과 객체 는입니다dict (강조 : a 가 아님Wharrgarbl ). (복사를하지 않는 한) dict에 “캐스트”할 이유가 전혀 없습니다.

그리고 객체 dict 이므로 초기화 서명은의 dict그것과 동일하므로 키워드 인수 또는 다른 사전 만 허용합니다.

    >>> w = Wharrgarbl(a=1,b=2,b=3)
    >>> w
    {'a': 1, 'b': 2, 'c': 3}
    >>> type(w)
    <class 'dict'>

강조 : 위의 “클래스” Wharrgarbl는 실제로 새로운 클래스가 아닙니다. dict유형 검사기에 대해 서로 다른 유형의 필드 를 사용하여 유형이 지정된 객체 를 생성하기위한 단순한 구문 설탕입니다 .

따라서이 옵션은 코드 판독기 (및 mypy와 같은 유형 검사기)에게 이러한 dict객체가 특정 값 유형을 가진 특정 키를 가질 것으로 예상 된다는 신호를 보내는 데 매우 편리 할 수 ​​있습니다 .

그러나 이것은 예를 들어 시도 할 수 있지만 다른 방법을 추가 할 수 없음을 의미합니다.

class MyDict(TypedDict):
    def my_fancy_method(self):
        return "world changing result"

…하지만 작동하지 않습니다.

>>> MyDict().my_fancy_method()
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'my_fancy_method'

* “매핑”은 dict오리와 같은 유형 의 표준 “이름”이되었습니다.


답변

원하는 것을 할 수있는 마법의 방법은 없습니다. 대답은 간단히 이름을 적절하게 지정하는 것입니다. 주로에서 영감을 얻은으로 asdict일반 변환을위한 합리적인 선택입니다 . 그러나 여러분의 메서드는 그 이름에서 즉시 명확하지 않을 수있는 특수 논리를 분명히 포함합니다. 클래스 상태의 하위 집합 만 반환합니다. 개념을 명확하게 전달하는 약간 더 자세한 이름을 생각 해낼 수 있다면 더 좋습니다.dictnamedtuple

다른 답변은 사용을 제안 __iter__하지만 객체가 진정으로 반복 가능하지 않는 한 (일련의 요소를 나타냄) 이것은 실제로 거의 의미가 없으며 메서드의 어색한 남용을 구성합니다. 클래스의 일부 상태를 필터링하려는 사실은이 접근 방식을 더욱 모호하게 만듭니다.


답변

이와 같은 것이 아마도 작동 할 것입니다.

class MyClass:
    def __init__(self,x,y,z):
       self.x = x
       self.y = y
       self.z = z
    def __iter__(self): #overridding this to return tuples of (key,value)
       return iter([('x',self.x),('y',self.y),('z',self.z)])

dict(MyClass(5,6,7)) # because dict knows how to deal with tuples of (key,value)


답변

나는 이것이 당신을 위해 일할 것이라고 생각합니다.

class A(object):
    def __init__(self, a, b, c, sum, version='old'):
        self.a = a
        self.b = b
        self.c = c
        self.sum = 6
        self.version = version

    def __int__(self):
        return self.sum + 9000

    def __iter__(self):
        return self.__dict__.iteritems()

a = A(1,2,3,4,5)
print dict(a)

산출

{ ‘a’: 1, ‘c’: 3, ‘b’: 2, ‘sum’: 6, ‘version’: 5}


답변

다른 많은 사람들과 마찬가지로 딕셔너리로의 캐스팅을 허용하는 것보다 (또는 추가로) to_dict () 함수를 구현하는 것이 좋습니다. 클래스가 그런 종류의 기능을 지원한다는 것이 더 분명해 졌다고 생각합니다. 다음과 같은 방법을 쉽게 구현할 수 있습니다.

def to_dict(self):
    class_vars = vars(MyClass)  # get any "default" attrs defined at the class level
    inst_vars = vars(self)  # get any attrs defined on the instance (self)
    all_vars = dict(class_vars)
    all_vars.update(inst_vars)
    # filter out private attributes
    public_vars = {k: v for k, v in all_vars.items() if not k.startswith('_')}
    return public_vars


답변

문제의 전체 컨텍스트를 알지 않고 말하기는 어렵지만 __iter__.

나는 __what_goes_here__수업에서 구현할 것 입니다.

as_dict(self:
    d = {...whatever you need...}
    return d


답변

a list또는 a “둘 다”인 클래스를 작성하려고 합니다 dict. 프로그래머가이 객체를 list(키 드롭) 또는 dict( 키 사용)으로 “캐스트”할 수 있기를 바랍니다 .

Python이 현재 dict()캐스트 를 수행하는 방식을 살펴보면 Mapping.update()전달 된 객체로 호출합니다 . 다음은 Python 저장소의 코드입니다 .

def update(self, other=(), /, **kwds):
    ''' D.update([E, ]**F) -> None.  Update D from mapping/iterable E and F.
        If E present and has a .keys() method, does:     for k in E: D[k] = E[k]
        If E present and lacks .keys() method, does:     for (k, v) in E: D[k] = v
        In either case, this is followed by: for k, v in F.items(): D[k] = v
    '''
    if isinstance(other, Mapping):
        for key in other:
            self[key] = other[key]
    elif hasattr(other, "keys"):
        for key in other.keys():
            self[key] = other[key]
    else:
        for key, value in other:
            self[key] = value
    for key, value in kwds.items():
        self[key] = value

반복되는 if 문의 마지막 하위 사례는 other대부분의 사람들이 염두에두고있는 것입니다. 그러나 보시 keys()다시피 속성 을 가질 수도 있습니다 . 즉, a와 결합 __getitem__()하면 하위 클래스를 사전에 적절하게 캐스팅하는 것이 쉬워야합니다.

class Wharrgarbl(object):
    def __init__(self, a, b, c, sum, version='old'):
        self.a = a
        self.b = b
        self.c = c
        self.sum = 6
        self.version = version

    def __int__(self):
        return self.sum + 9000

    def __keys__(self):
        return ["a", "b", "c"]

    def __getitem__(self, key):
        # have obj["a"] -> obj.a
        return self.__getattribute__(key)

그러면 작동합니다.

>>> w = Wharrgarbl('one', 'two', 'three', 6)
>>> dict(w)
{'a': 'one', 'c': 'three', 'b': 'two'}