为什么设置不存在的 class 的属性时 python 不抛出异常

Why doesn't python throw an exception when you set an attribute of a class that doesn't exist

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

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)

它会按预期抛出错误。

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

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

这有时很方便,例如当 class 上的另一个方法仅在调用该方法并且需要计算时才想延迟计算属性。它只是让属性未定义,在需要它的地方,它捕获 AttributeError 并计算(和缓存)当时的值。

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

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

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

并尝试分配给 b 属性(在 class 内部或外部)将死于:

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

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