__init__ 외부에서 새 속성 생성 방지


82

일단으로 초기화되고 __init__새 속성을 허용하지 않지만 기존 속성의 수정을 허용 하는 클래스 (Python에서)를 만들 수 있기를 원합니다 . 예를 들어 다음 __setattr__과 같은 방법을 사용하는 등 몇 가지 해킹 방법이 있습니다

def __setattr__(self, attribute, value):
    if not attribute in self.__dict__:
        print "Cannot set %s" % attribute
    else:
        self.__dict__[attribute] = value

그런 다음 __dict__내부 __init__에서 직접 편집 하지만 이것을 수행하는 '적절한'방법이 있는지 궁금합니다.


1
katrielalex는 좋은 점을 제공합니다. 그것에 대해 해키가 없습니다. 사용을 피할 수는 __setattr__있지만 아마도 해키가 될 것입니다.
aaronasterling 2010 년

왜 이것이 해키인지 모르겠어요? 제가 생각 해낼 수있는 최고의 솔루션이며 다른 일부가 제안한 것보다 훨씬 간결합니다.
Chris B

답변:


81

__dict__직접 사용 하지는 않지만 인스턴스를 명시 적으로 "고정"하는 함수를 추가 할 수 있습니다.

class FrozenClass(object):
    __isfrozen = False
    def __setattr__(self, key, value):
        if self.__isfrozen and not hasattr(self, key):
            raise TypeError( "%r is a frozen class" % self )
        object.__setattr__(self, key, value)

    def _freeze(self):
        self.__isfrozen = True

class Test(FrozenClass):
    def __init__(self):
        self.x = 42#
        self.y = 2**3

        self._freeze() # no new attributes after this point.

a,b = Test(), Test()
a.x = 10
b.z = 10 # fails

아주 멋지다! 나는 그 코드를 잡고 그것을 사용하기 시작할 것이라고 생각한다. (흠, 나는 그것이 장식으로 수행 할 수 있는지 궁금하거나 좋은 생각이 될하지 않는다면 ...)
베로니카

5
후기 의견 : 속성을 속성으로 변경하기 전까지 getter가 NotImplementedError를 발생시키는 동안이 레시피를 성공적으로 사용했습니다. 이것이 hasattractuall을 호출 getattr하고 결과를 제거하고 오류가 발생하면 False를 반환 하기 때문이라는 사실을 알아내는 데 오랜 시간이 걸렸습니다 . 이 블로그를 참조하십시오 . 바꾸어 해결 방법을 발견 not hasattr(self, key)하여 key not in dir(self). 느릴 수 있지만 문제가 해결되었습니다.
Bas Swinckels

31

누군가 데코레이터로 작업하는 데 관심이있는 경우 다음과 같은 작업 솔루션이 있습니다.

from functools import wraps

def froze_it(cls):
    cls.__frozen = False

    def frozensetattr(self, key, value):
        if self.__frozen and not hasattr(self, key):
            print("Class {} is frozen. Cannot set {} = {}"
                  .format(cls.__name__, key, value))
        else:
            object.__setattr__(self, key, value)

    def init_decorator(func):
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            func(self, *args, **kwargs)
            self.__frozen = True
        return wrapper

    cls.__setattr__ = frozensetattr
    cls.__init__ = init_decorator(cls.__init__)

    return cls

사용하기 매우 간단합니다.

@froze_it 
class Foo(object):
    def __init__(self):
        self.bar = 10

foo = Foo()
foo.bar = 42
foo.foobar = "no way"

결과:

>>> Class Foo is frozen. Cannot set foobar = no way

데코레이터 버전의 경우 +1. 그것이 내가 더 큰 프로젝트에 사용하는 것입니다. 더 큰 스크립트에서 이것은 과잉입니다 (아마도 표준 라이브러리에 있다면 ...). 현재로서는 "IDE 스타일 경고"만 있습니다.
Tomasz Gandor 2016

2
이 솔루션은 유산과 어떻게 작동합니까? 예를 들어 Foo의 자식 클래스가있는 경우이 자식은 기본적으로 고정 된 클래스입니까?
mrgiesel

이 데코레이터를위한 pypi 패키지가 있습니까?
winni2k

상속 된 클래스에서 작동하도록 데코레이터를 어떻게 향상시킬 수 있습니까?
이반 Nechipayko

30

슬롯은 이동 방법입니다.

비단뱀적인 방법은 __setter__. 문제를 해결할 수는 있지만 성능이 향상되지는 않습니다. 객체의 속성은 사전 " __dict__"에 저장됩니다. 이것이 바로 지금까지 만든 클래스의 객체에 속성을 동적으로 추가 할 수있는 이유입니다. 속성 저장에 딕셔너리를 사용하는 것은 매우 편리하지만 인스턴스 변수가 적은 객체를위한 공간 낭비를 의미 할 수 있습니다.

슬롯 은이 공간 소비 문제를 해결하는 좋은 방법입니다. 객체에 속성을 동적으로 추가 할 수있는 동적 사전을 갖는 대신 슬롯은 인스턴스 생성 후 추가를 금지하는 정적 구조를 제공합니다.

클래스를 디자인 할 때 슬롯을 사용하여 속성의 동적 생성을 방지 할 수 있습니다. 슬롯을 정의하려면 이름으로 목록을 정의해야합니다 __slots__. 목록에는 사용하려는 모든 속성이 포함되어야합니다. 슬롯 목록에 "val"속성의 이름 만 포함 된 다음 클래스에서이를 설명합니다.

class S(object):

    __slots__ = ['val']

    def __init__(self, v):
        self.val = v


x = S(42)
print(x.val)

x.new = "not possible"

=> "new"속성 생성 실패 :

42 
Traceback (most recent call last):
  File "slots_ex.py", line 12, in <module>
    x.new = "not possible"
AttributeError: 'S' object has no attribute 'new'

주의 :

  1. Python 3.3 이후로 공간 소비를 최적화하는 이점은 더 이상 인상적이지 않습니다. Python 3.3에서는 키 공유 사전이 객체 저장에 사용됩니다. 인스턴스의 속성은 내부 저장소의 일부, 즉 키와 해당 해시를 저장하는 부분을 공유 할 수 있습니다. 이것은 비 내장 유형의 많은 인스턴스를 생성하는 프로그램의 메모리 소비를 줄이는 데 도움이됩니다. 그러나 여전히 동적으로 생성 된 속성을 피하는 방법입니다.
  1. 슬롯을 사용하면 자체 비용이 발생합니다. 직렬화 (예 : 피클)가 중단됩니다. 또한 다중 상속을 중단합니다. 클래스는 슬롯을 정의하거나 C 코드 (예 : 목록, 튜플 또는 int)에 정의 된 인스턴스 레이아웃이있는 둘 이상의 클래스에서 상속 할 수 없습니다.

20

사실, 당신이 원하지 않는 __setattr__당신이 원하는, __slots__. __slots__ = ('foo', 'bar', 'baz')클래스 본문에 추가 하면 Python은 모든 인스턴스에 foo, bar 및 baz 만 있는지 확인합니다. 그러나 설명서 목록의주의 사항을 읽으십시오!


12
__slots__작업을 사용 하지만 직렬화 (예 : 피클)가 중단됩니다. 어쨌거나 메모리 오버 헤드를 줄이는 것보다 속성 생성을 제어하기 위해 슬롯을 사용하는 것은 일반적으로 나쁜 생각입니다 ...
Joe Kington

나도 알고 있고 직접 사용하기를 주저합니다.하지만 새 속성을 허용하지 않도록 추가 작업을하는 것도 일반적으로 나쁜 생각입니다.)

2
사용하면 __slots__다중 상속도 중단됩니다. 클래스는 슬롯을 정의 하거나 C 코드에 정의 된 인스턴스 레이아웃 ( list, tuple또는 int)을 포함 하는 둘 이상의 클래스에서 상속 할 수 없습니다 .
Feuermurmel

__slots__피클 이 깨지면 고대 피클 프로토콜을 사용하는 것입니다. 패스 protocol=-1(파이썬 2 2 사용할 수있는 가장 최근의 프로토콜, 방법 피클하기 위해 2003 년에 도입을 ). Python 3 (각각 3 및 4)의 기본 및 최신 프로토콜은 모두 __slots__.
Nick Matteo

물론, 대부분의 시간은 내가 전혀 피클을 사용하여 후회 바람 : benfrederickson.com/dont-pickle-your-data
에릭 Aronesty

7

올바른 방법은 __setattr__. 그것이 거기에있는 이유입니다.


그렇다면 변수를 설정하는 적절한 방법은 무엇입니까 __init__? __dict__직접 설정하는 것 입니까?
astrofrog 2010 년

1
__setattr__에서 __init__,으로 재정의 합니다 self.__setattr__ = <new-function-that-you-just-defined>.
Katriel

6
@katrielalex : __xxx__메소드는 인스턴스가 아닌 클래스에서만 조회 되므로 새로운 스타일 의 클래스에서는 작동하지 않습니다.
Ethan Furman 2011 년

6

저는 데코레이터를 사용하는 솔루션을 매우 좋아합니다. 각 클래스에 대해 최소한의 추가로 프로젝트 전체의 많은 클래스에서 사용하기 쉽기 때문입니다. 그러나 상속과는 잘 작동하지 않습니다. 그래서 여기에 내 버전이 있습니다. __setattr__ 함수 만 재정의합니다. 속성이 존재하지 않고 호출자 함수가 __init__가 아니면 오류 메시지를 출력합니다.

import inspect                                                                                                                             

def froze_it(cls):                                                                                                                      

    def frozensetattr(self, key, value):                                                                                                   
        if not hasattr(self, key) and inspect.stack()[1][3] != "__init__":                                                                 
            print("Class {} is frozen. Cannot set {} = {}"                                                                                 
                  .format(cls.__name__, key, value))                                                                                       
        else:                                                                                                                              
            self.__dict__[key] = value                                                                                                     

    cls.__setattr__ = frozensetattr                                                                                                        
    return cls                                                                                                                             

@froze_it                                                                                                                                  
class A:                                                                                                                                   
    def __init__(self):                                                                                                                    
        self._a = 0                                                                                                                        

a = A()                                                                                                                                    
a._a = 1                                                                                                                                   
a._b = 2 # error

4

이것에 대해 :

class A():
    __allowed_attr=('_x', '_y')

    def __init__(self,x=0,y=0):
        self._x=x
        self._y=y

    def __setattr__(self,attribute,value):
        if not attribute in self.__class__.__allowed_attr:
            raise AttributeError
        else:
            super().__setattr__(attribute,value)

2

다음은 init에서 freeze ()하는 _frozen 속성이나 메소드가 필요하지 않은 접근 방식입니다.

초기화 하는 동안 모든 클래스 속성을 인스턴스에 추가합니다.

_frozen, freeze ()가없고 _frozen도 vars (instance) 출력에 표시되지 않기 때문에 이것을 좋아합니다.

class MetaModel(type):
    def __setattr__(self, name, value):
        raise AttributeError("Model classes do not accept arbitrary attributes")

class Model(object):
    __metaclass__ = MetaModel

    # init will take all CLASS attributes, and add them as SELF/INSTANCE attributes
    def __init__(self):
        for k, v in self.__class__.__dict__.iteritems():
            if not k.startswith("_"):
                self.__setattr__(k, v)

    # setattr, won't allow any attributes to be set on the SELF/INSTANCE that don't already exist
    def __setattr__(self, name, value):
        if not hasattr(self, name):
            raise AttributeError("Model instances do not accept arbitrary attributes")
        else:
            object.__setattr__(self, name, value)


# Example using            
class Dog(Model):
    name = ''
    kind = 'canine'

d, e = Dog(), Dog()
print vars(d)
print vars(e)
e.junk = 'stuff' # fails

필드 중 하나가 목록이면 작동하지 않는 것 같습니다. 하자 names=[]. 그런 다음 d.names.append['Fido']삽입합니다 'Fido'모두 d.namese.names. 나는 파이썬에 대해 그 이유를 충분히 알지 못합니다.
Reinier Torenbeek

2

pystrict스택 오버플로 질문에서 영감을 얻은 pypi 설치 가능한 데코레이터 입니다. 클래스와 함께 사용하여 고정 할 수 있습니다. 프로젝트에서 mypy 및 pylint가 실행중인 경우에도 이와 같은 데코레이터가 필요한 이유를 보여주는 README의 예가 있습니다.

pip install pystrict

그런 다음 @strict 데코레이터를 사용하십시오.

from pystrict import strict

@strict
class Blah
  def __init__(self):
     self.attr = 1

1

나는 Jochen Ritzel의 "Frozen"을 좋아합니다. 불편한 점은 Class .__ dict를 인쇄 할 때 isfrozen 변수가 표시 된다는 것입니다. 이 문제를 해결하려면 인증 된 속성 목록을 만들었습니다 ( slots 과 유사 ).

class Frozen(object):
    __List = []
    def __setattr__(self, key, value):
        setIsOK = False
        for item in self.__List:
            if key == item:
                setIsOK = True

        if setIsOK == True:
            object.__setattr__(self, key, value)
        else:
            raise TypeError( "%r has no attributes %r" % (self, key) )

class Test(Frozen):
    _Frozen__List = ["attr1","attr2"]
    def __init__(self):
        self.attr1   =  1
        self.attr2   =  1

1

FrozenClass요헨 Ritzel하여 멋진이지만, 호출 _frozen()할 때마다 너무 냉각되지 않은 클래스에 약식 (당신이 그것을 잊어 버리는 위험을 감수해야 할 때). __init_slots__기능을 추가했습니다 .

class FrozenClass(object):
    __isfrozen = False
    def _freeze(self):
        self.__isfrozen = True
    def __init_slots__(self, slots):
        for key in slots:
            object.__setattr__(self, key, None)
        self._freeze()
    def __setattr__(self, key, value):
        if self.__isfrozen and not hasattr(self, key):
            raise TypeError( "%r is a frozen class" % self )
        object.__setattr__(self, key, value)
class Test(FrozenClass):
    def __init__(self):
        self.__init_slots__(["x", "y"])
        self.x = 42#
        self.y = 2**3


a,b = Test(), Test()
a.x = 10
b.z = 10 # fails
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.