Подтвердить что ты не робот

Предотвращение создания новых атрибутов вне __init__

Я хочу иметь возможность создать класс (в Python), который после инициализации с помощью __init__ не принимает новые атрибуты, но принимает изменения существующих атрибутов. Есть несколько способов, которые я могу увидеть, например, имея метод __setattr__, например

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

а затем отредактировав __dict__ непосредственно внутри __init__, но мне было интересно, есть ли "правильный" способ сделать это?

4b9b3361

Ответ 1

Я бы не использовал __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

Ответ 2

На самом деле вам не нужно __setattr__, вы хотите __slots__. Добавьте __slots__ = ('foo', 'bar', 'baz') в тело класса, и Python будет убедиться, что в любом экземпляре есть только foo, bar и baz. Но читайте оговорки в списках документации!

Ответ 3

Если кто-то заинтересован в том, чтобы сделать это с помощью декоратора, вот работающее решение:

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

Ответ 4

Правильный способ - переопределить __setattr__. Это то, что там есть.

Ответ 5

Мне очень нравится решение, которое использует декоратор, потому что его легко использовать для многих классов в проекте, с минимальными дополнениями для каждого класса. Но с наследованием это плохо работает. Итак, вот моя версия: она только переопределяет функцию __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

Ответ 6

Вот подход, который я придумал, для которого не требуется атрибут _frozen или метод для замораживания() в init.

Во время init я просто добавляю все атрибуты класса в экземпляр.

Мне это нравится, потому что нет _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

Ответ 7

Как насчет этого:

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)

Ответ 8

Мне нравится "Замороженный" Йохен Ритцель. Неудобным является то, что isfrozen variable появляется при печати класса.__ dict Я обошел эту проблему таким образом, создав список разрешенных атрибутов (аналогично слотам):

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

Ответ 9

FrozenClass Jochen 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