为什么调用 gettatr 方法需要显式传递 self?

Why does calling a gettatr method require to explicitly pass self?

考虑以下代码(有效)

class AAA:

    def run(self):
        getattr(AAA, 'meth')(what='hello')

    @staticmethod
    def meth(what):
        print(what)

AAA().run()

我需要在 meth() 中使用实例化对象的属性,因此 meth() 不能再是静态的。我试着做

class AAA:

    def __init__(self):
        self.who = 'John'

    def run(self):
        getattr(AAA, 'meth')(what='hello')

    def meth(self, what):
        print(f"{what} {self.who}")

AAA().run()

但这会与

一起崩溃
Traceback (most recent call last):
  File "C:/scratch_15.py", line 12, in <module>
    AAA().run()
  File "C:/scratch_15.py", line 7, in run
    getattr(AAA, 'meth')(what='hello')
TypeError: meth() missing 1 required positional argument: 'self'

然后我将我的代码更正为

class AAA:

    def __init__(self):
        self.who = 'John'

    def run(self):
        getattr(AAA, 'meth')(self, what='hello')

    def meth(self, what):
        print(f"{what} {self.who}")

AAA().run()

# outputs "hello John"

为什么在上述情况下调用方法时必须显式传递self

getattr(AAA, 'meth') 完全等同于 AAA.meth;您没有在属性查找中涉及绑定到 self 的实例,因此您得到的是原始函数,而不是 method 对象。

相反,传递 self 作为第一个参数:

    def run(self, methodname):
        getattr(self, methodname)(what='hello')  # self.meth(what='hello')

甚至忽略 getattr,类似

a = AAA()
a.run()

等同于

a = AAA()
AAA.run(a)

这与描述符协议有关;由于 function 对象实现了 __get__ 方法,因此 AAA.runa.run 实际上都是该方法的 return 值。

AAA.run 的情况下,None 作为第一个参数传递,return 值是函数本身。

# AAA.run
>>> AAA.run.__get__(None, AAA)
<function AAA.run at 0x100833048>

a.run的情况下,实例a作为第一个参数传递,结果是一个`方法对象。

# a.run
>>> AAA.run.__get__(a, AAA)
<bound method AAA.run of <__main__.AAA object at 0x1008916a0>>

method对象被调用时,它以a为第一个参数调用原始函数,其余参数为它自己的参数。

这是对class vs instance的误解。 AAA 是 class,而 AAA() 是 class AAA.[=30 的 实例 =]

请注意 class 中方法的第一个参数总是 self - 这是因为这些是 instance 方法在您的 实例 上运行。至于 classmethods 它们通常由 cls 定义为第一个参数以避免混淆。 (归根结底,这些参数的命名是任意的,但重要的是位置)。

因此,每当您检索 AAA.meth(相当于 getattr(AAA, 'meth')而没有 实例时,仍会检索属性,但实例对象, self,因为没有给出所以没有通过。因此,解释器正确地要求知道,这里要使用的实例是什么?

为简化起见,请观察以下内容:

>>> s = 'hello'
>>> s.upper
<built-in method upper of str object at 0x085AA6C0>
>>> str.upper
<method 'upper' of 'str' objects>
>>> str(s).upper
<built-in method upper of str object at 0x085AA6C0>

注意 str 方法 upper 等同于 s.upper 一旦你调用 str(s).upper,因为 class 现在知道哪个 class它正在与。巧合的是:

>>> str.upper(s)
'HELLO'
>>> str(s).upper()
'HELLO'
>>> str.upper()
Traceback (most recent call last):
  File "<pyshell#167>", line 1, in <module>
    str.upper()
TypeError: descriptor 'upper' of 'str' object needs an argument

请注意,只要在 class 或方法本身中提供了实例的上下文,它仍然会执行相同的操作。但是当在没有实例的情况下调用时,它期望第一个参数 (self) 得到满足。使用实例时,实例本身将自动满足方法调用中的第一个位置参数 (self)。


总而言之——注意你的对象、函数和方法是如何定义的。要实现的第一个参数是 self,这是您应该传入的内容,而不是 AAA class 对象。如果预期是实例方法,则直接在实例上工作:

def run(self, methodname, *args, **kwargs):
    getattr(self, methodname)(*args, **kwargs)