Reputation: 41
I have a piece of code that I am trying to understand, and even with the existing answers, I really couldn't understand the purpose of the following code, Can someone please help me in understanding the same?
I have already looked a various relevant questions ( __get__()
) here and I couldnt find specific answers. I understand that the class below is trying to create a method on the fly ( possibly we get to this class from a __getattr__()
method which fails to find an attribute ) and return the method to the caller. I have commented right above the lines of code I need understanding with.
class MethodGen(object):
def __getattr__(self, name):
method = self.method_gen(name)
if method:
return self.method_gen(name)
def method_gen(self, name):
def method(*args, **kwargs):
print("Creating a method here")
# Below are the two lines of code I need help understanding with
method.__name__ = name
setattr(self, name, method.__get__(self))
return method
If I am not wrong, the method()
function's attribute __name__
has been set, but in setattr()
function, the attribute of the class MethodGen
, name
is set to what ?
Upvotes: 0
Views: 826
Reputation: 532003
Consider the class below:
class Foo:
def bar(self):
print("hi")
f = Foo()
f.bar()
bar
is a class attribute that has a function as its value. Because function
implements the descriptor protocol, however, accessing it as Foo.bar
or f.bar
does not immediately return the function itself; it causes the function's __get__
method to be invoked, and that returns either the original function (as in Foo.bar
) or a new value of type instancemethod
(as in f.bar
). f.bar()
is evaluated as Foo.bar.__get__(f, Foo)()
.
method_gen
takes the function named method
, and attaches an actual method retrieved by calling the function's __get__
method to an object. The intent is so that something like this works:
>>> m = MethodGen()
>>> n = MethodGen()
>>> m.foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MethodGen' object has no attribute 'foo'
>>> n.foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MethodGen' object has no attribute 'foo'
>>> m.method_gen('foo')
<function foo at 0x10465c758>
>>> m.foo()
Creating a method here
>>> n.foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MethodGen' object has no attribute 'foo'
Initially, MethodGen
does not have any methods other than method_gen
. You can see the exception raised when attempting to invoke a method named foo
on either of two instances. Calling method_gen
, however, attaches a new method to just that particular instance. After calling m.method_gen("foo")
, m.foo()
calls the method defined by method_gen
. That call does not affect other instances of MethodGen
like n
.
Upvotes: 0
Reputation: 23119
This question really intrigued me. The two answers provided didn't seem to tell the whole story. What bothered me was the fact that in this line:
setattr(self, name, method.__get__(self))
the code is not setting things up so that method.__get__
Will be called at some point. Rather, method.__get__
is actually Being Called! But isn't the idea that this __get__
method will be called when a particular attribute of an object, an instance of MethodGen in this case, is actually referenced? If you read the docs, this is the impression you get...that an attribute is linked to a Descriptor that implements __get__
, and that implementation determines what gets returned when that attribute is referenced. But again, that's not what's going on here. This is all happening before that point. So what IS really going on here?
The answer lies HERE. The key language is this:
To support method calls, functions include the
__get__()
method for binding methods during attribute access. This means that all functions are non-data descriptors which return bound methods when they are invoked from an object.
method.__get__(self)
is exactly what's being described here. So what method.__get__(self)
is actually doing is returning a reference to the "method" function
that is bound to self
. Since in our case, self
is an instance of MethodGen
, this call is returning a reference to the "method" function
that is bound to an instance of MethodGen
. In this case, the __get__
method has nothing to do with the act of referencing an attribute. Rather, this call is turning a function reference into a method reference!
So now we have a reference to a method we've created on the fly. But how do we set it up so it gets called at the right time, when an attribute with the right name is referenced on the instance it is bound to? That's where the setattr(self, name, X)
part comes in. This call takes our new method and binds it to the attribute with name name
on our instance.
All of the above then is why:
setattr(self, name, method.__get__(self))
is adding a new method to self
, the instance of the MethodGen
class on which method_gen
has been called.
The method.__name__ = name
part is not all that important. Executing just the line of code discussed above gives you all the behavior you really want. This extra step just attaches a name to our new method so that code that asks for the name of the method, like code that uses introspection to write documentation, will get the right name. It is the instance attribute's name...the name passed to setattr
...that really matters, and really "names" the method.
Upvotes: 1
Reputation: 7887
Interesting, never seen this done before, seems tough to maintain (probably will make some fellow developers want to hang you).
I changed some code so you can see a little more of what is happening.
class MethodGen(object):
def method_gen(self, name):
print("Creating a method here")
def method(*args, **kwargs):
print("Calling method")
print(args) # so we can see what is actually being outputted
# Below are the two lines of code I need help understanding with
method.__name__ = name # These the method name equal to name (i.e. we can call the method this way)
# The following is adding the new method to the current class.
setattr(self, name, method.__get__(self)) # Adds the method to this class
# I would do: setattr(self, name, method) though and remove the __get__
return method # Returns the emthod
m = MethodGen()
test = m.method_gen("my_method") # I created a method in MethodGen class called my_method
test("test") # It returned a pointer to the method that I can use
m.my_method("test") # Or I can now call that method in the class.
m.method_gen("method_2")
m.method_2("test2")
Upvotes: 0