是否可以指定类方法的“cls”参数,就像普通方法的“self”一样?

Bla*_*ift 5 python

是否可以显式指定cls类方法的参数?

例如,这工作正常,但n不使用self,因此应该是一个类方法:

class A():
    def n(self):
        print(self.__class__.__name__)

class B(A):
    pass

A.n(self=B())
Run Code Online (Sandbox Code Playgroud)

但以下内容不起作用,虽然它对我来说似乎相当:

class A():
    @classmethod
    def n(cls):
        print(cls.__name__)

class B(A):
    pass

A.n(cls=B)
Run Code Online (Sandbox Code Playgroud)

产生的异常是

TypeError: n() got multiple values for argument 'cls'
Run Code Online (Sandbox Code Playgroud)

XY问题分析:我有很多类方法,并且A有很多类派生自A. 这本质上就是我想做的:

class A():
    @classmethod
    def n1(cls): print("1", cls.__name__)
    @classmethod
    def n2(cls): print("2", cls.__name__)
    @classmethod
    def n3(cls): print("3", cls.__name__)

class B(A): pass
class C(A): pass
class D(A): pass
class E(A): pass
class F(A): pass

for cls in [B, C, D, E, F]:
    for fun in [A.n1, A.n2, A.n3]:
        fun(cls)
Run Code Online (Sandbox Code Playgroud)

当最后一行替换为

        getattr(cls, fun.__name__)()
Run Code Online (Sandbox Code Playgroud)

但我觉得这很丑陋。

我期望标题问题的答案是“否”,所以我的后续(也许是实际的)问题是“为什么?”。我很困惑为什么fun(cls)不允许。也许@classmethod只是在做一个functools.partial

(可以说我在设计中做了一些非常规的事情,因为我也希望这些方法同时是 a@classmethod和 a ,这是不可能的。)@property

Sha*_*ger 4

这里的问题是类方法是类本身的实际方法。通过类访问的实例方法只是一个函数(至少在 Python 3 上,它删除了未绑定方法的概念);它只是在实例上访问时的绑定方法。但是当你这样做时A.n,它正在创建一个绑定类方法;thecls已嵌入其中,因此您无法再次通过它。这是因为描述符协议在每个协议上的实现方式不同;aclassmethod绑定(到类),无论是在类还是实例上访问,实例方法仅在实例上访问时绑定,从类访问时返回原始函数,而 astaticmethod从不绑定(始终返回原始函数)。

您有几个选择:

  1. 将其用作适当的类方法;就打电话吧B.n()。这就是你 99% 的时间都应该做的事情。
  2. 如果它从未用作类方法(从不cls隐式传递,始终作为显式参数),请将其设为@staticmethod仍然需要cls,这意味着它永远不会绑定,每次都必须传递一个参数cls
  3. 通过手动解开绑定的类方法来撤消方法绑定的效果(有点难看,但如果您必须支持类的隐式和显式传递,则可能):A.n.__func__(B)

选项 #3 为您的用例提供了最小的修复

for cls in [B, C, D, E, F]:
    for method in [A.n1, A.n2, A.n3]:
        method.__func__(cls)
Run Code Online (Sandbox Code Playgroud)

或者,您可以通过仅使用名称本身来使其不那么难看;无论如何,您不需要访问任何内容A,因此只需执行以下操作:

for cls in [B, C, D, E, F]:
    for fun in ['n1', 'n2', 'n3']:
        getattr(cls, fun)()
Run Code Online (Sandbox Code Playgroud)

或者为了稍微提高效率和稍微简洁的代码,提前创建一个访问器函数并使用它:

from operator import attrgetter

funcgetter = attrgetter('n1', 'n2', 'n3')
for cls in [B, C, D, E, F]:
    for fun in funcgetter(cls):
        fun()
Run Code Online (Sandbox Code Playgroud)

可以使用类似的技巧来methodcaller减少getattr使用时调用的冗长性,但在这种情况下,我认为attrgetter更简洁:

from operator import methodcaller

funccallers = methodcaller('n1'), methodcaller('n2'), methodcaller('n3')
for cls in [B, C, D, E, F]:
    for caller in funccallers:
        caller(cls)
Run Code Online (Sandbox Code Playgroud)