【问题标题】:In Python, count the number of variables in a class or prevent adding new class variables在 Python 中,统计类中变量的数量或防止添加新的类变量
【发布时间】:2012-08-15 06:22:36
【问题描述】:

在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['foo'])。如果可能的话,我更喜欢 obj.foo。

我想要这样,这样当我打算更改现有变量时,我就不会意外地声明一个变量。

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

if f.somename == 3:
    solve_everything()

【问题讨论】:

  • 感谢大家的及时和有帮助的回复。

标签: python class variables count


【解决方案1】:

我建议使用__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

结果:

有用! 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

【讨论】:

  • @Duncan 好点,但根据 OP,这里的目标是防止创建新属性 意外。我看不出任何人怎么可能偶然写出a.__dict__['d'] = 89 而不是a.c = 89。 ;)
【解决方案2】:

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

是的。 __slots__。但是仔细阅读注释。

【讨论】:

  • 但这不是__slots__的预期用途
  • @gnibbler:当然。但这是一个强大的副作用。
  • 如果需要只读对象,namedtuple 也很有用。
  • 如果不需要额外的方法。
  • @jjia6395 OP 不想要只读对象。他希望能够改变现有的属性,而不是偶然设置新的属性。所以 namedtuple 已经出来了,因为它是不可变的。
【解决方案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"

【讨论】:

  • 如果我还有一个赞成票,那就是给你。感谢您明确使用类装饰器。
【解决方案4】:
  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] != '_' ]))
    

【讨论】:

    【解决方案5】:

    使用它来计算实例的属性数:

    >>> 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
    

    【讨论】:

      【解决方案6】:

      您是指新的class 变量还是新的instance 变量?后者看起来像你的意思,而且更容易做到。

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

      如果您不使用插槽,return len(vars(self)) 将作为您建议的 count 方法的主体。

      作为插槽的替代方案,您可以定义一个 __setattr__ 来拒绝不在“已知良好”列表中的任何属性,或者在 @ 结尾处将 frozen 属性设置为 True 后拒绝任何新属性987654328@等。这更难正确,但更灵活。

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

      【讨论】:

        【解决方案7】:

        假设您现在希望您的类具有一组固定的可变和不可变属性?我已经破解了 gnibbler's answer 以使类属性在初始化后不可变:

        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
        

        【讨论】:

        • 我在工作中发布了上述内容,公司系统不允许个人登录。有没有办法可以将该帖子与我真正的 StackOverflow 登录相关联?
        • 下一步:添加迭代器支持。由于属性集是固定的,我们应该能够添加许多 namedtuple 功能。让这个装饰器创建 namedtuple 和 dict 的经典爱子。
        • __init__() 的存在似乎是无缘无故的:它只是可以生成的样板。为什么不为一个函数做一个装饰器呢?这会让人想起 ActiveState 配方 (code.activestate.com/recipes/500261-named-tuples/#c16) 中的这条评论。函数参数会变成可变属性,局部变量会变成不可变属性。
        • 还有一个变化:使python枚举隐喻(定义一个只包含类属性的类)在实例化时具有真正的const条目:替换:_init = cls.__init__替换为:if not hasattr(cls, '__init__'): def _init():pass; else: _init = cls.__init__
        猜你喜欢
        • 2018-11-11
        • 2011-03-24
        • 1970-01-01
        • 2021-06-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-12
        相关资源
        最近更新 更多