我希望能够创建一个(以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方法是使用插槽,而不是与 __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"

=>它无法创建一个属性“ 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'

NB:

  1. 由于Python 3.3优化空间消耗的优势不再令人印象深刻。与Python 3.3 钥匙共享 字典用于存储对象。实例的属性能够彼此之间共享其内部存储的一部分,即存储键及其相应哈希的部分。这有助于减少程序的记忆消耗,从而创建许多非构造类型的实例。但仍然是避免动态创建属性的方法。

  2. 使用插槽也带有自身成本。它将破坏序列化(例如泡菜)。它也将破坏多个继承。一个类不能从定义插槽或hat的一个类别的实例布局(例如列表,元组或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也没有出现在var(实例)输出中。

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的“冷冻”。不便是 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 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

pystrictPYPI可安装的装饰器 受这个堆叠问题的启发,可以与类一起用于冻结它们。 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