Я хочу иметь возможность создать класс (в 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__
, но мне было интересно, есть ли «правильный» способ сделать это?
python
python-3.x
class
oop
python-datamodel
астрофрог
источник
источник
__setattr__
но это, вероятно, было бы взломано.Ответы:
Я бы не стал использовать
__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
источник
hasattr
при вызовахgetattr
actall результат удаляется и в случае ошибок возвращается False, см. Этот блог . Нашел обходной путь, заменивnot hasattr(self, key)
наkey not in dir(self)
. Это могло быть медленнее, но решило проблему для меня.Если кому-то интересно сделать это с помощью декоратора, вот рабочее решение:
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
источник
Слоты - это то, что вам нужно:
Питонический способ - использовать слоты вместо игры с
__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'
источник
Собственно, не хочешь
__setattr__
, хочешь__slots__
. Добавьте__slots__ = ('foo', 'bar', 'baz')
в тело класса, и Python позаботится о том, чтобы в любом экземпляре были только foo, bar и baz. Но прочтите списки документации!источник
__slots__
работает, но, помимо прочего, это нарушит сериализацию (например, рассол) ... Как правило, использование слотов для управления созданием атрибутов - плохая идея, вместо того, чтобы уменьшать накладные расходы памяти, на мой взгляд, в любом случае ...__slots__
также нарушает множественное наследование. Класс не может наследовать более чем от одного класса, который определяет слоты или имеет макет экземпляра, определенный в коде C (напримерlist
,tuple
илиint
).__slots__
соления ломаются, значит, вы используете древний протокол. Перейдитеprotocol=-1
к методам pickle для самого последнего доступного протокола, который равен 2 в Python 2 ( представлен в 2003 году ). Протоколы по умолчанию и последние версии в Python 3 (3 и 4 соответственно) обрабатывают__slots__
.Правильный способ - переопределить
__setattr__
. Вот для чего он нужен.источник
__init__
? Чтобы установить их__dict__
напрямую?__setattr__
в__init__
, поself.__setattr__ = <new-function-that-you-just-defined>
.__xxx__
методы просматриваются только в классе, а не в экземпляре.Мне очень нравится решение, в котором используется декоратор, потому что его легко использовать для многих классов в проекте с минимальными добавлениями для каждого класса. Но с наследованием это плохо работает. Итак, вот моя версия: она переопределяет только функцию __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
источник
Что насчет этого:
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)
источник
Вот подход, который я придумал, для которого не нужен атрибут или метод _frozen для freeze () в 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
источник
names=[]
. Потомd.names.append['Fido']
вставлю'Fido'
в обаd.names
иe.names
. Я недостаточно знаю Python, чтобы понять, почему.pystrict
- это устанавливаемый декоратор pypi, вдохновленный этим вопросом stackoverflow, который можно использовать с классами для их замораживания. В README есть пример, который показывает, зачем нужен такой декоратор, даже если в вашем проекте запущены mypy и pylint:pip install pystrict
Тогда просто используйте декоратор @strict:
from pystrict import strict @strict class Blah def __init__(self): self.attr = 1
источник
Мне нравится «Холодное сердце» Йохена Ритцеля. Неудобно то, что переменная isfrozen затем появляется при печати Class .__ 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
Йохен 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
источник