为何Python会将实例传入作为类属性的函数?
我定义了一个类属性并为其赋值一个函数。当调用该函数时,实例会被作为第一个参数传入(就像带self的实例方法调用一样)。我原本认为实例不会关心属性fn是函数,更不会将自身作为参数传入,请问这是为什么?
class BStatic: fn = None @staticmethod def static_fn(*args, **kwargs): print(args, kwargs) BStatic.fn = BStatic.static_fn bs = BStatic() bs.fn() # (<__main__.BStatic object at 0x0000022D63214590>,) {} BStatic.fn() # () {}
在另一个示例中,我将类属性赋值为模块中定义的函数。当通过类的实例调用该函数时,实例同样会被作为第一个参数传入。
class B: fn = None def instance_fn(self, *args, **kwargs): print(self, args, kwargs) def fn(*args, **kwargs): print(args, kwargs) B.fn = fn B.fn() # () {} b = B() b.fn() # (<__main__.B object at 0x0000022D643D23F0>,) {}
但如果直接在实例上赋值该属性,则不会出现这种情况。
b.fn = fn b.fn() # () {}
核心原因:Python的方法绑定机制
这是Python的属性访问绑定规则导致的,关键区别在于你访问的函数是类属性还是实例自身属性:
类属性中的函数会被自动绑定为实例方法
当你通过实例访问类中定义的函数属性时,Python会自动触发方法绑定:把这个函数包装成一个「绑定方法」,并将当前实例作为第一个参数(也就是常见的self)传入。不管这个函数原本是模块级的普通函数,还是类里定义的函数,只要它是类的属性,通过实例调用时就会触发这个绑定逻辑。
比如第二个示例里,B.fn是类属性,赋值了模块级的fn函数,当b.fn()调用时,Python自动把b作为第一个参数传给fn,所以输出里会看到实例对象。直接通过类调用类属性函数不会触发绑定
当你直接通过类(比如B.fn())调用时,函数就是普通的函数对象,Python不会做绑定处理,所以调用时不会自动传入任何额外参数,输出就是空的参数元组。实例自身的函数属性不会触发绑定
当你直接给实例赋值函数属性(比如b.fn = fn),这个属性是属于实例自己的,并非从类继承而来。访问实例自身的函数属性时,Python不会执行方法绑定逻辑,所以调用b.fn()就是普通的函数调用,不会自动传入实例。
关于静态方法的特殊情况
你第一个示例里的static_fn是静态方法,但赋值给BStatic.fn后,通过实例调用时依然传入了实例,这是因为:@staticmethod装饰的函数本质是一个描述器对象,但当你把它赋值给另一个类属性BStatic.fn后,当通过实例访问bs.fn时,描述器的逻辑没有被正确触发,导致最终还是触发了实例方法的绑定行为。如果直接调用bs.static_fn(),你会发现不会传入实例,这才是静态方法的正常表现。
内容的提问来源于stack exchange,提问作者msrc

