Referring to class methods in class attributes

Bruno Desthuilliers bdesth.quelquechose at free.quelquepart.fr
Wed Feb 17 14:44:47 EST 2010


mk a écrit :
> Stephen Hansen wrote:
> 
>> You don't have to (and can't) refer to the class within the body.
>> Class statements are sort of... odd. They are code which is directly
>> executed, and the results are then passed into a
>> metaclass/type/whatever and a class object is created. While within
>> the class body, the class doesn't exist yet.
>>
>> But you don't need it to.
>>
>> Just do:
>>
>>    'internal_date': print_internal_date
>>
>> The 'def' is in the same local scope as 'tagdata' is.
> 
> 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.

> bc all other functions in
> tagdata have only a single argument:
> 
> class PYFileInfo(FileInfo):
>     'python file properties'
> 
>     def print_internal_date(filename):
> ...
> 
>     tagdata = {'compiled_fname': lambda x: x + 'c',
>                 'size': os.path.getsize,
>                 'internal_date': print_internal_date
>             }
> 
> That looks weird: a method with no 'self'.

It's not a method.

> Hmm that is probably
> seriously wrong.
> 
> This obviously means no other method can call it like
> self.print_internal_date(), because self would get passed as first
> argument, yes?

Unless you make it a staticmethod.

> I checked that print_internal_date can be called on an instance, so the
> same method

s/method/function/


> can be seen as:
> 
> - class method -- when used in local scope definition like tagdata, or
> - instance method -- when called from an instance or from self?

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'])


So, why is it that type(Foo.bar) != type(Foo.__dict__['bar']) ? 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)
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), 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.

A (naive) implementation of the whole thing might look like this:

class method(object):
    def __init__(self, func, instance, cls):
        self.im_func = func
        self.im_self = instance
        self.im_class = cls

    def __call__(self, *args, **kw):
        # XXX : all sanity checks removed for readability
        if self.im_self:
            args = (self.im_func,) + args
        return self.im_func(*args, **kw)


class function(object):
   # ...
   def __get__(self, instance, cls):
       return method(self, instance, cls)


So, what makes a function a "method" is not being defined in a class
statement's body (well, not directly at least), it's that it is an
attribute of the class. FWIW, the following code is perfectly legal:

class Foo(object):
    pass

def func(obj):
   print "obj is %s " % obj

Foo.method = func

f = Foo()
f.method()
Foo.method(f)
func(f)

> I wonder if I'm not trying to make Python things it shouldn't be doing,
> but it's the problem at hand that is leading me into this conundrum: all
> other functions for tagdata use single arguments. I should probably code
> around that anyway..

Well, the simple solution is to just leave print_internal_date as a
plain function instead of insisting on making it a method. Python is
100% OO in that everything is an object, but it's not a "pure" OO
language, ie it doesn't require everything to happen in a method. So if
all you need is a function, by all mean just use a function !-)

Now if you really need print_internal_date to be exposed as a method of
PYFileInfo - like, you need polymorphic dispatch - then make it a
staticmethod.

My 2 cents...



More information about the Python-list mailing list