파이썬에서 불변의 객체를 만드는 법?


181

필자는 이것이 필요하지 않았지만 파이썬에서 불변의 객체를 만드는 것이 약간 까다로울 수 있다는 사실에 놀랐습니다. 을 (를) 재정의 __setattr__할 수 없습니다 __init__.에 속성을 설정할 수도 없기 때문 입니다. 튜플 서브 클래 싱은 작동하는 트릭입니다.

class Immutable(tuple):

    def __new__(cls, a, b):
        return tuple.__new__(cls, (a, b))

    @property
    def a(self):
        return self[0]

    @property
    def b(self):
        return self[1]

    def __str__(self):
        return "<Immutable {0}, {1}>".format(self.a, self.b)

    def __setattr__(self, *ignored):
        raise NotImplementedError

    def __delattr__(self, *ignored):
        raise NotImplementedError

그러나 당신은 aand를 b통해 self[0]and 변수에 액세스 할 수 있습니다 self[1].

Pure Python에서 가능합니까? 그렇지 않은 경우 C 확장으로 어떻게합니까?

(Python 3에서만 작동하는 답변이 허용됩니다).

최신 정보:

그래서 서브 클래스 튜플 잘하여 데이터를 액세스하는 추가 가능성을 제외하고 작동 순수 파이썬, 그것을 할 수있는 방법이다 [0], [1]등등 그래서이 누락되는 모든 C에 "제대로"그것을 할 HOWTO이다이 질문을 완료하는 나는 단지 geititem또는 setattribute등을 구현하지 않음으로써 매우 간단하다고 생각합니다 . 그러나 나 자신을 대신하는 대신 게으 르기 때문에 그에 대한 현상금을 제공합니다. :)


2
당신의 코드를 통해 속성에 대한 접근 촉진하지 않습니다 .a.b? 그것이 속성이 결국 존재하는 것 같습니다.
Sven Marnach

1
@Sven Marnach : 예. 그러나 [0]과 [1]은 여전히 ​​작동합니다. 왜 그런가요? 나는 그들을 원하지 않습니다. :) 어쩌면 속성을 가진 불변의 객체에 대한 아이디어가 의미가 없습니까? :-)
Lennart Regebro

2
또 다른 참고 사항 NotImplemented은 풍부한 비교를위한 반환 값으로 만 사용됩니다. 에 대한 반환 값 __setatt__()은 어쨌든 의미가 없습니다. 일반적으로 전혀 보지 않기 때문입니다. 같은 코드 immutable.x = 42는 아무 것도하지 않습니다. TypeError대신 인상해야 합니다.
Sven Marnach

1
@ Sven Marnach : 좋아, 나는이 상황에서 NotImplemented를 올릴 수 있다고 생각했기 때문에 놀랐다. 그러나 그것은 이상한 오류를 준다. 그래서 대신 반환했는데 작동하는 것 같습니다. TypeError는 일단 당신이 그것을 사용하는 것을 보았습니다.
Lennart Regebro

1
@Lennart : 당신은 인상 할 수 NotImplementedError있지만, TypeError그것을 수정하려고하면 튜플이 발생합니다.
Sven Marnach

답변:


115

방금 생각한 또 다른 솔루션 : 원래 코드와 동일한 동작을 얻는 가장 간단한 방법은

Immutable = collections.namedtuple("Immutable", ["a", "b"])

이 속성을 통해 액세스 할 수 있는지 문제가 해결되지 않는 [0]등,하지만 적어도 그것은 상당히 짧다과 호환되는 부가적인 장점 제공 picklecopy.

namedtuple내가 설명 것과 유사한 유형의 생성 이 답변 에서 파생하여 예 tuple및 사용 __slots__. Python 2.6 이상에서 사용할 수 있습니다.


7
수작업으로 작성된 아날로그 (Python 2.5에서도 ( 코드에 verbose매개 변수를 사용 namedtuple하는 것은 쉽게 생성됨)) 와 비교하여이 변형의 장점은 단일 인터페이스 / 구현이 namedtuple수십 개의 매우 다른 수작업 인터페이스 / 구현을 선호하는 것입니다 이렇게 거의 같은 일을.
jfs

2
가장 좋은 방법은 "최고의 답변"입니다. Sebastian은 짧은 Cython 구현으로 현상금을받습니다. 건배!
Lennart Regebro

1
불변 객체의 또 다른 특징은 함수를 통해 매개 변수로 객체를 전달할 때 다른 참조가 아닌 값으로 복사된다는 것입니다. 겠습니까이 namedtuple함수를 통해 전달 될 때 값으로 복사 할 수 s는?
hlin117

4
@ hlin117 : 모든 매개 변수는 변경 가능 여부에 관계없이 Python의 객체에 대한 참조로 전달됩니다. 불변 개체의 경우 복사를하는 것이 특히 의미가 없습니다. 개체를 변경할 수 없으므로 원본 개체에 대한 참조를 전달할 수도 있습니다.
Sven Marnach

객체를 외부에서 인스턴스화하는 대신 클래스 내부에서 namedtuple을 사용할 수 있습니까? 나는 파이썬을 처음 접했지만 다른 대답의 이점은 클래스가 세부 정보를 숨기고 선택적 매개 변수와 같은 기능을 가질 수 있다는 것입니다. 이 대답 만 보면 클래스를 사용하는 튜플을 인스턴스화하는 모든 것을 가지고 있어야하는 것처럼 보입니다. 두 답변 모두 감사합니다.
Asaf

78

가장 쉬운 방법은 다음을 사용하는 것입니다 __slots__.

class A(object):
    __slots__ = []

A속성을 설정할 수 없으므로 인스턴스 는 변경할 수 없습니다.

클래스 인스턴스에 데이터가 포함되도록하려면 다음에서 파생 된 것과 결합 할 수 있습니다 tuple.

from operator import itemgetter
class Point(tuple):
    __slots__ = []
    def __new__(cls, x, y):
        return tuple.__new__(cls, (x, y))
    x = property(itemgetter(0))
    y = property(itemgetter(1))

p = Point(2, 3)
p.x
# 2
p.y
# 3

편집 : 인덱싱을 제거하려면 다음을 무시하십시오 __getitem__().

class Point(tuple):
    __slots__ = []
    def __new__(cls, x, y):
        return tuple.__new__(cls, (x, y))
    @property
    def x(self):
        return tuple.__getitem__(self, 0)
    @property
    def y(self):
        return tuple.__getitem__(self, 1)
    def __getitem__(self, item):
        raise TypeError

operator.itemgetter이 경우 속성 Point.__getitem__()대신에 사용할 수 없으므로 속성에 사용할 수 없습니다 tuple.__getitem__(). 또한 이것으로의 사용을 막을 tuple.__getitem__(p, 0)수는 없지만 이것이 어떻게 문제를 구성 해야하는지 상상할 수는 없습니다.

나는 불변의 객체를 만드는 "올바른"방법이 C 확장을 작성한다고 생각하지 않습니다. 파이썬은 일반적으로 성인동의하는 라이브러리 구현 자 및 라이브러리 사용자에 의존하며 실제로 인터페이스를 시행하는 대신 설명서에 인터페이스를 명확하게 명시해야합니다. 이것이 내가 문제 __setattr__()를 부름 으로써 재정의를 피할 가능성을 고려하지 않는 이유 object.__setattr__()입니다. 누군가이 일을하면 위험을 감수해야합니다.


1
을 사용하는 것이 더 좋은 생각하지 않을까요 tuple여기 __slots__ = ()보다는 __slots__ = []? (그냥 명확히)
user225312

1
@ sukhbir : 나는 이것이 전혀 중요하지 않다고 생각합니다. 왜 튜플을 선호합니까?
Sven Marnach

1
@ Sven : 나는 그것이 중요하지 않다는 것에 동의하지만 (우리가 무시할 수있는 속도 부분을 제외하고), 나는 이것을 다음과 같이 생각했다 : __slots__올바르게 변경되지 않을 것인가? 어떤 속성을 설정할 수 있는지 한 번 식별하는 것이 목적입니다. 그래서이되지 않습니다 tuple훨씬 보인다 천연 이러한 경우에 선택을?
user225312

5
그러나 비어 있으면 속성을 __slots__설정할 수 없습니다 . 그리고 내가 가지고 있다면 a와 b 속성은 여전히 ​​변경 가능합니다. __slots__ = ('a', 'b')
Lennart Regebro

그러나 솔루션이 재정의하는 것보다 낫기 __setattr__때문에 내 개선입니다. +1 :)
Lennart Regebro

50

C.에서 "올바로"수행하는 방법

Cython 을 사용하여 Python의 확장 유형을 작성할 수 있습니다 .

cdef class Immutable:
    cdef readonly object a, b
    cdef object __weakref__ # enable weak referencing support

    def __init__(self, a, b):
        self.a, self.b = a, b

Python 2.x와 3 모두에서 작동합니다.

테스트

# compile on-the-fly
import pyximport; pyximport.install() # $ pip install cython
from immutable import Immutable

o = Immutable(1, 2)
assert o.a == 1, str(o.a)
assert o.b == 2

try: o.a = 3
except AttributeError:
    pass
else:
    assert 0, 'attribute must be readonly'

try: o[1]
except TypeError:
    pass
else:
    assert 0, 'indexing must not be supported'

try: o.c = 1
except AttributeError:
    pass
else:
    assert 0, 'no new attributes are allowed'

o = Immutable('a', [])
assert o.a == 'a'
assert o.b == []

o.b.append(3) # attribute may contain mutable object
assert o.b == [3]

try: o.c
except AttributeError:
    pass
else:
    assert 0, 'no c attribute'

o = Immutable(b=3,a=1)
assert o.a == 1 and o.b == 3

try: del o.b
except AttributeError:
    pass
else:
    assert 0, "can't delete attribute"

d = dict(b=3, a=1)
o = Immutable(**d)
assert o.a == d['a'] and o.b == d['b']

o = Immutable(1,b=3)
assert o.a == 1 and o.b == 3

try: object.__setattr__(o, 'a', 1)
except AttributeError:
    pass
else:
    assert 0, 'attributes are readonly'

try: object.__setattr__(o, 'c', 1)
except AttributeError:
    pass
else:
    assert 0, 'no new attributes'

try: Immutable(1,c=3)
except TypeError:
    pass
else:
    assert 0, 'accept only a,b keywords'

for kwd in [dict(a=1), dict(b=2)]:
    try: Immutable(**kwd)
    except TypeError:
        pass
    else:
        assert 0, 'Immutable requires exactly 2 arguments'

인덱싱 지원이 마음에 들지 않으면 @Sven Marnachcollections.namedtuple제안하는 것이 좋습니다 .

Immutable = collections.namedtuple("Immutable", "a b")

@Lennart : namedtuple(또는 더 정확하게 함수에 의해 반환 된 유형의) 인스턴스 namedtuple()는 변경할 수 없습니다. 명확히.
Sven Marnach

@Lennart Regebro : namedtuple모든 테스트를 통과합니다 (인덱싱 지원 제외). 어떤 요구 사항을 놓쳤습니까?
jfs

네, 그렇습니다. 나는 명명 된 튜플 유형을 만들고 인스턴스화 한 다음 인스턴스 대신 유형에 대해 테스트를 수행했습니다. 허. :-)
Lennart Regebro

왜 여기에 약한 참조가 필요한지 물어볼 수 있습니까?
맥시 닉스

1
@McSinyx : 그렇지 않으면 객체를 weakref의 컬렉션에서 사용할 수 없습니다. 파이썬에서 정확히 무엇입니까 __weakref__?
jfs

40

또 다른 아이디어는 생성자에서 완전히 허용하지 __setattr__않고 사용하는 것 object.__setattr__입니다.

class Point(object):
    def __init__(self, x, y):
        object.__setattr__(self, "x", x)
        object.__setattr__(self, "y", y)
    def __setattr__(self, *args):
        raise TypeError
    def __delattr__(self, *args):
        raise TypeError

물론 당신은 사용할 수 object.__setattr__(p, "x", 3)수정 Point인스턴스를 p,하지만 같은 문제에서 원래 구현 겪고있다가 (시도 tuple.__setattr__(i, "x", 42)Immutable경우).

원래 구현에서 동일한 트릭을 적용 할 수 있습니다.를 제거 __getitem__()하고 tuple.__getitem__()속성 함수에서 사용 하십시오.


11
나는 수퍼 클래스를 사용하여 고의로 객체를 수정하는 누군가에 대해 신경 쓰지 않을 것 __setattr__입니다. 요점은 수정해서는 안되며 실수로 수정하는 것을 방지하는 것입니다.
zvone

18

당신은 만들 수 @immutable중 하나는보다 우선 장식을 __setattr__ 하고 을 변경 __slots__빈 목록에 다음 장식 __init__함께 방법을.

편집 : OP가 언급했듯이 __slots__속성을 변경 하면 수정이 아닌 새 속성을 만들 수 없습니다.

Edit2 : 구현은 다음과 같습니다.

Edit3 :를 사용 __slots__하면 객체의 생성이 중지 되므로이 코드가 중단됩니다 __dict__. 대안을 찾고 있습니다.

Edit4 : 글쎄, 그게 다야. 그것은 hackish이지만 운동으로 작동합니다 :-)

class immutable(object):
    def __init__(self, immutable_params):
        self.immutable_params = immutable_params

    def __call__(self, new):
        params = self.immutable_params

        def __set_if_unset__(self, name, value):
            if name in self.__dict__:
                raise Exception("Attribute %s has already been set" % name)

            if not name in params:
                raise Exception("Cannot create atribute %s" % name)

            self.__dict__[name] = value;

        def __new__(cls, *args, **kws):
            cls.__setattr__ = __set_if_unset__

            return super(cls.__class__, cls).__new__(cls, *args, **kws)

        return __new__

class Point(object):
    @immutable(['x', 'y'])
    def __new__(): pass

    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(1, 2) 
p.x = 3 # Exception: Attribute x has already been set
p.z = 4 # Exception: Cannot create atribute z

1
솔루션에서 (class?) 데코레이터 또는 메타 클래스를 만드는 것은 실제로 좋은 생각이지만 문제는 솔루션이 무엇인지입니다. :)
Lennart Regebro

3
object.__setattr__()그것을 깨뜨린 다 stackoverflow.com/questions/4828080/…
jfs

과연. 방금 데코레이터에 대한 연습으로 계속했습니다.
PaoloVictor

13

고정 된 데이터 클래스 사용

Python 3.7 이상 에서는 옵션 과 함께 데이터 클래스 를 사용할 수 있습니다frozen=True 은 원하는 것을 수행하는 매우 파이썬적이고 유지 관리 가능한 방법입니다.

그것은 다음과 같이 보일 것입니다 :

from dataclasses import dataclass

@dataclass(frozen=True)
class Immutable:
    a: Any
    b: Any

데이터 클래스의 필드에 유형 힌트가 필요 하므로 모듈의 Any를 사용했습니다.typing .

Namedtuple을 사용하지 않는 이유

파이썬 3.7 이전에는 명명 된 튜플이 불변 객체로 사용되는 것을 자주 보았습니다. 여러 가지면에서 까다로울 수 있습니다. 그중 하나는 명명 된 __eq__튜플 간의 메소드가 객체의 클래스를 고려하지 않는다는 것입니다. 예를 들면 다음과 같습니다.

from collections import namedtuple

ImmutableTuple = namedtuple("ImmutableTuple", ["a", "b"])
ImmutableTuple2 = namedtuple("ImmutableTuple2", ["a", "c"])

obj1 = ImmutableTuple(a=1, b=2)
obj2 = ImmutableTuple2(a=1, c=2)

obj1 == obj2  # will be True

보시다시피 유형 obj1과 유형 obj2이 다르더라도 필드 이름이 다르더라도obj1 == obj2 더라도 여전히을 제공합니다 True. __eq__사용 된 방법은 튜플의 방법 이기 때문에 위치가 주어진 필드의 값만 비교합니다. 특히 이러한 클래스를 서브 클래 싱하는 경우 큰 오류의 원인이 될 수 있습니다.


10

나는 튜플이나 명명 된 튜플을 사용하는 것을 제외하고는 완전히 가능하다고 생각하지 않습니다. 어쨌든 __setattr__()사용자 를 재정의 하는 경우 항상 object.__setattr__()직접 호출하여 사용자 를 무시할 수 있습니다. 에 의존하는 모든 솔루션__setattr__ 작동하지 않습니다.

다음은 일종의 튜플을 사용하지 않고 얻을 수있는 가장 가까운 것입니다.

class Immutable:
    __slots__ = ['a', 'b']
    def __init__(self, a, b):
        object.__setattr__(self, 'a', a)
        object.__setattr__(self, 'b', b)
    def __setattr__(self, *ignored):
        raise NotImplementedError
    __delattr__ = __setattr__

그러나 충분히 노력하면 깨집니다.

>>> t = Immutable(1, 2)
>>> t.a
1
>>> object.__setattr__(t, 'a', 2)
>>> t.a
2

그러나 Sven의 사용 namedtuple은 진정으로 불변입니다.

최신 정보

C에서 올바르게 수행하는 방법을 묻는 질문이 업데이트되었으므로 Cython에서 올바르게 수행하는 방법에 대한 내 대답은 다음과 같습니다.

먼저 immutable.pyx:

cdef class Immutable:
    cdef object _a, _b

    def __init__(self, a, b):
        self._a = a
        self._b = b

    property a:
        def __get__(self):
            return self._a

    property b:
        def __get__(self):
            return self._b

    def __repr__(self):
        return "<Immutable {0}, {1}>".format(self.a, self.b)

그리고 setup.py그것을 컴파일하기 위해 (명령을 사용하여 setup.py build_ext --inplace:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules = [Extension("immutable", ["immutable.pyx"])]

setup(
  name = 'Immutable object',
  cmdclass = {'build_ext': build_ext},
  ext_modules = ext_modules
)

그런 다음 사용해보십시오.

>>> from immutable import Immutable
>>> p = Immutable(2, 3)
>>> p
<Immutable 2, 3>
>>> p.a = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: attribute 'a' of 'immutable.Immutable' objects is not writable
>>> object.__setattr__(p, 'a', 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: attribute 'a' of 'immutable.Immutable' objects is not writable
>>> p.a, p.b
(2, 3)
>>>      

Cython 코드 덕분에 Cython은 훌륭합니다. 읽기 전용 JF Sebastians 구현은 더 깔끔하고 먼저 도착하므로 현상금을 얻습니다.
Lennart Regebro

5

를 재정의 __setattr__하고 호출자가 __init__다음과 같은 경우 집합을 허용하여 불변 클래스를 만들었습니다 .

import inspect
class Immutable(object):
    def __setattr__(self, name, value):
        if inspect.stack()[2][3] != "__init__":
            raise Exception("Can't mutate an Immutable: self.%s = %r" % (name, value))
        object.__setattr__(self, name, value)

이것은 사람 ___init__이 객체를 변경할 수 있기 때문에 아직 충분하지 않지만 아이디어를 얻습니다.


object.__setattr__()그것을 깨뜨린 다 stackoverflow.com/questions/4828080/…
jfs

3
스택 검사를 사용하여 호출자가 __init__만족스럽지 않은지 확인하십시오 .
gb.

5

훌륭한 다른 답변 외에도 파이썬 3.4 (또는 3.3)에 대한 메소드를 추가하고 싶습니다. 이 답변은이 질문에 대한 몇 가지 이전 답변을 바탕으로합니다.

Python 3.4에서는 setter없이 속성 사용 하여 수정할 수없는 클래스 멤버를 만들 수 있습니다. (이전 버전에서는 세터없이 속성에 할당 할 수있었습니다.)

class A:
    __slots__=['_A__a']
    def __init__(self, aValue):
      self.__a=aValue
    @property
    def a(self):
        return self.__a

다음과 같이 사용할 수 있습니다.

instance=A("constant")
print (instance.a)

인쇄됩니다 "constant"

그러나 전화 instance.a=10하면

AttributeError: can't set attribute

설명 : setter가없는 속성은 python 3.4의 최신 기능입니다 (그리고 3.3이라고 생각합니다). 이러한 속성에 할당하려고하면 오류가 발생합니다. 나는에 membervariables을 제한 슬롯을 사용하여 __A_a인 (__a .

문제 :에 할당 할 _A__a수 있습니다 ( instance._A__a=2). 그러나 개인 변수에 할당하면 자신의 잘못입니다 ...

그러나이 답변 은의 사용을 권장하지 않습니다 __slots__. 속성 생성을 방지하기 위해 다른 방법을 사용하는 것이 좋습니다.


property파이썬 2에서도 사용할 수 있습니다 (질문 자체의 코드를보십시오). 그것은 불변의 객체를 만들지 않고 내 대답에서 테스트를 시도 합니다. 예를 들어 instance.b = 1새로운 b속성을 만듭니다 .
jfs

문제는 실제로 A().b = "foo"새로운 속성 설정을 허용하지 않는 방법을 방지하는 것입니다.
Lennart Regebro

세터가없는 속성은 파이썬 3.4에서 해당 속성에 할당하려고하면 오류가 발생합니다. 이전 버전에서는 setter가 내재적으로 생성되었습니다.
Bernhard

@Lennart : 내 솔루션은 불변 개체에 대한 사용 사례의 하위 집합에 대한 답변이며 이전 답변에 대한 추가입니다. 불변의 객체를 원할 수있는 한 가지 이유는 해시 가능하게 만들 수 있기 때문에 솔루션이 작동 할 수 있습니다. 그러나 당신은 맞습니다, 이것은 불변의 객체가 아닙니다.
Bernhard

@ jf-sebastian : 속성 생성을 방지하기 위해 슬롯을 사용하도록 내 대답을 변경했습니다. 다른 답변과 비교하여 내 답변의 새로운 점은 기존 속성 변경을 피하기 위해 python3.4의 속성을 사용한다는 것입니다. 이전 답변에서도 동일하게 적용되지만 속성 동작의 변경으로 인해 코드가 짧습니다.
Bernhard

5

우아한 해결책 은 다음과 같습니다 .

class Immutable(object):
    def __setattr__(self, key, value):
        if not hasattr(self, key):
            super().__setattr__(key, value)
        else:
            raise RuntimeError("Can't modify immutable object's attribute: {}".format(key))

이 클래스에서 상속하여 생성자에서 필드를 초기화하면 모든 설정이 완료됩니다.


1
그러나이 논리를 사용하면 객체에 새로운 속성을 할당 할 수 있습니다.
Jan

3

동작이있는 객체에 관심이 있다면 namedtuple이 거의 해결책입니다.

namedtuple 문서 의 맨 아래에 설명 된 것처럼 namedtuple 에서 자신의 클래스를 파생시킬 수 있습니다. 그런 다음 원하는 동작을 추가 할 수 있습니다.

예를 들어 ( 문서 에서 직접 가져온 코드 ) :

class Point(namedtuple('Point', 'x y')):
    __slots__ = ()
    @property
    def hypot(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5
    def __str__(self):
        return 'Point: x=%6.3f  y=%6.3f  hypot=%6.3f' % (self.x, self.y, self.hypot)

for p in Point(3, 4), Point(14, 5/7):
    print(p)

결과는 다음과 같습니다.

Point: x= 3.000  y= 4.000  hypot= 5.000
Point: x=14.000  y= 0.714  hypot=14.018

이 접근법은 Python 3과 Python 2.7 모두에서 작동합니다 (IronPython에서도 테스트 됨).
유일한 단점은 상속 트리가 약간 이상하다는 것입니다. 그러나 이것은 당신이 보통 가지고 노는 것이 아닙니다.


1
Python 3.6+는 다음을 사용하여 직접 지원합니다class Point(typing.NamedTuple):
Elazar

3

다음 Immutable클래스 에서 상속 된 클래스는 __init__메서드 실행이 완료된 후 인스턴스와 마찬가지로 변경할 수 없습니다 . 순수한 파이썬이기 때문에 다른 사람들이 지적 밖으로을 가지고, 기본에서 돌연변이 특별한 방법을 사용하여 누군가를 중지 아무것도 objecttype , 그러나 이것은 실수로 클래스 / 인스턴스를 돌연변이에서 정지 누군가에 충분하다.

메타 클래스로 클래스 생성 프로세스를 가로채는 방식으로 작동합니다.

"""Subclasses of class Immutable are immutable after their __init__ has run, in
the sense that all special methods with mutation semantics (in-place operators,
setattr, etc.) are forbidden.

"""  

# Enumerate the mutating special methods
mutation_methods = set()
# Arithmetic methods with in-place operations
iarithmetic = '''add sub mul div mod divmod pow neg pos abs bool invert lshift
                 rshift and xor or floordiv truediv matmul'''.split()
for op in iarithmetic:
    mutation_methods.add('__i%s__' % op)
# Operations on instance components (attributes, items, slices)
for verb in ['set', 'del']:
    for component in '''attr item slice'''.split():
        mutation_methods.add('__%s%s__' % (verb, component))
# Operations on properties
mutation_methods.update(['__set__', '__delete__'])


def checked_call(_self, name, method, *args, **kwargs):
    """Calls special method method(*args, **kw) on self if mutable."""
    self = args[0] if isinstance(_self, object) else _self
    if not getattr(self, '__mutable__', True):
        # self told us it's immutable, so raise an error
        cname= (self if isinstance(self, type) else self.__class__).__name__
        raise TypeError('%s is immutable, %s disallowed' % (cname, name))
    return method(*args, **kwargs)


def method_wrapper(_self, name):
    "Wrap a special method to check for mutability."
    method = getattr(_self, name)
    def wrapper(*args, **kwargs):
        return checked_call(_self, name, method, *args, **kwargs)
    wrapper.__name__ = name
    wrapper.__doc__ = method.__doc__
    return wrapper


def wrap_mutating_methods(_self):
    "Place the wrapper methods on mutative special methods of _self"
    for name in mutation_methods:
        if hasattr(_self, name):
            method = method_wrapper(_self, name)
            type.__setattr__(_self, name, method)


def set_mutability(self, ismutable):
    "Set __mutable__ by using the unprotected __setattr__"
    b = _MetaImmutable if isinstance(self, type) else Immutable
    super(b, self).__setattr__('__mutable__', ismutable)


class _MetaImmutable(type):

    '''The metaclass of Immutable. Wraps __init__ methods via __call__.'''

    def __init__(cls, *args, **kwargs):
        # Make class mutable for wrapping special methods
        set_mutability(cls, True)
        wrap_mutating_methods(cls)
        # Disable mutability
        set_mutability(cls, False)

    def __call__(cls, *args, **kwargs):
        '''Make an immutable instance of cls'''
        self = cls.__new__(cls)
        # Make the instance mutable for initialization
        set_mutability(self, True)
        # Execute cls's custom initialization on this instance
        self.__init__(*args, **kwargs)
        # Disable mutability
        set_mutability(self, False)
        return self

    # Given a class T(metaclass=_MetaImmutable), mutative special methods which
    # already exist on _MetaImmutable (a basic type) cannot be over-ridden
    # programmatically during _MetaImmutable's instantiation of T, because the
    # first place python looks for a method on an object is on the object's
    # __class__, and T.__class__ is _MetaImmutable. The two extant special
    # methods on a basic type are __setattr__ and __delattr__, so those have to
    # be explicitly overridden here.

    def __setattr__(cls, name, value):
        checked_call(cls, '__setattr__', type.__setattr__, cls, name, value)

    def __delattr__(cls, name, value):
        checked_call(cls, '__delattr__', type.__delattr__, cls, name, value)


class Immutable(object):

    """Inherit from this class to make an immutable object.

    __init__ methods of subclasses are executed by _MetaImmutable.__call__,
    which enables mutability for the duration.

    """

    __metaclass__ = _MetaImmutable


class T(int, Immutable):  # Checks it works with multiple inheritance, too.

    "Class for testing immutability semantics"

    def __init__(self, b):
        self.b = b

    @classmethod
    def class_mutation(cls):
        cls.a = 5

    def instance_mutation(self):
        self.c = 1

    def __iadd__(self, o):
        pass

    def not_so_special_mutation(self):
        self +=1

def immutabilityTest(f, name):
    "Call f, which should try to mutate class T or T instance."
    try:
        f()
    except TypeError, e:
        assert 'T is immutable, %s disallowed' % name in e.args
    else:
        raise RuntimeError('Immutability failed!')

immutabilityTest(T.class_mutation, '__setattr__')
immutabilityTest(T(6).instance_mutation, '__setattr__')
immutabilityTest(T(6).not_so_special_mutation, '__iadd__')

2

나는 조금 전에 이것을 필요로했고 그것을 위해 파이썬 패키지를 만들기로 결정했다. 초기 버전은 현재 PyPI에 있습니다.

$ pip install immutable

쓰다:

>>> from immutable import ImmutableFactory
>>> MyImmutable = ImmitableFactory.create(prop1=1, prop2=2, prop3=3)
>>> MyImmutable.prop1
1

전체 문서는 다음과 같습니다. https://github.com/theengineear/immutable

그것이 도움이되기를 바랍니다. 그것은 논의 된 것처럼 명명 된 튜플을 감싸지 만 인스턴스화를 훨씬 간단하게 만듭니다.


2

이 방법은 object.__setattr__작동을 멈추지 않지만 여전히 유용하다는 것을 알았습니다.

class A(object):

    def __new__(cls, children, *args, **kwargs):
        self = super(A, cls).__new__(cls)
        self._frozen = False  # allow mutation from here to end of  __init__
        # other stuff you need to do in __new__ goes here
        return self

    def __init__(self, *args, **kwargs):
        super(A, self).__init__()
        self._frozen = True  # prevent future mutation

    def __setattr__(self, name, value):
        # need to special case setting _frozen.
        if name != '_frozen' and self._frozen:
            raise TypeError('Instances are immutable.')
        else:
            super(A, self).__setattr__(name, value)

    def __delattr__(self, name):
        if self._frozen:
            raise TypeError('Instances are immutable.')
        else:
            super(A, self).__delattr__(name)

__setitem__사용 사례에 따라 더 많은 것들 (예 :)을 재정의해야 할 수도 있습니다 .


이것을보기 전에 비슷한 것을 생각해 냈지만에 getattr대한 기본값을 제공 할 수있었습니다 frozen. 그것은 조금 단순화했습니다. stackoverflow.com/a/22545808/5987
Mark Ransom

나는이 접근법이 가장 좋지만 __new__재정의 가 필요하지 않습니다 . 내부 __setattr__는 조건부로 대체if name != '_frozen' and getattr(self, "_frozen", False)
Pete Cacioppi

또한 시공시 수업을 중단 할 필요가 없습니다. freeze()기능 을 제공하면 언제든지 고정 할 수 있습니다 . 그런 다음 개체는 "한 번 고정"됩니다. 마지막으로, object.__setattr__"우리는 모두 성인입니다"때문에 걱정하는 것은 어리석은 일입니다.
Pete Cacioppi

2

Python 3.7부터 클래스 에서 @dataclass데코레이터 를 사용할 수 있으며 구조체처럼 변경할 수 없습니다! 그러나 __hash__()클래스에 메소드를 추가하거나 추가하지 않을 수 있습니다 . 인용문:

hash ()는 내장 hash ()에서 사용되며 사전 및 세트와 같은 해시 콜렉션에 오브젝트가 추가 될 때 사용됩니다. 해시 ()가 있으면 클래스의 인스턴스는 변경할 수 없습니다. 변경 가능성은 프로그래머의 의도, eq () 의 존재와 동작 , dataclass () 데코레이터의 eq 및 고정 플래그 값에 따라 달라지는 복잡한 속성입니다 .

기본적으로 dataclass () 는 안전하지 않은 경우 암시 적으로 해시 () 메서드를 추가하지 않습니다 . 기존의 명시 적으로 정의 된 해시 () 메소드를 추가하거나 변경하지도 않습니다 . 클래스 속성 hash = None 설정은 해시 () 문서에 설명 된대로 Python에 특정한 의미를 갖습니다 .

경우 해시 () 정의 명시가 아니거나 없음으로 설정되어있는 경우, 다음 dataclass () 암시 추가 할 수 있습니다 해시 () 메소드를. 권장되지는 않지만, unsafe_hash = True 를 사용하여 dataclass ()가 해시 () 메소드 를 작성하도록 할 수 있습니다 . 클래스가 논리적으로 변경 불가능하지만 변경 될 수있는 경우에 해당 될 수 있습니다. 이것은 특수한 사용 사례이므로 신중하게 고려해야합니다.

다음은 위의 문서에서 가져온 예제입니다.

@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

1
당신은 사용할 필요가 frozen즉, @dataclass(frozen=True)하지만 기본적으로 블록의 사용 __setattr____delattr__여기에 다른 답변의 대부분처럼. 다른 옵션의 데이터 클래스와 호환되는 방식으로 수행합니다.
CS

2

setattr 을 재정의 하고 init 를 사용하여 변수를 설정할 수 있습니다. 수퍼 클래스 setattr을 사용 합니다. 여기 코드가 있습니다.

불변의 클래스 :
    __slots__ = ( 'a', 'b')
    데프 __init __ (자체, a, b) :
        super () .__ setattr __ ( 'a', a)
        super () .__ setattr __ ( 'b', b)

    데프 __str__ (자체) :
        "".format (self.a, self.b) 반환

    데프 __setattr __ (자기, * 무시) :
        NotImplementedError 발생

    데프 __delattr __ (자기, * 무시) :
        NotImplementedError 발생

아니면 그냥 pass대신raise NotImplementedError
jonathan.scholbach

이 경우 __setattr__ 및 __delattr__에서 "통과"를 수행하는 것은 전혀 좋은 생각이 아닙니다. 간단한 이유는 누군가가 필드 / 속성에 값을 할당하면 자연스럽게 필드가 변경 될 것으로 기대하기 때문입니다. "최소한의 놀람"경로를 따라 가고 싶다면 (오류 발생) 그러나 NotImplementedError가 올바로 발생하는지 확실하지 않습니다. "현장 / 재산은 변하지 않습니다." 오류 ... 맞춤 예외가 발생해야한다고 생각합니다.
darlove

1

타사 attr모듈은 이 기능을 제공 합니다 .

편집 : python 3.7은이 아이디어를 stdlib에 채택했습니다 @dataclass.

$ pip install attrs
$ python
>>> @attr.s(frozen=True)
... class C(object):
...     x = attr.ib()
>>> i = C(1)
>>> i.x = 2
Traceback (most recent call last):
   ...
attr.exceptions.FrozenInstanceError: can't set attribute

attr__setattr__설명서에 따르면 재정 의하여 고정 클래스를 구현 하고 각 인스턴스화 시간에 약간의 성능 영향을 미칩니다.

클래스를 데이터 유형으로 사용하는 습관이 있다면 attr, 상용구를 처리 할 때 특히 유용 할 수 있습니다 (그러나 마술은하지 않습니다). 특히, repr, init, hash 및 모든 비교 함수를 포함하여 9 개의 dunder (__ X__) 메소드를 작성합니다 (해당 메소드를 끄지 않는 한).

attr에 대한 도우미__slots__ 도 제공합니다 .


1

그래서 나는 각각 파이썬 3을 작성하고 있습니다 :

I) 데이터 클래스 데코레이터를 사용하여 frozen = True로 설정하십시오. 파이썬에서 불변 객체를 만들 수 있습니다.

이를 위해서는 데이터 클래스 lib에서 데이터 클래스를 가져와야하고 frozen = True를 설정해야합니다.

전의.

데이터 클래스에서 가져 오기 데이터 클래스

@dataclass(frozen=True)
class Location:
    name: str
    longitude: float = 0.0
    latitude: float = 0.0

o / p :

l = Location ( "Delhi", 112.345, 234.788) l.name 'Delhi'l.longitude 112.345 l.latitude 234.788 l.name = "Kolkata"dataclasses.FrozenInstanceError : 'name'필드에 할당 할 수 없습니다

출처 : https://realpython.com/python-data-classes/


0

다른 방법은 인스턴스를 변경할 수없는 래퍼를 만드는 것입니다.

class Immutable(object):

    def __init__(self, wrapped):
        super(Immutable, self).__init__()
        object.__setattr__(self, '_wrapped', wrapped)

    def __getattribute__(self, item):
        return object.__getattribute__(self, '_wrapped').__getattribute__(item)

    def __setattr__(self, key, value):
        raise ImmutableError('Object {0} is immutable.'.format(self._wrapped))

    __delattr__ = __setattr__

    def __iter__(self):
        return object.__getattribute__(self, '_wrapped').__iter__()

    def next(self):
        return object.__getattribute__(self, '_wrapped').next()

    def __getitem__(self, item):
        return object.__getattribute__(self, '_wrapped').__getitem__(item)

immutable_instance = Immutable(my_instance)

이는 함수 호출의 기본 인수와 같이 일부 인스턴스 만 변경할 수없는 상황에서 유용합니다.

다음과 같은 불변 공장에서 사용할 수도 있습니다 :

@classmethod
def immutable_factory(cls, *args, **kwargs):
    return Immutable(cls.__init__(*args, **kwargs))

또한 object.__setattr__파이썬의 동적 특성으로 인해 다른 트릭에 빠지지 않지만 보호합니다 .


0

Alex와 같은 아이디어를 사용했습니다 : 메타 클래스와 "초기 마커", 덮어 쓰기 __setattr__과 함께 :

>>> from abc import ABCMeta
>>> _INIT_MARKER = '_@_in_init_@_'
>>> class _ImmutableMeta(ABCMeta):
... 
...     """Meta class to construct Immutable."""
... 
...     def __call__(cls, *args, **kwds):
...         obj = cls.__new__(cls, *args, **kwds)
...         object.__setattr__(obj, _INIT_MARKER, True)
...         cls.__init__(obj, *args, **kwds)
...         object.__delattr__(obj, _INIT_MARKER)
...         return obj
...
>>> def _setattr(self, name, value):
...     if hasattr(self, _INIT_MARKER):
...         object.__setattr__(self, name, value)
...     else:
...         raise AttributeError("Instance of '%s' is immutable."
...                              % self.__class__.__name__)
...
>>> def _delattr(self, name):
...     raise AttributeError("Instance of '%s' is immutable."
...                          % self.__class__.__name__)
...
>>> _im_dict = {
...     '__doc__': "Mix-in class for immutable objects.",
...     '__copy__': lambda self: self,   # self is immutable, so just return it
...     '__setattr__': _setattr,
...     '__delattr__': _delattr}
...
>>> Immutable = _ImmutableMeta('Immutable', (), _im_dict)

참고 : 파이썬 2.x와 3.x에서 모두 작동하도록 메타 클래스를 직접 호출하고 있습니다.

>>> class T1(Immutable):
... 
...     def __init__(self, x=1, y=2):
...         self.x = x
...         self.y = y
...
>>> t1 = T1(y=8)
>>> t1.x, t1.y
(1, 8)
>>> t1.x = 7
AttributeError: Instance of 'T1' is immutable.

슬롯에서도 작동합니다 ... :

>>> class T2(Immutable):
... 
...     __slots__ = 's1', 's2'
... 
...     def __init__(self, s1, s2):
...         self.s1 = s1
...         self.s2 = s2
...
>>> t2 = T2('abc', 'xyz')
>>> t2.s1, t2.s2
('abc', 'xyz')
>>> t2.s1 += 'd'
AttributeError: Instance of 'T2' is immutable.

... 그리고 다중 상속 :

>>> class T3(T1, T2):
... 
...     def __init__(self, x, y, s1, s2):
...         T1.__init__(self, x, y)
...         T2.__init__(self, s1, s2)
...
>>> t3 = T3(12, 4, 'a', 'b')
>>> t3.x, t3.y, t3.s1, t3.s2
(12, 4, 'a', 'b')
>>> t3.y -= 3
AttributeError: Instance of 'T3' is immutable.

그러나 변경 가능한 속성은 변경 가능한 상태로 유지됩니다.

>>> t3 = T3(12, [4, 7], 'a', 'b')
>>> t3.y.append(5)
>>> t3.y
[4, 7, 5]

0

여기에 실제로 포함되지 않은 한 가지는 전체 불변성입니다. 부모 개체뿐만 아니라 모든 자식도 마찬가지입니다. 예를 들어 튜플 / 냉동 세트는 변경할 수 없지만 그 일부가 아닐 수도 있습니다. 다음은 불변성을 강제하는 적절한 작업을 수행하는 작은 (불완전한) 버전입니다.

# Initialize lists
a = [1,2,3]
b = [4,5,6]
c = [7,8,9]

l = [a,b]

# We can reassign in a list 
l[0] = c

# But not a tuple
t = (a,b)
#t[0] = c -> Throws exception
# But elements can be modified
t[0][1] = 4
t
([1, 4, 3], [4, 5, 6])
# Fix it back
t[0][1] = 2

li = ImmutableObject(l)
li
[[1, 2, 3], [4, 5, 6]]
# Can't assign
#li[0] = c will fail
# Can reference
li[0]
[1, 2, 3]
# But immutability conferred on returned object too
#li[0][1] = 4 will throw an exception

# Full solution should wrap all the comparison e.g. decorators.
# Also, you'd usually want to add a hash function, i didn't put
# an interface for that.

class ImmutableObject(object):
    def __init__(self, inobj):
        self._inited = False
        self._inobj = inobj
        self._inited = True

    def __repr__(self):
        return self._inobj.__repr__()

    def __str__(self):
        return self._inobj.__str__()

    def __getitem__(self, key):
        return ImmutableObject(self._inobj.__getitem__(key))

    def __iter__(self):
        return self._inobj.__iter__()

    def __setitem__(self, key, value):
        raise AttributeError, 'Object is read-only'

    def __getattr__(self, key):
        x = getattr(self._inobj, key)
        if callable(x):
              return x
        else:
              return ImmutableObject(x)

    def __hash__(self):
        return self._inobj.__hash__()

    def __eq__(self, second):
        return self._inobj.__eq__(second)

    def __setattr__(self, attr, value):
        if attr not in  ['_inobj', '_inited'] and self._inited == True:
            raise AttributeError, 'Object is read-only'
        object.__setattr__(self, attr, value)

0

최종 init 명령문에서 setAttr을 대체 할 수 있습니다. 구성 할 수는 있지만 변경할 수는 없습니다. 분명히 usint 객체로 재정의 할 수 있습니다. setAttr 그러나 실제로 대부분의 언어에는 어떤 형태의 반성이 있으므로 불변성은 항상 누출되는 추상화입니다. 불변성은 클라이언트가 실수로 객체의 계약을 위반하는 것을 방지하는 것입니다. 나는 사용한다:

===============================

제공된 원래 솔루션이 잘못되었습니다. 여기 에서 솔루션을 사용한 의견을 기반으로 업데이트되었습니다.

원래 솔루션은 흥미로운 방식으로 잘못되었으므로 하단에 포함되어 있습니다.

================================

class ImmutablePair(object):

    __initialised = False # a class level variable that should always stay false.
    def __init__(self, a, b):
        try :
            self.a = a
            self.b = b
        finally:
            self.__initialised = True #an instance level variable

    def __setattr__(self, key, value):
        if self.__initialised:
            self._raise_error()
        else :
            super(ImmutablePair, self).__setattr__(key, value)

    def _raise_error(self, *args, **kw):
        raise NotImplementedError("Attempted To Modify Immutable Object")

if __name__ == "__main__":

    immutable_object = ImmutablePair(1,2)

    print immutable_object.a
    print immutable_object.b

    try :
        immutable_object.a = 3
    except Exception as e:
        print e

    print immutable_object.a
    print immutable_object.b

출력 :

1
2
Attempted To Modify Immutable Object
1
2

========================================

원래 구현 :

주석에서 클래스 setattr 메소드를 오버라이드 할 때 둘 이상의 객체가 생성되는 것을 막기 때문에 실제로 작동하지 않는다는 점이 올바르게 지적되었습니다. 즉, 두 번째는 self로 작성할 수 없습니다. 두 번째 초기화에서 실패합니다.

class ImmutablePair(object):

    def __init__(self, a, b):
        self.a = a
        self.b = b
        ImmutablePair.__setattr__ = self._raise_error

    def _raise_error(self, *args, **kw):
        raise NotImplementedError("Attempted To Modify Immutable Object")

1
작동하지 않습니다 : 클래스 의 메서드 재정의 하므로 두 번째 인스턴스를 만들려고하면 즉시 NotImplementedError가 발생합니다.
slinkp

1
이 방법을 사용하려면 런타임시 특수 방법을 재정의하기가 어렵습니다 . 이에 대한 몇 가지 해결 방법 은 stackoverflow.com/a/16426447/137635 를 참조하십시오 .
slinkp

0

아래의 기본 솔루션은 다음 시나리오를 해결합니다.

  • __init__() 평소처럼 속성에 액세스하여 쓸 수 있습니다.
  • 속성 변경에 대해서만 OBJECT가 고정 된 후 :

아이디어는 __setattr__객체 고정 상태가 변경 될 때마다 메서드 를 재정의 하고 구현을 대체하는 것입니다.

따라서 우리 _freeze는 두 구현을 저장하고 요청시 그들 사이를 전환하는 메소드 ( ) 가 필요합니다 .

이 메커니즘은 사용자 클래스 내부에서 구현되거나 Freezer아래와 같이 특수 클래스 에서 상속 될 수 있습니다.

class Freezer:
    def _freeze(self, do_freeze=True):
        def raise_sa(*args):            
            raise AttributeError("Attributes are frozen and can not be changed!")
        super().__setattr__('_active_setattr', (super().__setattr__, raise_sa)[do_freeze])

    def __setattr__(self, key, value):        
        return self._active_setattr(key, value)

class A(Freezer):    
    def __init__(self):
        self._freeze(False)
        self.x = 10
        self._freeze()

0

그냥 dict

기능적인 방식으로 작업을 수행하는 오픈 소스 라이브러리가 있으므로 변경 불가능한 객체에서 데이터를 이동하는 것이 도움이됩니다. 그러나 클라이언트가 상호 작용하기 위해 데이터 객체를 변환하지 않아도됩니다. 그래서, 나는 이것을 생각해 냈습니다- 그것은 당신에게 불변 + 일부 도우미 메소드 와 같은 dict을 제공합니다 .

에 신용 스벤 Marnach 자신의 답변을 등록 갱신을 제한 및 삭제의 기본 구현.

import json 
# ^^ optional - If you don't care if it prints like a dict
# then rip this and __str__ and __repr__ out

class Immutable(object):

    def __init__(self, **kwargs):
        """Sets all values once given
        whatever is passed in kwargs
        """
        for k,v in kwargs.items():
            object.__setattr__(self, k, v)

    def __setattr__(self, *args):
        """Disables setting attributes via
        item.prop = val or item['prop'] = val
        """
        raise TypeError('Immutable objects cannot have properties set after init')

    def __delattr__(self, *args):
        """Disables deleting properties"""
        raise TypeError('Immutable objects cannot have properties deleted')

    def __getitem__(self, item):
        """Allows for dict like access of properties
        val = item['prop']
        """
        return self.__dict__[item]

    def __repr__(self):
        """Print to repl in a dict like fashion"""
        return self.pprint()

    def __str__(self):
        """Convert to a str in a dict like fashion"""
        return self.pprint()

    def __eq__(self, other):
        """Supports equality operator
        immutable({'a': 2}) == immutable({'a': 2})"""
        if other is None:
            return False
        return self.dict() == other.dict()

    def keys(self):
        """Paired with __getitem__ supports **unpacking
        new = { **item, **other }
        """
        return self.__dict__.keys()

    def get(self, *args, **kwargs):
        """Allows for dict like property access
        item.get('prop')
        """
        return self.__dict__.get(*args, **kwargs)

    def pprint(self):
        """Helper method used for printing that
        formats in a dict like way
        """
        return json.dumps(self,
            default=lambda o: o.__dict__,
            sort_keys=True,
            indent=4)

    def dict(self):
        """Helper method for getting the raw dict value
        of the immutable object"""
        return self.__dict__

헬퍼 메소드

def update(obj, **kwargs):
    """Returns a new instance of the given object with
    all key/val in kwargs set on it
    """
    return immutable({
        **obj,
        **kwargs
    })

def immutable(obj):
    return Immutable(**obj)

obj = immutable({
    'alpha': 1,
    'beta': 2,
    'dalet': 4
})

obj.alpha # 1
obj['alpha'] # 1
obj.get('beta') # 2

del obj['alpha'] # TypeError
obj.alpha = 2 # TypeError

new_obj = update(obj, alpha=10)

new_obj is not obj # True
new_obj.get('alpha') == 10 # True
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.