You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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保存的是某次生成的绑定对象,反序列化后会生成新的绑定对象。解决办法有两种:

  1. 对比底层函数对象
    类方法的绑定对象有一个__func__属性,指向原始的函数对象——这个原始函数是固定的,多次访问都是同一个。所以可以通过对比这个属性来判断是否是同一个类方法:

    # 序列化前
    original_method = Foo.foo_classmethod
    # 反序列化后
    loaded_method = pickle.loads(pickle.dumps(original_method))
    print(original_method.__func__ is loaded_method.__func__)  # 输出 True
    
  2. 序列化类和方法名而非绑定对象
    不要直接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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:38:55