Python: 获取未绑定的类方法

20 投票
2 回答
6909 浏览
提问于 2025-04-17 14:00

你怎么能获取一个没有绑定的类方法呢?

class Foo:
    @classmethod
    def bar(cls): pass

>>> Foo.bar
<bound method type.bar of <class '__main__.Foo'>>

补充说明:这是Python 3。抱歉让你困惑了。

2 个回答

0

根据我的经验,在Python 3中确实存在未绑定的类方法,虽然时间很短。当一个类方法刚被创建时,因为它是在类定义的一部分,这个类还没有真正存在,所以这个方法是未绑定的。一旦这个类真正存在了,这个方法就变成了绑定的。为了保留对未绑定方法的引用,它必须“隐藏”在一个列表、字典等里面;仅仅把它保存在类的属性中是不够的。

我用这个方法来获取一个未绑定的类方法,这样我就可以在后面检查其他对象是否与它的类型相符。

    class _HasUnboundClassMethod(object):
        @classmethod
        def _classmethod(cls):
            pass  # pragma: no cover
        _methods = [ _classmethod ]

    _ClassMethodType = type(_HasUnboundClassMethod._methods[0])
27

Python 3 没有“未绑定的方法”。先不谈 classmethod,我们来看这个:

>>> class Foo:
...     def baz(self): pass
>>> Foo.baz
<function __main__.baz>

在 2.x 版本中,这个会显示为 <unbound method Foo.baz>,但在 3.x 版本中就没有未绑定的方法了。

如果你想从一个绑定的方法中提取出函数,这很简单:

>>> foo = Foo()
>>> foo.baz
<bound method Foo.baz of <__main__.Foo object at 0x104da6850>>
>>> foo.baz.__func__
<function __main__.baz>

同样的道理:

>>> class Foo:
...     @classmethod
...     def bar(cls): pass
>>> Foo.bar
<bound method type.bar of <class '__main__.Foo'>>
>>> Foo.bar.__func__
<function __main__.bar>

在 2.x 版本中事情就更有趣了,因为实际上是有未绑定的方法可以获取的。通常你看不到未绑定的 classmethod,因为它们的设计初衷就是在类创建时就绑定到类上,而不是在实例创建时再绑定到每个实例上。

但实际上,未绑定的方法就是任何 instancemethod,其 im_self 是 None。所以,就像你可以这样做:

class Foo(object):
    def baz(self): pass

foo = Foo()
bound_baz = foo.baz
unbound_baz = new.instancemethod(bound_baz.im_func, None, bound_baz.im_class)

注意,bound_baz.im_func 是 2.x 版本中 bound_baz.__func__ 的对应物,但 new.instancemethod 在 3.x 中没有对应的东西。

文档中提到,new 已经被弃用,推荐使用 types,以便与 3.x 兼容。实际上,在 2.x 中你可以这样做:

unbound_baz = types.MethodType(bound_baz.im_func, None, bound_baz.im_class)

但在 3.x 中这就不行了,因为 MethodType 不接受 class 参数,也不允许 instance 参数为 None。个人来说,当我在做一些明确只能在 2.x 中使用的事情,而不能移植到 3.x 时,我觉得使用 new 更清晰。

总之,给定一个 2.x 版本的类,你可以这样做:

class Foo(object):
    @classmethod
    def bar(cls): pass

bound_bar = Foo.bar
unbound_bar = new.instancemethod(bound_bar.im_func, None, bound_bar.im_class)

如果你打印出来,你会看到:

<unbound method type.bar>

或者,使用你的例子,配合一个旧式类:

class Foo:
    @classmethod
    def bar(cls): pass

<unbound method classobj.bar>

是的,可能有点作弊,因为旧式类的 classmethodim_classclassobj,尽管这不是 Foo.__class__,但这似乎是让旧式类和新式类在所有常见用例中正常工作的最合理的方法。

撰写回答