使Python类在创建新属性时抛出错误

时间:2014-07-31 14:40:24

标签: python class python-2.7 attributes

让我们说这是我的班级:

class A:
    def __init__(self):
        self.good_attr = None
        self.really_good_attr = None
        self.another_good_attr = None

然后调用者可以设置这些变量的值:

a = A()
a.good_attr = 'a value'
a.really_good_attr = 'better value'
a.another_good_attr = 'a good value'

但他们也可以添加新属性:

a.goood_value = 'evil'

这对我的用例来说是不可取的。我的对象被用于将一些值传递给一组方法。 (基本上,这个对象替换了一些方法上的一长串共享参数,以避免重复,并清楚地区分共享内容和不同内容。)如果调用者拼写错误属性名称,那么该属性将被忽略,导致意外和令人困惑,可能很难弄清楚行为。最好快速失败,通知调用者他们使用了将被忽略的属性名称。因此,类似于以下内容的是当我们使用对象上尚不存在的属性名称时我想要的行为:

>>> a.goood_value = 'evil'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: A instance has no attribute 'goood_value'

我怎样才能做到这一点?

我还要注意,我完全清楚调用者可以创建一个新类并做任何他们想做的事情,完全绕过它。但是,这将是不受支持的行为。制作我提供的对象只是创建一个快速失败的骨头检查,以便为那些利用我提供的对象(包括我自己)的人节省打字错误的时间,而不是让他们抓住头脑,想知道为什么事情以意想不到的方式表现

3 个答案:

答案 0 :(得分:8)

您可以使用__setattr__方法挂钩属性设置。此方法是为所有属性设置调用的,因此请考虑将其调用为正确的&#39;属性也是:

class A(object):
    good_attr = None
    really_good_attr = None
    another_good_attr = None

    def __setattr__(self, name, value):
        if not hasattr(self, name):
            raise AttributeError(
                '{} instance has no attribute {!r}'.format(
                    type(self).__name__, name))
        super(A, self).__setattr__(name, value)

由于good_attr等在类上定义,hasattr()调用会返回True这些属性,并且不会引发异常。您也可以在__init__中设置相同的属性,但要在hasattr()类上定义属性 才能生效。

另一种方法是创建一个可以测试的白名单。

演示:

>>> a = A()
>>> a.good_attr = 'foo'
>>> a.bad_attr = 'foo'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 10, in __setattr__
AttributeError: A instance has no attribute 'bad_attr'

当然,确定的开发人员仍然可以通过向a.__dict__实例字典添加键来为您的实例添加属性。

另一种选择是使用__slots__的副作用;槽用于节省内存,因为字典需要更多的空间,而不仅仅是将值直接放入Python为每个实例创建的C结构中(不需要键和动态表)。这种副作用是在这样的类实例上没有更多属性的地方:

class A(object):
    __slots__ = ('good_attr', 'really_good_attr', 'another_good_attr')

    def __init__(self):
        self.good_attr = None
        self.really_good_attr = None
        self.another_good_attr = None

然后错误消息如下:

>>> a = A()
>>> a.good_attr = 'foo'
>>> a.bad_attr = 'foo'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'bad_attr'

但请阅读文档中列出的使用__slots__的注意事项。

由于使用__dict__时没有__slots__实例属性,因此该选项实际上关闭了在实例上设置任意属性的大门。

答案 1 :(得分:0)

更惯用的选择是使用命名元组。

Python 3.6及更高版本

在Python 3.6及更高版本中,您可以使用typing.NamedTuple来轻松实现这一目标:

from typing import NamedTuple, Any

class A(NamedTuple):
    good_attr: Any = None
    really_good_attr: Any = None
    another_good_attr: Any = None

如果需要,可以使用更具体的类型约束,但是必须包含注释,NamedTuple才能使用属性。

这不仅阻止了新属性的添加,而且还阻止了现有属性的设置:

>>> a = A()
>>> a.goood_value = 'evil'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'goood_value'
>>> a.good_attr = 'a value'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

这会强制您在构造时指定所有值:

a = A(
    good_attr='a value',
    really_good_attr='better value',
    another_good_attr='a good value',
)

这样做通常不是问题,当出现问题时,可以明智地使用局部变量来解决此问题。

Python 3.5及更低版本(包括2.x)

这些版本的Python要么没有typing模块,要么typing.NamedTuple不能如上使用。在这些版本中,您可以使用collections.namedtuple来达到几乎相同的效果。

定义类很简单:

from collections import namedtuple

A = namedtuple('A', ['good_attr', 'really_good_attr', 'another_good_attr'])

然后按上述方式进行施工:

a = A(
    good_attr='a value',
    really_good_attr='better value',
    another_good_attr='a good value',
)

但是,这不允许忽略调用构造函数的某些值。构造对象时,可以显式包括None值:

a = A(
    good_attr='a value',
    really_good_attr=None,
    another_good_attr='a good value',
)

或者您可以使用one of several techniques为参数提供默认值:

A.__new__.func_defaults = (None,) * 3
a = A(
    good_attr='a value',
    another_good_attr='a good value',
)

答案 2 :(得分:-1)

通过向它添加两个下划线使参数变为私有,ex self .__ good_attr,这样有人就无法在类之外设置该参数。然后创建一个设置__good_attr变量的函数,如果错误则抛出异常。