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

时间:2019-12-11 11:46:09

标签: python

是否可以明确指定类方法的cls自变量?

例如,这很好用,但是n不使用self,因此应该是一种分类方法:

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

class B(A):
    pass

A.n(self=B())

但是以下内容不起作用,尽管它似乎和我一样:

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

class B(A):
    pass

A.n(cls=B)

产生的异常是

TypeError: n() got multiple values for argument 'cls'

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)

当最后一行替换为

时有效
        getattr(cls, fun.__name__)()

但是我觉得这很丑。

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

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

2 个答案:

答案 0 :(得分:2)

这里的问题是,类方法实际上是类本身的方法。通过类访问的实例方法只是一个函数(至少在Python 3上,该方法删除了未绑定方法的概念);在实例上访问时,它只是一个绑定方法。但是,当您执行A.n时,它就是在创建绑定类方法。 cls已嵌入其中,因此您无法再次传递它。这是因为每个描述符协议的实现方式; classmethod绑定(绑定到类)无论是在类还是实例上访问,仅当在实例上访问时才使用实例方法,从类访问时返回原始函数,而staticmethod则从不绑定(总是返回原始功能)。

您有几种选择:

  1. 将其用作适当的类方法;只需致电B.n()。这就是您99%的时间应该做的事情。
  2. 如果从未将其用作类方法(永远不会隐式地传递cls,始终将其作为显式参数),请将其设为仍使用@staticmethod的{​​{1}},这意味着它永远不会绑定,您必须每次都传递一个cls参数
  3. 通过手动解开绑定的类方法来解除方法绑定的效果(有点丑陋,但如果必须同时支持类的隐式和显式传递,则有可能):cls

选项3为您的用例提供了最小的解决方法

A.n.__func__(B)

或者,您可以仅通过使用名称本身来使其不那么难看;无论如何,您都不需要访问for cls in [B, C, D, E, F]: for method in [A.n1, A.n2, A.n3]: method.__func__(cls) 上的任何内容,只需执行以下操作:

A

或者为获得更高的效率和更简洁的代码,请提前创建访问器函数并使用它:

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

可以使用from operator import attrgetter funcgetter = attrgetter('n1', 'n2', 'n3') for cls in [B, C, D, E, F]: for fun in funcgetter(cls): fun() 做类似的技巧来减少使用时methodcaller调用的冗长性,但是在这种情况下,我认为getattr会更干净一些:

attrgetter

答案 1 :(得分:1)

或者您可以将丑陋封装在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__)

    @staticmethod
    def call_method(cls, fun):
        getattr(cls, fun.__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]:
        A.call_method(cls, fun)