파이썬에 가변 명명 된 튜플이 있습니까?


121

누구나 namedtuple을 수정 하거나 변경 가능한 객체에 대해 작동하도록 대체 클래스를 제공 할 수 있습니까 ?

주로 가독성을 위해 다음을 수행하는 namedtuple과 비슷한 것을 원합니다.

from Camelot import namedgroup

Point = namedgroup('Point', ['x', 'y'])
p = Point(0, 0)
p.x = 10

>>> p
Point(x=10, y=0)

>>> p.x *= 10
Point(x=100, y=0)

결과물을 피클 할 수 있어야합니다. 그리고 명명 된 튜플의 특성에 따라 표현 될 때 출력의 순서는 객체를 구성 할 때 매개 변수 목록의 순서와 일치해야합니다.


3
참조 : stackoverflow.com/q/5131044 . 사전 만 사용할 수없는 이유가 있나요?
senshin 2015 년

@senshin 링크 주셔서 감사합니다. 나는 그것에 지적 된 이유로 사전을 사용하지 않는 것을 선호한다. 이 응답은 code.activestate.com/recipes/… 에도 연결되어 있으며 , 이는 제가 추구 하는 것과 매우 유사합니다.
Alexander는

와 달리 namedtupleS, 당신이 필요 그래서 즉, 인덱스 속성을 참조 할 수 있도록이 없다 표시 p[0]p[1]참조에 다른 방법이 될 것입니다 xy각각 올바른을?
martineau 2015 년

이상적으로는 이름뿐 아니라 일반 튜플과 같은 위치별로 인덱싱 할 수 있으며 튜플처럼 압축을 풉니 다. 이 ActiveState 레시피는 비슷하지만 OrderedDict 대신 일반 사전을 사용한다고 생각합니다. code.activestate.com/recipes/500261
Alexander

2
가변 네임 튜플을 클래스라고합니다.
gbtimmon

답변:


132

로 변경 가능한 대안이 collections.namedtuple- recordclass은 .

API와 메모리 공간이 동일 namedtuple하며 할당을 지원합니다 (더 빠를 것입니다). 예를 들면 :

from recordclass import recordclass

Point = recordclass('Point', 'x y')

>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
>>> print(p.x, p.y)
1 2
>>> p.x += 2; p.y += 3; print(p)
Point(x=3, y=5)

Python 3.6 이상 recordclass(0.5 이후)의 경우 typehints를 지원합니다.

from recordclass import recordclass, RecordClass

class Point(RecordClass):
   x: int
   y: int

>>> Point.__annotations__
{'x':int, 'y':int}
>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
>>> print(p.x, p.y)
1 2
>>> p.x += 2; p.y += 3; print(p)
Point(x=3, y=5)

더 완전한 예가 있습니다 (성능 비교도 포함됨).

0.9 이후 recordclass라이브러리는 recordclass.structclass공장 기능이라는 또 다른 변형을 제공 합니다. 인스턴스가 __slots__기반 인스턴스 보다 적은 메모리를 차지하는 클래스를 생성 할 수 있습니다 . 이는 참조주기를 갖지 않는 속성 값이있는 인스턴스에 중요 할 수 있습니다. 수백만 개의 인스턴스를 만들어야하는 경우 메모리 사용량을 줄이는 데 도움이 될 수 있습니다. 다음은 예시적인 입니다.


4
좋아요. '이 라이브러리는 실제로 이름 tuple.`의 "가변"대안의 문제에 대한 "개념의 증거"입니다
알렉산더

1
recordclassAntti Haapala의 레시피 및 .NET 과 비교 하여 더 느리고 더 많은 메모리를 사용하며 C 확장이 필요합니다 namedlist.
GrantJ

recordclasscollection.namedtupleAPI, 메모리 풋 프린트를 상속하지만 할당을 지원 하는 변경 가능한 버전입니다 . namedlist실제로 슬롯이있는 파이썬 클래스의 인스턴스입니다. 인덱스로 필드에 빠르게 액세스 할 필요가없는 경우 더 유용합니다.
intellimath

recordclass예 를 들어 속성 액세스 (python 3.5.2)는 for보다 약 2-3 % 느립니다.namedlist
intellimath dec

namedtuple간단한 클래스 생성을 사용할 때 Point = namedtuple('Point', 'x y')Jedi는 속성을 자동 완성 할 수 있지만 recordclass. 더 긴 생성 코드 (기반 RecordClass)를 사용하면 Jedi는 Point클래스를 이해 하지만 생성 자나 속성은 이해 하지 못합니다. recordclassJedi와 잘 작동 할 수있는 방법이 있습니까?
PhilMacKay

34

types.SimpleNamespace 는 Python 3.3에서 도입되었으며 요청 된 요구 사항을 지원합니다.

from types import SimpleNamespace
t = SimpleNamespace(foo='bar')
t.ham = 'spam'
print(t)
namespace(foo='bar', ham='spam')
print(t.foo)
'bar'
import pickle
with open('/tmp/pickle', 'wb') as f:
    pickle.dump(t, f)

1
나는 수년 동안 이와 같은 것을 찾고 있었다. dotmap 같은 점 DICT 라이브러리에 대한 중대한 교체
액 스웰

1
더 많은 찬성표가 필요합니다. OP가 찾던 바로 그 것이고 표준 라이브러리에 있으며 사용하기가 더 간단 할 수 없습니다. 감사!
Tom Zych

3
-1 OP는 자신의 테스트를 통해 그가 필요로하는 것이 무엇인지 명확하게했고 테스트 SimpleNamespace6-10 (인덱스 별 액세스, 반복적 압축 풀기, 반복, 순서 지정 사전, 내부 교체) 및 12, 13 (필드, 슬롯)에 실패했습니다. 문서 (답변에 링크)에 " SimpleNamespace이를 대체하는 데 유용 할 수 있습니다 class NS: pass. 그러나 구조화 된 레코드 유형을 namedtuple()대신 사용 하는 경우 "라고 명시되어 있습니다 .
Ali

1
-1도 SimpleNamespace클래스 생성자가 아닌 객체를 생성하며 namedtuple을 대체 할 수 없습니다. 유형 비교가 작동하지 않으며 메모리 사용량이 훨씬 더 많아집니다.
RedGlyph

26

이 작업에 대한 매우 Pythonic 대안으로서, Python-3.7 이후로, 일반 클래스 정의를 사용하기 때문에 dataclasses변경 가능한 것처럼 동작 NamedTuple할뿐만 아니라 다른 클래스 기능도 지원 하는 모듈을 사용할 수 있습니다.

PEP-0557에서 :

매우 다른 메커니즘을 사용하지만 데이터 클래스는 "기본값이있는 변경 가능한 명명 된 튜플"로 생각할 수 있습니다. 데이터 클래스는 일반 클래스 정의 구문을 사용하기 때문에 상속, 메타 클래스, 독 스트링, 사용자 정의 메서드, 클래스 팩토리 및 기타 Python 클래스 기능을 자유롭게 사용할 수 있습니다.

PEP 526 , "Syntax for Variable Annotations"에 정의 된대로 유형 주석이있는 변수에 대한 클래스 정의를 검사하는 클래스 데코레이터가 제공됩니다 . 이 문서에서는 이러한 변수를 필드라고합니다. 데코레이터는 이러한 필드를 사용하여 생성 된 메서드 정의를 클래스에 추가하여 인스턴스 초기화, repr, 비교 메서드 및 선택적으로 사양 섹션에 설명 된 다른 메서드를 지원합니다 . 이러한 클래스를 데이터 클래스라고하지만 클래스에 특별한 것은 없습니다. 데코레이터는 생성 된 메서드를 클래스에 추가하고 제공된 것과 동일한 클래스를 반환합니다.

이 기능은 PEP-0557에 도입되었으며 제공된 문서 링크에서 자세한 내용을 읽을 수 있습니다.

예:

In [20]: from dataclasses import dataclass

In [21]: @dataclass
    ...: class InventoryItem:
    ...:     '''Class for keeping track of an item in inventory.'''
    ...:     name: str
    ...:     unit_price: float
    ...:     quantity_on_hand: int = 0
    ...: 
    ...:     def total_cost(self) -> float:
    ...:         return self.unit_price * self.quantity_on_hand
    ...:    

데모:

In [23]: II = InventoryItem('bisc', 2000)

In [24]: II
Out[24]: InventoryItem(name='bisc', unit_price=2000, quantity_on_hand=0)

In [25]: II.name = 'choco'

In [26]: II.name
Out[26]: 'choco'

In [27]: 

In [27]: II.unit_price *= 3

In [28]: II.unit_price
Out[28]: 6000

In [29]: II
Out[29]: InventoryItem(name='choco', unit_price=6000, quantity_on_hand=0)

1
OP의 테스트를 통해 무엇이 필요하고 dataclassPython 3.7에서 테스트 6-10 (인덱스, 반복 압축 풀기, 반복, 순서 지정 dict, 내부 교체) 및 12, 13 (필드, 슬롯)에 실패했습니다. .1.
Ali

1
이것은 OP가 찾고있는 것이 구체적이지 않을 수도 있지만 확실히 도움이되었습니다. :)
Martin CR

25

최신 namedlist 1.7은 2016 년 1 월 11 일자로 Python 2.7 및 Python 3.5를 사용하여 모든 테스트를 통과합니다 . 순수한 Python 구현 이지만 recordclass은 C 확장입니다. 물론 C 확장이 선호되는지 여부는 요구 사항에 따라 다릅니다.

테스트 (아래 참고 참조) :

from __future__ import print_function
import pickle
import sys
from namedlist import namedlist

Point = namedlist('Point', 'x y')
p = Point(x=1, y=2)

print('1. Mutation of field values')
p.x *= 10
p.y += 10
print('p: {}, {}\n'.format(p.x, p.y))

print('2. String')
print('p: {}\n'.format(p))

print('3. Representation')
print(repr(p), '\n')

print('4. Sizeof')
print('size of p:', sys.getsizeof(p), '\n')

print('5. Access by name of field')
print('p: {}, {}\n'.format(p.x, p.y))

print('6. Access by index')
print('p: {}, {}\n'.format(p[0], p[1]))

print('7. Iterative unpacking')
x, y = p
print('p: {}, {}\n'.format(x, y))

print('8. Iteration')
print('p: {}\n'.format([v for v in p]))

print('9. Ordered Dict')
print('p: {}\n'.format(p._asdict()))

print('10. Inplace replacement (update?)')
p._update(x=100, y=200)
print('p: {}\n'.format(p))

print('11. Pickle and Unpickle')
pickled = pickle.dumps(p)
unpickled = pickle.loads(pickled)
assert p == unpickled
print('Pickled successfully\n')

print('12. Fields\n')
print('p: {}\n'.format(p._fields))

print('13. Slots')
print('p: {}\n'.format(p.__slots__))

Python 2.7의 출력

1. 필드 값의 변형  
페이지 : 10, 12

2. 문자열  
p : 점 (x = 10, y = 12)

3. 표현  
점 (x = 10, y = 12) 

4. 크기  
p의 크기 : 64 

5. 필드 명으로 접근  
페이지 : 10, 12

6. 인덱스에 의한 접근  
페이지 : 10, 12

7. 반복적 인 압축 풀기  
페이지 : 10, 12

8. 반복  
p : [10, 12]

9. 정렬 된 사전  
p : OrderedDict ([( 'x', 10), ( 'y', 12)])

10. 내부 교체 (업데이트?)  
p : 점 (x = 100, y = 200)

11. 피클 및 언 피클  
성공적으로 절임

12. 필드  
p : ( 'x', 'y')

13. 슬롯  
p : ( 'x', 'y')

Python 3.5와의 유일한 차이점 namedlist은 크기가 더 작아지고 크기가 56이라는 것입니다 (Python 2.7 보고서 64).

내부 교체를 위해 테스트 10을 변경했습니다. namedlist_replace()얕은 복사를 수행하는 방법을하고 있기 때문에 그것은 나에게 완벽한 의미가 namedtuple표준 라이브러리에서이 같은 방식으로 동작합니다. _replace()메서드 의 의미를 변경하면 혼란 스러울 것입니다. 제 생각에는이 _update()방법을 내부 업데이트에 사용해야합니다. 아니면 시험 10의 의도를 이해하지 못했을까요?


중요한 뉘앙스가 있습니다. namedlist목록 인스턴스 의 저장소 값입니다. 문제는이다 cpython의가 list실제로 동적 배열입니다. 의도적으로 목록의 변형을 더 저렴하게 만들기 위해 필요한 것보다 많은 메모리를 할당합니다.
intellimath

1
@intellimath namedlist는 약간 잘못된 이름입니다. 실제로 상속되지 않으며 list기본적으로 __slots__최적화를 사용 합니다. 내가 측정했을 때 메모리 사용량은 다음보다 적습니다 recordclass. Python 2.7의 6 개 필드에 대해 96 바이트 대 104 바이트
GrantJ

@GrantJ 예. 가변 메모리 크기를 가진 유사 객체 recorclass이기 때문에 더 많은 메모리를 사용 tuple합니다.
intellimath

2
익명의 반대표는 누구에게도 도움이되지 않습니다. 대답에 문제가 있습니까? 왜 반대 투표입니까?
Ali

와 관련하여 제공하는 오타에 대한 안전이 마음에 듭니다 types.SimpleNamespace. 불행하게도, pylint는 :-( 그것을 좋아하지 않는다
xverges

23

이 질문에 대한 대답은 '아니오'인 것 같습니다.

아래는 매우 가깝지만 기술적으로 변경할 수는 없습니다. namedtuple()업데이트 된 x 값 으로 새 인스턴스를 만듭니다 .

Point = namedtuple('Point', ['x', 'y'])
p = Point(0, 0)
p = p._replace(x=10) 

반면에 __slots__클래스 인스턴스 속성을 자주 업데이트하는 데 잘 작동 하는 간단한 클래스를 만들 수 있습니다 .

class Point:
    __slots__ = ['x', 'y']
    def __init__(self, x, y):
        self.x = x
        self.y = y

이 답변에 추가하려면 __slots__많은 클래스 인스턴스를 만들 때 메모리 효율적이기 때문에 여기에서 좋은 사용 이라고 생각 합니다. 유일한 단점은 새 클래스 속성을 만들 수 없다는 것입니다.

메모리 효율성을 보여주는 하나의 관련 스레드 ( 사전 대 객체)가 있습니다. 더 효율적이며 그 이유는 무엇입니까?

이 스레드의 답변에 인용 된 내용은 왜 __slots__메모리 효율성이 더 높은지 매우 간결한 설명 입니다 -Python 슬롯


1
가깝지만 투박합니다. + = 할당을하고 싶다고 가정 해 봅시다. p._replace (x = px + 10) 대 px + = 10
Alexander

1
그래, 그것은 정말 새로운 인스턴스를 생성하는 것, 기존의 튜플을 변경하지 않는 것은
kennes

7

다음은 Python 3을위한 좋은 솔루션입니다 . 기본 클래스 __slots__Sequence추상을 사용하는 최소 클래스입니다. 멋진 오류 감지 등을 수행하지 않지만 작동하며 대부분 가변 튜플처럼 작동합니다 (유형 검사 제외).

from collections import Sequence

class NamedMutableSequence(Sequence):
    __slots__ = ()

    def __init__(self, *a, **kw):
        slots = self.__slots__
        for k in slots:
            setattr(self, k, kw.get(k))

        if a:
            for k, v in zip(slots, a):
                setattr(self, k, v)

    def __str__(self):
        clsname = self.__class__.__name__
        values = ', '.join('%s=%r' % (k, getattr(self, k))
                           for k in self.__slots__)
        return '%s(%s)' % (clsname, values)

    __repr__ = __str__

    def __getitem__(self, item):
        return getattr(self, self.__slots__[item])

    def __setitem__(self, item, value):
        return setattr(self, self.__slots__[item], value)

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

class Point(NamedMutableSequence):
    __slots__ = ('x', 'y')

예:

>>> p = Point(0, 0)
>>> p.x = 10
>>> p
Point(x=10, y=0)
>>> p.x *= 10
>>> p
Point(x=100, y=0)

원하는 경우 클래스를 생성하는 메서드도 사용할 수 있습니다 (명시 적 클래스를 사용하는 것이 더 투명하지만).

def namedgroup(name, members):
    if isinstance(members, str):
        members = members.split()
    members = tuple(members)
    return type(name, (NamedMutableSequence,), {'__slots__': members})

예:

>>> Point = namedgroup('Point', ['x', 'y'])
>>> Point(6, 42)
Point(x=6, y=42)

Python 2에서는 약간 조정해야합니다. 에서 상속 Sequence하면 클래스에 a가__dict__ 있고 __slots__작동이 중지됩니다.

Python 2의 솔루션은에서 상속하지 Sequence않고 object. 경우에 isinstance(Point, Sequence) == True요구되는, 당신은 등록해야 NamedMutableSequence하는 기본 클래스 Sequence:

Sequence.register(NamedMutableSequence)

3

동적 유형 생성으로이를 구현해 보겠습니다.

import copy
def namedgroup(typename, fieldnames):

    def init(self, **kwargs): 
        attrs = {k: None for k in self._attrs_}
        for k in kwargs:
            if k in self._attrs_:
                attrs[k] = kwargs[k]
            else:
                raise AttributeError('Invalid Field')
        self.__dict__.update(attrs)

    def getattribute(self, attr):
        if attr.startswith("_") or attr in self._attrs_:
            return object.__getattribute__(self, attr)
        else:
            raise AttributeError('Invalid Field')

    def setattr(self, attr, value):
        if attr in self._attrs_:
            object.__setattr__(self, attr, value)
        else:
            raise AttributeError('Invalid Field')

    def rep(self):
         d = ["{}={}".format(v,self.__dict__[v]) for v in self._attrs_]
         return self._typename_ + '(' + ', '.join(d) + ')'

    def iterate(self):
        for x in self._attrs_:
            yield self.__dict__[x]
        raise StopIteration()

    def setitem(self, *args, **kwargs):
        return self.__dict__.__setitem__(*args, **kwargs)

    def getitem(self, *args, **kwargs):
        return self.__dict__.__getitem__(*args, **kwargs)

    attrs = {"__init__": init,
                "__setattr__": setattr,
                "__getattribute__": getattribute,
                "_attrs_": copy.deepcopy(fieldnames),
                "_typename_": str(typename),
                "__str__": rep,
                "__repr__": rep,
                "__len__": lambda self: len(fieldnames),
                "__iter__": iterate,
                "__setitem__": setitem,
                "__getitem__": getitem,
                }

    return type(typename, (object,), attrs)

작업을 계속하기 전에 속성이 유효한지 확인합니다.

이게 절임이 가능한가요? 다음을 수행하는 경우에만 가능합니다.

>>> import pickle
>>> Point = namedgroup("Point", ["x", "y"])
>>> p = Point(x=100, y=200)
>>> p2 = pickle.loads(pickle.dumps(p))
>>> p2.x
100
>>> p2.y
200
>>> id(p) != id(p2)
True

정의는 네임 스페이스에 있어야하며 pickle이 찾을 수있을만큼 충분히 오래 있어야합니다. 따라서 이것을 패키지에 포함하도록 정의하면 작동합니다.

Point = namedgroup("Point", ["x", "y"])

다음을 수행하거나 정의를 임시로 만들면 Pickle이 실패합니다 (예 : 함수가 종료되면 범위를 벗어남).

some_point = namedgroup("Point", ["x", "y"])

그리고 예, 유형 생성에 나열된 필드의 순서를 유지합니다.


를 사용하여 __iter__메서드 를 추가하면 for k in self._attrs_: yield getattr(self, k)튜플처럼 풀기를 지원합니다.
snapshoe 2015

그것은 아주 쉽게 추가 할 수도 있어요 __len__, __getitem__그리고 __setiem__같은 인덱스를 받고 valus을 지원하는 방법을 p[0]. 이 마지막 부분에서 이것은 (어쨌든 나에게) 가장 완전하고 정답 인 것처럼 보입니다.
snapshoe 2015

__len__그리고 __iter__좋습니다. __getitem__그리고 __setitem__정말로 매핑 할 수 있습니다 self.__dict__.__setitem__self.__dict__.__getitem__
MadMan2064

2

튜플은 정의상 불변입니다.

그러나 점 표기법으로 속성에 액세스 할 수있는 사전 하위 클래스를 만들 수 있습니다.

In [1]: %cpaste
Pasting code; enter '--' alone on the line to stop or use Ctrl-D.
:class AttrDict(dict):
:
:    def __getattr__(self, name):
:        return self[name]
:
:    def __setattr__(self, name, value):
:        self[name] = value
:--

In [2]: test = AttrDict()

In [3]: test.a = 1

In [4]: test.b = True

In [5]: test
Out[5]: {'a': 1, 'b': True}

2

namedtuples와 유사한 동작을 원하지만 변경 가능한 경우 namedlist를 시도 하십시오.

변경 가능하려면 튜플이 될 수 없습니다 .


링크 주셔서 감사합니다. 이것은 지금까지 가장 가까운 것처럼 보이지만 더 자세히 평가해야합니다. Btw, 나는 튜플이 불변한다는 것을 완전히 알고 있으므로 namedtuple 과 같은 솔루션을 찾고 있습니다.
Alexander는

0

성능이 중요하지 않다면 다음과 같은 어리석은 해킹을 사용할 수 있습니다.

from collection import namedtuple

Point = namedtuple('Point', 'x y z')
mutable_z = Point(1,2,[3])

1
이 대답은 잘 설명되어 있지 않습니다. 목록의 변경 가능한 특성을 이해하지 못하면 혼란스러워 보입니다. ---이 예에서는 ... 재 지정하려면 z, 당신은 전화를해야 mutable_z.z.pop(0)다음 mutable_z.z.append(new_value). 이것이 잘못되면 하나 이상의 요소로 끝나고 프로그램이 예기치 않게 작동합니다.
byxor

1
@byxor 또는 당신은 그냥 : mutable_z.z[0] = newValue. 언급했듯이 실제로 해킹입니다.
Srg

아, 그래요, 재 할당하는 더 분명한 방법을 놓친 것이 놀랍습니다.
byxor

나는 그것을 좋아한다, 진짜 해킹.
WebOrCode
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.