Python类构造方法中self赋值引发AttributeError的原因解析
问题描述
我写了一个Test类,想通过同类实例创建新实例。在__init__方法里,当第二个参数是Test实例时执行self = a / b,否则给self绑定a、b属性。但调用Test(1, hf)时出现AttributeError: 'Test' object has no attribute 'a',调用1/Test(1, hf)时在__rtruediv__方法里也出现关于属性b的错误,想知道原因是不是self = ...的写法错了?
对应的代码:
class Test: def __init__(self, a, b): if type(b) == Test: self = a / b else: self.a = a self.b = b def __rtruediv__(self, other): return Test(other * self.b, self.a) def __str__(self): return f"{self.a = }, {self.b = }" if __name__ == '__main__': hf = Test(1, 2) print(hf) # self.a = 1, self.b = 2 print(Test(1, hf)) # AttributeError: 'Test' object has no attribute 'a' print(1 / Test(1, hf)) # AttributeError: 'Test' object has no attribute 'b'
问题根源
没错,self = a / b的写法就是错误的,核心是对Python中__init__方法的作用理解有误:
__init__是实例初始化方法,它的作用是给已经创建好的实例绑定属性,而不是创建或替换实例本身。self只是__init__方法的一个局部参数,指向当前创建的实例。你给self重新赋值,只会改变这个局部变量的指向,完全不会影响外部实际返回的那个Test实例。
当你调用Test(1, hf)时,Python会先创建一个空的Test实例,再调用__init__。此时进入if分支,self = a / b会生成一个新的Test实例,但这个赋值只在__init__内部有效,外部返回的还是最初那个没有a、b属性的空实例。后续print调用__str__时,访问self.a自然会报错。
第二个错误同理,Test(1, hf)返回的空实例没有b属性,所以1 / 该实例触发__rtruediv__时,访问self.b就会抛出异常。
修复方案
要实现“根据参数类型返回不同实例”的逻辑,不能在__init__里处理,推荐两种方式:
方式一:用类方法作为自定义构造器
把参数判断逻辑放到类方法里,__init__只负责基础的属性初始化:
class Test: def __init__(self, a, b): self.a = a self.b = b @classmethod def create(cls, a, b): if isinstance(b, cls): return a / b return cls(a, b) def __rtruediv__(self, other): return Test(other * self.b, self.a) def __str__(self): return f"{self.a = }, {self.b = }" if __name__ == '__main__': hf = Test(1, 2) print(hf) # self.a = 1, self.b = 2 print(Test.create(1, hf)) # self.a = 2, self.b = 1 print(1 / Test.create(1, hf)) # self.a = 1, self.b = 2
方式二:重写__new__方法
__new__才是Python中负责创建实例的方法,我们可以在这里处理参数判断逻辑:
class Test: def __new__(cls, a, b): if isinstance(b, cls): return a / b return super().__new__(cls) def __init__(self, a, b): # 只有当__new__返回当前类的实例时,才会执行__init__ if not isinstance(b, Test): self.a = a self.b = b def __rtruediv__(self, other): return Test(other * self.b, self.a) def __str__(self): return f"{self.a = }, {self.b = }" if __name__ == '__main__': hf = Test(1, 2) print(hf) # self.a = 1, self.b = 2 print(Test(1, hf)) # self.a = 2, self.b = 1 print(1 / Test(1, hf)) # self.a = 1, self.b = 2
注意事项
- 用
isinstance(b, cls)代替type(b) == Test,这样能兼容子类实例,更符合Python的多态设计。 - 方式二中,如果
__new__返回的不是当前类的实例,__init__不会被执行,避免了无效的初始化操作。
内容的提问来源于stack exchange,提问作者Danny
相关产品推荐
相关产品推荐

