防止在__init__之外创build新的属性

我希望能够创build一个类(以Python),一旦用__init__初始化,不接受新的属性,但接受现有属性的修改。 有几种黑客方式我可以看到这样做,例如有一个__setattr__方法,如

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

然后直接在__init__编辑__dict__ ,但是我想知道是否有一个“正确”的方法来做到这一点?

__dict__直接使用__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() ax = 10 bz = 10 # fails 

其实,你不想要__setattr__ ,你想__slots__ 。 将__slots__ = ('foo', 'bar', 'baz')到类体中,Python将确保在任何实例上只有foo,bar和baz。 但请阅读文档列表中的警告!

如果有人对装饰者有兴趣,下面是一个可行的解决scheme:

 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__ 。 这就是它的目的。

我非常喜欢使用装饰器的解决scheme,因为很容易将其用于整个项目中的许多类,并且每个类都添加了最less的附加内容。 但是,inheritance不能很好地工作。 所以这里是我的版本:它只覆盖__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 

这里是我想到的方法,不需要在init中冻结()的_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 

那这个呢:

 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) 

我喜欢Jochen Ritzel的“冰雪奇缘”。 不方便的是isfrozenvariables然后出现在打印一个类.__字典我这样绕过这个问题通过创build一个授权属性列表(类似于插槽 ):

 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 

Jochen Ritzel的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() ax = 10 bz = 10 # fails