Python中类方法每次访问返回不同对象是否为Bug?
为什么访问Python类的classmethod时总是返回不同对象?这是Bug吗?
这绝对不是Bug,而是Python对类方法的设计实现导致的预期行为,而且这个行为在Python 2和3中是一致的,咱们来一步步拆解原因和解决办法。
核心原因:类方法的动态绑定特性
当你访问类上的实例方法或静态方法时,返回的是函数本身——它们是固定的对象,所以多次用is判断会得到True。但类方法不一样:
- 被
@classmethod装饰的方法,每次通过类(或子类)访问时,Python都会生成一个新的绑定类方法对象,这个对象会自动把当前访问的类绑定为第一个参数cls。 - 这种动态生成的绑定对象,每次都是新的实例,自然
is判断会返回False。
看你给出的示例代码和运行结果:
class Foo(object): @classmethod def foo_classmethod(cls): pass def foo_instancemethod(self): pass @staticmethod def foo_staticmethod(): pass
运行对比:
In [37]: print(Foo.foo_instancemethod is Foo.foo_instancemethod) True In [38]: print(Foo.foo_staticmethod is Foo.foo_staticmethod) True In [39]: print(Foo.foo_classmethod is Foo.foo_classmethod) False
这种设计是为了支持子类的动态绑定,比如当你通过子类访问父类的类方法时,绑定对象会自动把子类作为cls参数传入:
class Bar(Foo): pass print(Bar.foo_classmethod.__self__) # 输出 <class '__main__.Bar'> print(Foo.foo_classmethod.__self__) # 输出 <class '__main__.Foo'>
每次生成新的绑定对象,才能保证这种动态绑定的正确性。
解决pickle时的is判断问题
你提到在pickle类方法后,反序列化的对象is检查失败,这正是因为pickle保存的是某次生成的绑定对象,反序列化后会生成新的绑定对象。解决办法有两种:
对比底层函数对象
类方法的绑定对象有一个__func__属性,指向原始的函数对象——这个原始函数是固定的,多次访问都是同一个。所以可以通过对比这个属性来判断是否是同一个类方法:# 序列化前 original_method = Foo.foo_classmethod # 反序列化后 loaded_method = pickle.loads(pickle.dumps(original_method)) print(original_method.__func__ is loaded_method.__func__) # 输出 True序列化类和方法名而非绑定对象
不要直接pickle绑定的类方法对象,而是pickle类(或类名)和方法名字符串,反序列化时通过getattr获取:# 序列化 data = (Foo, "foo_classmethod") pickled = pickle.dumps(data) # 反序列化 cls, method_name = pickle.loads(pickled) loaded_method = getattr(cls, method_name) # 虽然loaded_method是新的绑定对象,但逻辑上是同一个方法
内容的提问来源于stack exchange,提问作者Saim Raza
相关产品推荐
相关产品推荐

