Referring to class methods in class attributes

Bruno Desthuilliers bruno.42.desthuilliers at websiteburo.invalid
Thu Feb 18 08:34:37 EST 2010


mk a écrit :
> Bruno Desthuilliers wrote:
>>> Thanks, that worked. But in order to make it work I had to get rid of
>>> 'self' in print_internal_date signature
>>
>> Indeed. Using it that way, the print_internal_date will not be wrapped
>> in a method object.
> 
> Hold on! How does Python know what to wrap and what not to wrap, 
> assuming of course programmer doesn't use @classmethod or @staticmethod? 

answered below - read on, young padawan <g>

> Bc self has no special significance, it's just a (strong) convention, 
> Python can't know what's the first argument of a function supposed to 
> be, self or regular argument, and therefore it has no way of 
> differentiating between functions (defined in class body) designed to 
> become methods and those that are not?

Indeed.

> Where can I read on Python internals like this (aside from post of 
> yours, that is)? Bc  frankly skimming http://docs.python.org/reference/ 
> didn't give me impression that a lot on the subject is there (well 
> there's some, I found smth akin to your explanation below, although 
> yours is way more readable)?

Thanks <blush />

> Thanks for explanation below -- I'm going to ask some related questions.
> 
>> Mmmm... Let's try to explain the whole damn thing. It's really (and IMHO
>> beautifully) simple once you get it, but I agree it's a bit peculiar
>> when compared to most mainstream OO languages.
>>
>> The first thing is that the def statement *always* yield a function
>> object. Always. If you don't believe it, try the following snippet:
>>
>> class Foo(object):
>>     def bar(self):
>>         return "baaz"
>>
>> print Foo.__dict__.keys()
>> print type(Foo.__dict__['bar'])
> 
> Just one thing here:
> 
>  >>> Foo.bar
> <unbound method Foo.bar>
> 
> Huh?! Why does it say 'unbound' method? Shouldn't that be bound method 
> (bound to Foo, that is)?

Yes, but it's not bound to a Foo instance.

>> So, why is it that type(Foo.bar) != type(Foo.__dict__['bar']) ?
> 
>  >>> type(Foo.__dict__['bar'])
> <type 'function'>

Yeps. That's the function object created by the def statement. Just a 
plain old function - expect it's an attribute of class object "Foo".

>  >>> type(Foo.bar)
> <type 'instancemethod'>
> 
> instancemethod - now that's something new.

Don't let the "<unbound method Foo.bar>" fools you - it's just 
instancemethod.__repr__ that issues different wording according to 
whether the instancemethod instance is bound or not.

> 
>> The
>> answer is : attribute lookup rules and the descriptor protocol.
>>
>> To make a long story short, the descriptor protocol specify that, when,
>> during an attribute lookup, a name resolves to a class attribute AND
>> this attribute has a __get__ method, then this __get__ method is called
>>  (with either the instance or None and the class itself as arguments)
> 
> Depending, I assume, on whether this is instance call | class method 
> call, respectively?

s/call/lookup/

If it's looked up on the class, there's no instance to pass to __get__.

> 
> Hmm why does the __get__ receive class as argument on top of instance | 
> None? After all, when having an instance, the class can always be found 
> by instance.__class__ ? Is this for sake of class methods?

Having access to the class is handy when you don't have the instance. 
The point is mostly to let the descriptor know how it has been looked up 
and take appropriate decisions based on this - for a definition of 
"appropriote" that depends on what the descriptor is intended for . 
Remember that this mechanism provides the generic support for all kind 
of computed attributes - methods, properties, and whatever you can write 
yourself.

> Python is science, I gather: an answer to one question bears another 10 
> questions.

That's the case with most technical domains - until you solved enough of 
the puzzle to start and see the big picture.

>> and whatever it returns becomes the result of the attribute lookup. This
>> mechanism is what provides support for computed attributes.
>>
>> Now the trick is that the function type do implement the descriptor
>> protocol. So when a function is an attribute of a class object and you
>> try to access it as an attribute of either the class itself or an
>> instance of the class, it's __get__ method is called with the instance
>> (or None) and the class. 
> 
>> Having access to itself (of course),
> 
> Quick question: how does a function access itself?

In that case, it's quite simple: function.__get__ is a method of the 
function type, so it's called with 'self' as first argument !-)

> Aside from rejected 
> PEP (http://www.python.org/dev/peps/pep-3130/) I don't see the way of 
> accessing itself outside globals()

You're confusing the function instance itself with the content of the 
def statement's block. The code within the def statement's block has no 
access to the function instance that will be built from it, but other 
methods of the function instance are, well, ordinary methods.

> (and even then how would a function 
> know its name -- well it shouldn't care about it really, as function 
> object doesn't care how it's labelled, right?). Or does in "real Python" 
> func's __get__ receive its own function (func)

it receives itself, yes. Ordinary method call, nothing magical here - 
well, almost...!-).

> as an argument, like in 
> your example implementation below?

Exactly.

>> the
>> instance (if there's one) and the class, it's easy for it to wrap all
>> this into a method object. Which is itself a callable object, that when
>> called mostly inject the instance as first object in the argument's list
>> and returns the result of calling the wrapped function object.
> 
> Aha! So that's the mechanism that makes self magically appear in an 
> argument list! I always wondered how it worked. 

Now you know. As far as I'm concerned, I do find this whole mechanism to 
be a thing of beauty - instead of special-casing methods, you just use 
plain functions and the much more generic descriptor protocol to achieve 
the same result - with much more flexibility.

> !!THANKS!!
> 
>> My 2 cents...
> 
> Well, Bruno -- that was more like $200!

Ok, make it 3 cents then !-)

More seriously, all this is explained in Raymond Hettinger's excellent 
'descriptor howto' article, that is linked from the official doc - and 
that's how I learned all this.



More information about the Python-list mailing list