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

StackOverflow https://stackoverflow.com/questions/3603502

Вопрос

Я хочу быть в состоянии создать класс (в 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__, но мне было интересно, есть ли «правильный» способ сделать это?

Это было полезно?

Решение

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

Другие советы

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

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

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

Слоты - это путь:

Pythonic Way - использовать слоты вместо того, чтобы играть с __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"

=> Это не может создать атрибут «новый»:

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'

NB:

  1. Поскольку Python 3.3 Преимущество оптимизации потребления пространства не так впечатляет. С Python 3.3. Соблюдение ключа Словари используются для хранения объектов. Атрибуты экземпляров способны совмещать часть своего внутреннего хранения между собой, то есть часть, которая хранит ключи и их соответствующий хэси. Это помогает уменьшить потребление программ памяти, которые создают много экземпляров не встроенных типов. Но все равно есть способ пойти, чтобы избежать динамически созданных атрибутов.

  2. Использование слотов идет также с собственной стоимостью. Он сломает сериализацию (например, соленья). Это также сломает многократное наследование. Класс не может наследовать от более чем одного класса, который либо определяет слоты, либо шляпу экземпляра, определяемая в C-коде C (например, список, кортеж или INT).

Правильный путь - переопределить __setattr__. Отказ Это то, что он там.

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

Вот подход, я придумал, что это не нужен атрибут _frozen или метод для замораживания () в init.

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

Мне это нравится, потому что нет _frozen, Freeze (), а _frozen также не отображается в выходе Vars (экземпляра).

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

Что насчет этого:

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)

Мне нравится «замороженные» Йохен Ритзеля. Неудобные это то, что Переменная ISFROZEN затем появляется при печати класса .__ 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

То FrozenClass Йочен Ритзель круто, но звонит _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

pystrict является PYPI установлен декоратор Вдохновленный этим вопросом стойки, который можно использовать с классами, чтобы заморозить их. Существует пример для Readme, который показывает, почему такое декоратор нужен, даже если у вас Mypy и Pylint работает на вашем проекте:

pip install pystrict

Тогда просто используйте @strict Decorator:

from pystrict import strict

@strict
class Blah
  def __init__(self):
     self.attr = 1
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top