1

Consider the following code (which works)

class AAA:

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

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

AAA().run()

I will need to use in meth() an attribute of the instanced object, so meth() cannot be static anymore. I tried to do

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()

but this crashes with

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'

I then corrected my code to

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"

Why do I have to explicitly pass self when calling a method in the situation above?

WoJ
  • 27,165
  • 48
  • 180
  • 345
  • 3
    Why use `getattr()` at all? Why not `AAA.meth(what="hello")` in `run()`? – vanPelt2 Jun 08 '20 at 17:42
  • @vanPelt2: in my real code I will get the name of the method to call as a string. The code in the question is the [minimal reproductible](https://stackoverflow.com/help/minimal-reproducible-example) one. – WoJ Jun 08 '20 at 17:44
  • `getattr` is a builtin Python function. It works on any object, but you have to tell it which object. `self` is only passed implicitly to "bound methods". – bnaecker Jun 08 '20 at 17:44
  • Also, you could call it as: `getattr(self, 'meth')(what='hello')`. Those are equivalent. [This answer](https://stackoverflow.com/a/114267/1354854) may be helpful. – bnaecker Jun 08 '20 at 17:45
  • 1
    You are looking up ``meth`` on the *class* ``AAA``, not the *instance* ``self``. – MisterMiyagi Jun 08 '20 at 17:50

2 Answers2

4

getattr(AAA, 'meth') is exactly equivalent to AAA.meth; you haven't involved the instance bound to self in the attribute lookup, so you are getting back a raw function, not a method object.

Instead, pass self as the first argument:

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

Even ignoring getattr, something like

a = AAA()
a.run()

is equivalent to

a = AAA()
AAA.run(a)

This has to do with the descriptor protocol; since function objects implement a __get__ method, both AAA.run and a.run are actually the return value of that method.

In the case AAA.run, None is passed as the first argument, and the return value is the function itself.

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

In the case of a.run, the instance a is passed as the first argument, and the result is a `method object.

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

When the method object is called, it calls the original function with a as the first argument and its own arguments as the remaining arguments.

chepner
  • 497,756
  • 71
  • 530
  • 681
2

This is a misunderstanding of class vs instance. Where you have AAA is a class, and AAA() is an instance of class AAA.

Notice how the very first argument of your methods within the class are always self - that's because these are instance methods that are expected to be operated on your instance. Where as for classmethods they are usually defined by cls as the first argument to avoid confusion. (Ultimately, the naming of these argument is arbitrary, but the position is what's important).

As such, whenever you retrieve AAA.meth (equivalent of getattr(AAA, 'meth')) without an instance, the attribute is still retrieved, but the instance object, self, is not passed because it wasn't given. So therefore the interpreter is rightly asking to know, what is the instance to be worked with here?

To simplify, observe below:

>>> 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>

Notice the str method upper is equivalent to s.upper once you call str(s).upper, because the class now knows which class it is working with. Coincidentally:

>>> 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

Notice how as long as the context of the instance is provided within the class, or the method itself, it will still execute the same. But when called without an instance, it is expecting that first argument (self) to be fulfilled. When working with instances, the instance itself will automatically fulfill the first positional argument (self) in the method calls.


In summary - pay attention to how your objects, functions and methods are defined. The first argument to be fulfilled is self, and that is what you should be passing in, instead of the AAA class object. Work directly on the instance if it is expected to be an instance method:

def run(self, methodname, *args, **kwargs):
    getattr(self, methodname)(*args, **kwargs)
r.ook
  • 13,466
  • 2
  • 22
  • 39