在Python中,计算类中的变量数或阻止添加新的类变量

时间:2012-08-15 06:22:37

标签: python class variables count

在python中,有没有办法在定义对象后阻止添加新的类变量?

例如:

class foo:
    def __init__(self):
        self.a = 1
        self.b = 2
        self.c = 3

bar = foo()
try:
    bar.d = 4
except Exception, e:
    print "I want this to always print"

或者,有没有办法计算对象中的变量数量?

class foo:
    def __init__(self):
        self.a = 1
        self.b = 2
        self.c = 3
    def count(self):
        ...

bar = foo()
if bar.count() == 3:
    print "I want this to always print"

我想到这一点的唯一方法是使用字典或列表:

class foo:
    def __int__(self):
        self.dict = {'foo':1, 'bar':2} 
        self.len  = 2
    def chk():
        return self.len == len(self.list)

但是,这样做对python来说相当麻烦。 (obj.dict [ '富'])。如果可能的话,我更喜欢obj.foo。

我希望这样做,以便在我改变现有变量时,我从不会意外地声明变量。

f = foo()
f.somename = 3
...
f.simename = 4 #this is a typo

if f.somename == 3:
    solve_everything()

先谢谢。

7 个答案:

答案 0 :(得分:5)

我建议使用__setattr__来避免__slots__的奇怪之处。

在弄乱__setattr__时总是要小心,因为它负责设置所有实例属性,包括您在__init__中设置的属性。因此,它必须有一些方法知道何时允许设置属性,以及何时拒绝它。在这个解决方案中,我指定了一个特殊属性来控制是否允许新属性:

class A(object):
    def __init__(self):
        self.a = 1
        self.b = 2
        self.c = 3
        self.freeze = True

    def __setattr__(self, attr, value):
        if getattr(self, "freeze", False) and not hasattr(self, attr):
            raise AttributeError("You shall not set attributes!")
        super(A, self).__setattr__(attr, value)

测试:

a = A()
try:
    a.d = 89
except AttributeError:
    print "It works!"
else:
    print "It doesn't work."
a.c = 42
print a.a
print a.c
a.freeze = False
a.d = 28
a.freeze = True
print a.d

结果:

It works!
1
42
28

另请参阅gnibblers answer,它将这个概念整齐地包装在类装饰器中,因此它不会使类定义混乱,并且可以在几个类中重复使用而不会重复代码。


修改

一年后回到这个答案,我意识到上下文管理器可能会更好地解决这个问题。这是gnibbler的类装饰器的修改版本:

from contextlib import contextmanager

@contextmanager
def declare_attributes(self):
    self._allow_declarations = True
    try:
        yield
    finally:
        self._allow_declarations = False

def restrict_attributes(cls):
    cls.declare_attributes = declare_attributes
    def _setattr(self, attr, value):
        disallow_declarations = not getattr(self, "_allow_declarations", False)
        if disallow_declarations and attr != "_allow_declarations":
            if not hasattr(self, attr):
                raise AttributeError("You shall not set attributes!")
        super(cls, self).__setattr__(attr, value)
    cls.__setattr__ = _setattr

    return cls

以下是如何使用它:

@restrict_attributes
class A(object):
    def __init__(self):
        with self.declare_attributes():
            self.a = 1
            self.b = 2
            self.c = 3

因此,只要您想设置新属性,只需使用上面的with语句即可。它也可以从实例外部完成:

a = A()
try:
    a.d = 89
except AttributeError:
    print "It works!"
else:
    print "It doesn't work."
a.c = 42
print a.a
print a.c
with a.declare_attributes():
    a.d = 28
print a.d

答案 1 :(得分:4)

  

在python中,有没有办法阻止在定义对象后添加新的类变量?

是。 __slots__。但仔细阅读说明。

答案 2 :(得分:3)

基于lazyr's answer

的类装饰器怎么样?
def freeze(cls):
    _init = cls.__init__
    def init(self, *args, **kw):
        _init(self, *args, **kw)
        self.freeze = True
    cls.__init__ = init 

    def _setattr(self, attr, value):
        if getattr(self, "freeze", None) and (attr=="freeze" or not hasattr(self, attr)):
            raise AttributeError("You shall not set attributes!")
        super(cls, self).__setattr__(attr, value)
    cls.__setattr__ = _setattr

    return cls

@freeze
class foo(object):
    def __init__(self):
        self.a = 1
        self.b = 2
        self.c = 3


bar = foo()
try:
    bar.d = 4
except Exception, e:
    print "I want this to always print"

答案 3 :(得分:2)

  1. 使用__slots__类属性阻止添加新属性:

    class foo(object):
        __slots__ = ['a', 'b', 'c']
        def __init__(self):
            self.a = 1
            self.b = 2
            self.c = 3
    
    bar = foo()
    
    try:
        bar.d = 4
    except Exception as e:
        print(e,"I want this to always print")
    
  2. 计算属性:

    print(len([attr for attr in dir(bar) if attr[0] != '_' ]))
    

答案 4 :(得分:1)

使用它来计算实例的no.of属性:

>>> class foo:
    def __init__(self):
        self.a = 1
        self.b = 2
        self.c = 3


>>> bar=foo()
>>> bar.__dict__
{'a': 1, 'c': 3, 'b': 2}
>>> len(bar.__dict__)  #returns no. of attributes of bar
3

答案 5 :(得分:1)

您的意思是新的变量或新的实例变量吗?后者看起来像你的意思,而且更容易做到。

Per Ignacio Vazquez-Abrams的回答,__slots__可能就是你想要的。只需在您的类中执行__slots__ = ('a', 'b', 'c'),这将阻止创建任何其他属性。请注意,这仅适用于您的类的实例 - 仍然可以设置类级属性,并且子类可以添加它们所需的任何属性。他是对的 - 有一些奇怪之处,所以在开始向各处插槽之前阅读链接的文档。

如果您不使用广告位,则return len(vars(self))可用作建议的count广告的正文。

作为插槽的替代方法,您可以定义__setattr__拒绝任何不属于“已知商品”列表的属性,或者在frozen属性设置为True后拒绝任何新属性__init__等的结束。这很难做到,但更灵活。

如果您确实希望实例在初始化后完全是只读的,并且您使用的是最新版本的Python,请考虑定义namedtuple或其子类。元组子类也有一些局限性;如果你需要走这条路,我可以扩展它,但我会坚持使用插槽,除非你有理由不这样做。

答案 6 :(得分:0)

假设您现在希望您的类具有一组固定的可变属性和不可变属性?我已经攻击gnibbler's answer以在init:

之后使类属性不可变
def frozenclass(cls):
    """ Modify a class to permit no new attributes after instantiation.
        Class attributes are immutable after init.
        The passed class must have a superclass (e.g., inherit from 'object').
    """
    _init = cls.__init__
    def init(self, *args, **kw):
        _init(self, *args, **kw)
        self.freeze = True
    cls.__init__ = init

    def _setattr(self, attr, value):
        if getattr(self, "freeze", None):
            if attr=="freeze" or not hasattr(self, attr):
                raise AttributeError("You shall not create attributes!")
            if hasattr(type(self), attr):
                raise AttributeError("You shall not modify immutable attributes!")
        super(cls, self).__setattr__(attr, value)
    cls.__setattr__ = _setattr

    return cls

一个例子:

@frozenclass
class myClass(object):
    """ A demo class."""
    # The following are immutable after init:
    a = None
    b = None
    c = None

    def __init__(self, a, b, c, d=None, e=None, f=None):
        # Set the immutable attributes (just this once, only during init)
        self.a = a
        self.b = b
        self.c = c
        # Create and set the mutable attributes (modifyable after init)
        self.d = d
        self.e = e
        self.f = f