【问题标题】:Why doesn't python throw an exception when you set an attribute of a class that doesn't exist为什么设置不存在的类的属性时python不抛出异常
【发布时间】:2020-12-30 11:59:29
【问题描述】:

我试图永远调试我的代码,结果发现这是我的错误的原因,所以很难找到。一个简单的例子来说明我在说什么:

class Test():
  def __init__(self):
    self.a = 0

x = Test()
x.b = 2
print(x.a)
print(x.b)

此代码不会引发任何错误。事实上,它会成功打印出 0 和 2。所以即使 Test 不包含实例变量b,它仍然会在我分配它时创建它。如果我初始化第二个测试变量

y = Test()
print(y.b)

它会按预期抛出错误。

那么为什么首先存在这个功能,以便能够在类的实例上创建新属性?启用此行为的幕后发生了什么?有什么方法可以禁用这种行为,或者至少在编程时以某种方式捕获它?

【问题讨论】:

  • __dict__ 是您要查找的搜索词
  • 有趣。所以看起来阻止它的唯一方法是覆盖自定义类的 setattr 。最初启用此功能背后的思考过程是什么?我觉得这样做弊大于利。
  • 不是这样。 __slots__ 就是为此目的而制作的。如果你有__dict__,我总是可以通过x.__dict__['blah'] = 'haha!'绕过你的setattr

标签: python python-class


【解决方案1】:

标准 Python 类将实例属性存储在底层的 dict 上(名为 __dict__)。没有特殊的规则可以让它在__init__ 内的赋值和其他任何地方的赋值之间做出有意义的区分;在__init__ 之前它是一个空的命名空间,__init__ 可以添加到该命名空间,但其他任何人也可以(如果您只在 __init__ 中创建相同的属性集并且永远不会创建更多属性,现代 CPython 有一些优化可以减少内存使用,但如果您违反该规则以保留现有行为,它将回退到旧的、内存密集型存储)。

这有时很方便,例如当类上的另一个方法想要懒惰地计算一个属性时,只有当该方法被调用并且需要计算时。它只是保留未定义的属性,并在需要它的地方捕获AttributeError 并计算(并缓存)当时的值。

这是高级脚本类语言中非常常见的设计(除了 Python,其他默认允许这样做的语言包括 Perl、Ruby 和 JavaScript,仅举几例),因为它们对类实例的基本定义是只是“一个以dict 为键的字符串,上面有一些魔法”。虽然他们可以制定规则以使事情更具限制性,但这并没有什么好处,所以他们只是让事情尽可能灵活。

正如您所注意到的,像这样的属性的自动激活可能会变得混乱,而且有时是不可取的。如果这是一个问题,并且您想预先定义一组可以定义的受限属性,只需在类本身上使用有效属性的字符串名称定义__slots__。这将替换底层属性的dict,在底层属性数组中具有连续分配的插槽(插槽名称成为知道如何唯一访问每个插槽的描述符)。它通过避免每个实例相对浪费的dict 来节省内存,并且它会阻止创建新属性。对于您的情况,您只需:

class Test():
  __slots__ = 'a',
  def __init__(self):
      self.a = 0

并尝试分配给b 属性(在类内部或外部)将终止:

AttributeError: 'Test' object has no attribute 'b'

请注意,这也会禁止对类的实例进行弱引用;如果你想允许的话,你必须明确地将'__weakref__' 列为类中的一个槽。

【讨论】:

  • 感谢您快速而全面的回复!
猜你喜欢
  • 2013-12-28
  • 2010-10-12
  • 2011-11-21
  • 1970-01-01
  • 2017-07-03
  • 1970-01-01
  • 1970-01-01
  • 2011-11-04
  • 1970-01-01
相关资源
最近更新 更多