arun raju
arun raju

Reputation: 41

__get__() of a method/function in Python

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

Answers (3)

chepner
chepner

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

CryptoFool
CryptoFool

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

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

Related Questions