Предотвращение создания новых атрибутов вне __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__
, но мне было интересно, есть ли "правильный" способ сделать это?
Ответы
Ответ 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