3

在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()
4

7 回答 7

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

结果:

有用!
1
42
28

另请参阅gnibblers 的答案,该答案将这个概念巧妙地包装在类装饰器中,因此它不会弄乱类定义,并且可以在多个类中重用而无需重复代码。


编辑:

一年后回到这个答案,我意识到上下文管理器可能会更好地解决这个问题。这是 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
于 2012-08-15T06:41:23.027 回答
4

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

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

于 2012-08-15T06:24:15.433 回答
3

基于lazyr的答案的类装饰器怎么样

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"
于 2012-08-15T07:12:08.003 回答
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] != '_' ]))
    
于 2012-08-15T06:29:19.737 回答
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
于 2012-08-15T06:25:01.040 回答
1

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

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

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

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

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

于 2012-08-15T06:39:19.390 回答
0

假设您现在希望您的类具有一组固定的可变和不可变属性?我破解了 gnibbler 的答案,使类属性在初始化后不可变:

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
于 2012-09-26T21:14:32.080 回答