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


85

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

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
벌써 본 적있어? docs.python.org/3/reference/…
Garrett R

@GarrettR 도움이되지만이 특정 문제에 대한 많은 통찰력을 실제로 드러내지는 않습니다.
Zizouz212


@RickTeachey는 더 많은 사용자 정의 가능한 사전 표현을 찾고 있지만 Overloading ... 질문과 확실히 유사합니다. 예를 들어 객체는 내가 dict에서보고 싶지 않은 것들을 저장하고있을 수 있습니다.
penchant

귀하의 질문은 오해의 소지가 있으므로 개체를 dict 표현으로 변환하거나 캐스팅하고 싶지 않습니다 (다시 변환하면 개체를 완전히 복구 할 수 있음). 퍼블릭 인스턴스 변수에 대한 dict 표현보기 만 만들고 싶습니다.
smci

답변:


117

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

옵션 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오리와 같은 유형 의 표준 "이름"이되었습니다.


1
이것은 이다 영업 이익이 원하는 효과를 얻을 수있는 방법,하지만 당신은 아마 예를 들어, 그것은 다른 효과를 가지고 있음을 명확히해야한다 for k, v in my_object또한 활성화됩니다.
zwol

@RickTeachey 나는 당신이 기소 지점에서 무엇을 운전하고 있는지 봅니다. 객체가 반복 될 필요는 없습니다.
penchant

다른 링크 /programming/61517/python-dictionary-from-an-objects-fields에 언급 된대로 단순히 vars 함수를 사용할 수있는 이유. 이 질문에서 내가 놓친 것이 있습니까?
사용자

@user 영업의 질문은 회원 만 원 a, b그리고 c에를 dict. vars기능을 원하지 않은 사람을 포함하여 개체의 모든 구성원과 사전을 반환합니다 sumversion. 어떤 경우 vars에는 작동하지만 내 경험상 실제로 관용적 인 파이썬은 아닙니다. 일반적으로 사람들은 예를 들어 "이제 내 개체를 사전에 캐스팅하고 있습니다"라고 더 명확하게 말합니다 obj.asdict(). 를 사용하면 vars일반적으로 obj_kwargs = vars(obj)뒤에 오는 것과 비슷 합니다 MyObject(**obj_kwargs). 즉 : vars주로 객체를 복사하는 데 사용됩니다.
Rick은 Monica를

반환하는 메서드를 추가하는 것은 self.__dict__어떻습니까?
user32882

17

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

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


예, 클래스 전체 상태의 하위 집합 인 관련성 값을 반환하는 것이 제가 달성하고자하는 것입니다. 계산 / 비교 등의 작업을 더 빠르게 수행 할 수있는 "숨겨진"값이 저장되어 있지만 인간이보고 놀기 원하는 것이 아니며 실제로 개체 자체를 표현하지도 않습니다.
penchant

@ tr3buchet 예제 코드에서 해당 값은 initializer에 전달됩니다 . 이것이 실제 코드에있는 방식입니까? 캐시 된 계산이 인수로 전달되는 것이 이상하게 보입니다.
jpmc26

전혀 아니에요, 저는 단지 예를 제공하기 위해 그것을하고있었습니다
penchant

그냥 참고로 원하는 문서가 사용 묵과하는 것 __iter__"매핑의 경우, 컨테이너의 키를 반복해야하고, 또한 방법 iterkeys ()로 제공해야한다."다른 답변이 제안으로
dbenton

@dbenton OP 가하는 일이나 다른 답변이 권장하는 것이 아닙니다. OP의 객체는 기본적으로 컬렉션의 동의어로 사용되는 "컨테이너"가 아닙니다. OP의 객체는 또한 일반적으로 매핑이 의미하는 것이 아닙니다. 매핑은 임의의 수의 키와 값을 가지며 Mapping기본 클래스를 확장해야하는 것 입니다. 특정 명명 된 속성이있는 개체는이 범주에 속하지 않습니다.
jpmc26

11

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

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)

오 흥미 롭군요! 튜플의 반복 가능, 나는 이것을 고려하지 않았습니다. 빠른 테스트를 할게요
penchant

또한 튜플 목록의 이터를 반환하면 x, y, z의 모든 값이 이터의 기반이되는 목록에 스냅 샷됩니다. 3 개의 yield 문이있는 경우, 스레드 환경에서 이것을 실행하면 다른 스레드가 y 속성 또는 z 속성을 수정하여보고 된 x, y 및 z는 일관성이 없습니다.
PaulMcG

@PaulMcGuire : CPython은 Joran의 코드와도 모두 일치 할 것이라고 보장합니까? 예를 들어 첫 번째 튜플 x이 생성 된 후, 두 번째 튜플이 y. 내가 틀렸고 실제로 목록 이해는 스레드 스케줄링과 관련하여 원자 적입니까?
Steve Jessop

이제 여러분이 물어 보았으니, 파이썬의 스레드 스위처가 소스 수준이 아닌 바이트 코드 수준에서 그렇게하기 때문에 목록 이해도 원 자성이 보장되지 않는다는 것을 깨달았습니다. 그러나 노출은 목록을 만드는 기간 동안 만 이루어집니다. 목록이 생성되면 속성에 대한 업데이트는 중요하지 않습니다 (속성 값이 변경 가능한 개체가 아닌 경우). dis실제 바이트 코드를 보려면 모듈 을 사용하는 것이 가장 좋습니다 .
PaulMcG

11

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

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}


합계와 버전은 어떻게 되었습니까? __dict__제안 된 다른 답변과 같이 액세스하는 것과 어떻게 다른가요?
Joran Beasley

그것은 dict가 아닙니다. iteritems에 대한 호출은 반복자를 반환합니다
Garrett R

1
이 답변처럼 나는 ...하지만 그것은 ... 영업의 요구 사항을 충족하기 전에 조금 더 많은 작업이 필요
Joran 비즐리

이 대답은 @JoranBeasley와 실제로 다르지 않습니다. 차이점은 그의 self.__dict__사전 버전에서 원하지 않는 항목을 포함 하는 전체 를 잡는 반면 선택적 이라는 것입니다.
penchant

6

다른 많은 사람들과 마찬가지로 딕셔너리로의 캐스팅을 허용하는 것보다 (또는 추가로) 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

1

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

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

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

아니요, 그게 변수가 저장되는 곳입니다.
Zizouz212

3
나는 w.__dict__그것이 내가 필요한 것을 하지 않는다는 것을 구체적으로 지적했다 .
penchant

그러나 __dict__무시할 수 있습니까?
noisewaterphd

@noisewaterphd 더는 마법의 방법입니다 (아마도 당신은 그것을 무시할 수 없었다하지만,이 의미는 적어도 말을 무서운)
Joran 비즐리

1
dict에서 상속하지 않거나 더 나은 방법은 클래스에 메서드를 만들어 dict로 반환하는 것입니다.
noisewaterphd

0

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

매핑 프로토콜을 구현했습니다. 위의 답변에서 옵션 3 을 참조하십시오 . 또한 list키를 삭제 하려는 경우 __iter__값을 반복 하는 메서드를 추가 할 수 있으며 캐스트 dict는 계속 작동합니다. 일반적으로 사전과 함께 목록으로 캐스트하면 필요한 값이 아닌 키가 반환됩니다. 왜 이런 일이 발생하는지 분명하지 않으면 코드를 사용하는 다른 사람들에게 놀랍습니다.
Rick은
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.