防止在__init__之外创建新属性

时间:2010-08-30 19:20:36

标签: python python-3.x class oop python-datamodel

我希望能够创建一个使用__init__初始化的类(在Python中),不接受新属性,但接受对现有属性的修改。我可以通过几种黑客方式来做到这一点,例如使用__setattr__方法,例如

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

然后直接在__dict__内编辑__init__,但我想知道是否有“正确”的方法来执行此操作?

13 个答案:

答案 0 :(得分:64)

我不会直接使用__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

答案 1 :(得分:24)

如果有人有兴趣使用装饰者这样做,这是一个有效的解决方案:

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

答案 2 :(得分:18)

实际上,您不希望__setattr__,而是__slots__。将__slots__ = ('foo', 'bar', 'baz')添加到类主体,Python将确保任何实例上只有foo,bar和baz。但请阅读文档列表中的警告!

答案 3 :(得分:14)

老虎机是要走的路:

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"

=>它无法创建属性" 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 Key-Sharing字典用于存储对象。实例的属性能够在彼此之间共享其内部存储器的一部分,即存储密钥的部分及其相应的散列。这有助于减少程序的内存消耗,从而创建许多非内置类型的实例。但仍然是避免动态创建属性的方法。

  2.   
  3. 使用插槽也需要自己承担费用。它将打破序列化(例如泡菜)。它还将打破多重继承。一个类不能从多个类继承,这些类要么定义槽,要么定义C代码中定义的实例布局(如list,tuple或int)。

  4.   

答案 4 :(得分:6)

正确的方法是覆盖__setattr__。这就是它的用途。

答案 5 :(得分:4)

我非常喜欢使用装饰器的解决方案,因为它很容易在项目中的许多类中使用它,每个类的添加量最少。但它对继承不起作用。 所以这是我的版本:它只覆盖__setattr__函数 - 如果该属性不存在且调用函数不是__init__,则会输出错误消息。

            List<S3ObjectSummary> files = s3client.listObjects(bucket.getName()).getObjectSummaries();
            for (S3ObjectSummary file : files) {
                if (file.getKey().endsWith("/"))
                    System.out.println("----" + file.getKey() + " (ES CARPETA)");
                else 
                    System.out.println("----" + file.getKey() + " NO NO NO");
            }

答案 6 :(得分:3)

这个怎么样:

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)

答案 7 :(得分:2)

这是我想出的方法,在init中不需要_frozen属性或方法来冻结()。

init 期间,我只需将所有类属性添加到实例中。

我喜欢这个,因为没有_frozen,freeze(),而且_frozen也没有显示在vars(实例)输出中。

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

答案 8 :(得分:1)

我喜欢&#34; Frozen&#34; Jochen Ritzel的评论不方便的是,当打印Class .__ dict 时会出现 isfrozen变量 我通过创建授权属性列表(类似于插槽)以这种方式解决了这个问题:

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 :(得分: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()
a.x = 10
b.z = 10 # fails

答案 10 :(得分:1)

pystrict受到a pypi installable decorator的启发,这个stackoverflow问题可以与类一起使用来冻结它们。自述文件中有一个示例,说明了即使在项目上运行了mypy和pylint的情况,为什么也需要这样的装饰器:

pip install pystrict

然后只使用@strict装饰器:

from pystrict import strict

@strict
class Blah
  def __init__(self):
     self.attr = 1

答案 11 :(得分:0)

没有一个答案提到覆盖 __setattr__ 的性能影响,这在创建许多小对象时可能是一个问题。 (而 __slots__ 将是高性能解决方案,但会限制 pickle/继承)。

所以我想出了这个变体,它会在 init 之后安装我们较慢的 settatr:

class FrozenClass:

    def freeze(self):
        def frozen_setattr(self, key, value):
            if not hasattr(self, key):
                raise TypeError("Cannot set {}: {} is a frozen class".format(key, self))
            object.__setattr__(self, key, value)
        self.__setattr__ = frozen_setattr

class Foo(FrozenClass): ...

如果您不想在 freeze 的末尾调用 __init__,如果继承是一个问题,或者如果您不想在 vars() 中调用,它也可以进行调整:例如,这里是基于 pystrict 答案的装饰器版本:

import functools
def strict(cls):
    cls._x_setter = getattr(cls, "__setattr__", object.__setattr__)
    cls._x_init = cls.__init__
    @functools.wraps(cls.__init__)
    def wrapper(self, *args, **kwargs):
        cls._x_init(self, *args, **kwargs)
        def frozen_setattr(self, key, value):
            if not hasattr(self, key):
                raise TypeError("Class %s is frozen. Cannot set '%s'." % (cls.__name__, key))
            cls._x_setter(self, key, value)
        cls.__setattr__ = frozen_setattr
    cls.__init__ = wrapper
    return cls

@strict
class Foo: ...

答案 12 :(得分:-1)

使用__slots__对此excellent solution进行的改进是让__init__方法定义允许的属性,然后使用当前类{{设置__slots__属性1}}

优点是在__dict__中添加属性时避免了双重更新:

__init__