ast*_*rog 66 python oop class python-datamodel python-3.x
我希望能够创建一个类(在Python中),一旦初始化__init__,不接受新属性,但接受现有属性的修改.我可以看到有几种黑客方法可以做到这一点,例如有一个__setattr__方法,比如
def __setattr__(self, attribute, value):
if not attribute in self.__dict__:
print "Cannot set %s" % attribute
else:
self.__dict__[attribute] = value
Run Code Online (Sandbox Code Playgroud)
然后__dict__直接在里面编辑__init__,但我想知道是否有"正确"的方法来做到这一点?
Joc*_*zel 69
我不会__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
Run Code Online (Sandbox Code Playgroud)
Yoa*_*ann 27
如果有人有兴趣使用装饰器这样做,这是一个有效的解决方案:
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
Run Code Online (Sandbox Code Playgroud)
非常简单易用:
@froze_it
class Foo(object):
def __init__(self):
self.bar = 10
foo = Foo()
foo.bar = 42
foo.foobar = "no way"
Run Code Online (Sandbox Code Playgroud)
结果:
>>> Class Foo is frozen. Cannot set foobar = no way
Run Code Online (Sandbox Code Playgroud)
小智 18
实际上,你不想要__setattr__,你想要的__slots__.添加__slots__ = ('foo', 'bar', 'baz')到类主体,Python将确保在任何实例上只有foo,bar和baz.但请阅读文档列表中的警告!
Dhi*_*aTN 18
pythonic的方式是使用插槽而不是玩游戏__setter__.虽然它可以解决问题,但它不会带来任何性能提升.对象的属性存储在字典" __dict__"中,这就是为什么你可以动态地将属性添加到我们到目前为止创建的类的对象的原因.使用字典进行属性存储非常方便,但它可能意味着浪费空间,而对象只有少量的实例变量.
插槽是解决这个空间消耗问题的好方法.插槽提供静态结构,禁止在创建实例后添加,而不是具有允许动态向对象添加属性的动态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"
Run Code Online (Sandbox Code Playgroud)
=>无法创建属性"new":
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'
Run Code Online (Sandbox Code Playgroud)
注意:
从Python 3.3开始,优化空间消耗的优势就不那么令人印象深刻了.使用Python 3.3 密钥共享字典用于存储对象.实例的属性能够在彼此之间共享其内部存储的一部分,即存储密钥的部分及其对应的散列.这有助于减少程序的内存消耗,从而创建许多非内置类型的实例.但仍然是避免动态创建属性的方法.
使用插槽也需要它自己的成本.它将打破序列化(例如泡菜).它还将打破多重继承.类不能从多个类继承,这些类要么定义槽,要么在C代码中定义实例布局(如list,tuple或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
Run Code Online (Sandbox Code Playgroud)