How are arguments passed to a function through __getattr__
Asked Answered
D

2

16

Consider the following code example (python 2.7):

class Parent:
    def __init__(self, child):
        self.child = child

    def __getattr__(self, attr):
        print("Calling __getattr__: "+attr)
        if hasattr(self.child, attr):
            return getattr(self.child, attr)
        else:
            raise AttributeError(attr)

class Child:
    def make_statement(self, age=10):
        print("I am an instance of Child with age "+str(age))

kid = Child()
person = Parent(kid)

kid.make_statement(5)
person.make_statement(20)

it can be shown, that the function call person.make_statement(20) calls the Child.make_statement function through the Parent's __getattr__ function. In the __getattr__ function I can print out the attribute, before the corresponding function in the child instance is called. So far so clear.

But how is the argument of the call person.make_statement(20) passed through __getattr__? How am I able to print out the number '20' in my __getattr__ function?

Daystar answered 8/12, 2012 at 10:12 Comment(0)
L
29

You are not printing 20 in your __getattr__ function. The function finds the make_statement attribute on the Child instance and returns that. As it happens, that attribute is a method, so it is callable. Python thus calls the returned method, and that method then prints 20.

If you were to remove the () call, it would still work; we can store the method and call it separately to get 20 printed:

>>> person.make_statement
Calling __getattr__: make_statement
<bound method Child.make_statement of <__main__.Child instance at 0x10db5ed88>>
>>> ms = person.make_statement
Calling __getattr__: make_statement
>>> ms()
I am an instance of Child with age 10

If you have to see the arguments, you'd have to return a wrapper function instead:

def __getattr__(self, attr):
    print("Calling __getattr__: "+attr)
    if hasattr(self.child, attr):
        def wrapper(*args, **kw):
            print('called with %r and %r' % (args, kw))
            return getattr(self.child, attr)(*args, **kw)
        return wrapper
    raise AttributeError(attr)

This now results in:

>>> person.make_statement(20)
Calling __getattr__: make_statement
called with (20,) and {}
I am an instance of Child with age 20
Latimore answered 8/12, 2012 at 10:15 Comment(4)
@Alex: Updated, you'd need a wrapper function.Latimore
Yes, from your explanation I had the same idea, but it would have taken me longer for the realization. Thanks a lot, that seem to answer my question.Daystar
One more question though: Does your solution work even within the unittest framework? I see that your solution does not seem to work in such a case and I am trying to nail it down further.Daystar
@Alex: Not sure what you mean. If you want the unittest framework to still detect test* methods you need to add a __dir__ method to list all your proxied method names, see docs.python.org/2/library/functions.html#dirLatimore
A
0

For people who found the best answer somewhat confusing like me. I found that __getattr__ just expects to return callable function which it invokes, and sends the *args and **kwargs.

def printf(name, *args, **kwargs):
    print(name)
    print(args)
    print(kwargs)

class MyClass:
    def __getattr__(self, name, *args, **kwargs):
        # *args and **kwargs are available here as lists and dictionaries, respectively
        # You can use them just like any other argument
        # For example, you can pass them to another function
        return printf

t = MyClass()

t.get("hello", "wow", headers ={"token": "123456789"})

Output:

hello  
('wow',)
{'headers': {'token': '123456789'}}

Which is what I wanted, so you would need to wrap your function with another function to pass *args and *kwargs for that matter.

Adrien answered 27/12, 2022 at 15:33 Comment(3)
The code above does not work, getattr receives has only one arguments : the name of the searched attribute.Bladdernut
@Bladdernut I don't know what you are talking about... It works... I tested it again on an online compiler.Adrien
Sorry, "does not work" was a bit misleading, let me explain : getattr has only one parameter : the name of the searched attribute. Your definition : def __getattr__(self, name, *args, **kwargs): is misleading : *args and **kwargs are not transmitted and are not available. Hence the need for a wrapper function as in the answer by Martijn Pieters. I hope this makes things clearer.Bladdernut

© 2022 - 2024 — McMap. All rights reserved.