1

I understand the basic function and use case of __getattr__ method. But I'm having trouble how parameters are passed around inside __getattr__.

I have code

class Wrapper:
    def __init__(self, object):
        self.wrapped = object
    def __getattr__(self, attrname):
        print('trace: ' + attrname)
        return getattr(self.wrapped, attrname)


x = Wrapper([1, 2, 3])
x.append(4)
print(x.wrapped)

And it prints

trace: append
[1, 2, 3, 4]

Forgive me if my assumption below is incorrect. I assume that the parameter 4 in x.append(4) is somehow passed from the initial call to __getattr__ and then to the call to getattr method and then to the append method of list class. But I'm not sure what exactly is happening. Could someone please clarify the details.

Thor
  • 9,638
  • 15
  • 62
  • 137
  • 1
    What? Neither `__getattr__` nor `getattr` ever come in contact with that `4`. 4 isn't an attribute, after all. – Aran-Fey Sep 30 '18 at 08:01
  • @Aran-Fey I understand that `4` is not an attribute, but I was just wondering how is the original argument `4` passed to the call to `append` method of `list` class – Thor Sep 30 '18 at 08:02

2 Answers2

3

Here is how I understand it:

x = Wrapper([1, 2, 3])

So, x.wrapped is a list, that is [1, 2, 3].

Now, when you do x.append, you call __getattr__ of Wrapper, with argument append. So when python resolves:

getattr(self.wrapped, attrname)

it gets the method append of your inner list, which is then returned. So, you could imagine that your code is interpreted as:

# just got it outside for later explanation
l = [1, 2, 3]

x = Wrapper(l)

f = x.__getattr__('append')
# here `f` is the `append` method of `l`

f(4) # which is equivalent to `l.append(4)`

# finally
print(x.wrapped)
# which is equivalent to `print(l)`, hence the output
# [1, 2, 3, 4]

Hope it helps.

Léopold Houdin
  • 1,515
  • 13
  • 18
  • hi Houdin, thank for the answer. It definitely helps me better visualise the process. Just wondering if you know any official documentation that document the process? – Thor Sep 30 '18 at 08:14
  • Well... You have python's [official documentation for `__getattr__`](https://docs.python.org/3/reference/datamodel.html#object.__getattr__), but apart from this link, I don't really know... – Léopold Houdin Sep 30 '18 at 08:16
  • I have already checked the official documentation for __getattr__ but it doesn't seem to contain the info I'm looking for. Thanks for helping out! really appreciate it :) – Thor Sep 30 '18 at 08:18
  • 1
    I have found this [link](https://www.python.org/download/releases/2.3/mro/) about python's resolution order of methods. It's actually for python 2.3, but I guess it doesn't differ a lot from the one used in 3.x. – Léopold Houdin Sep 30 '18 at 08:22
1

__getattr__ never comes in contact with that 4. __getattr__ simply returns a bound method, which is then called with the argument 4. Obtaining the bound method and calling the bound method are two unrelated steps, and __getattr__ is only responsible for the first one.

It might be easier to follow along if you mentally split this line:

x.append(4)

into two:

method = x.append  # obtain the bound method
method(4)          # call the bound method

Clearly, __getattr__ is called in the first line and has nothing to do with the 2nd line. __getattr__ simply returns a reference to the append method of the wrapped list, as you can easily verify for yourself:

>>> x.append
<built-in method append of list object at 0x7f096feb2fc8>

This is a bound method. A bound method is simply a method that has a reference to the object that will take the place of the self when the method is called (in this case, the list x.wrapped). Bound methods are the reason why we can call methods without explicitly passing an argument for self.

For more details about bound methods, see What is the difference between a function, an unbound method and a bound method?.

Aran-Fey
  • 39,665
  • 11
  • 104
  • 149