Function creation (what happened?)

Bruno Desthuilliers bruno.42.desthuilliers at websiteburo.invalid
Fri May 9 11:12:29 EDT 2008


Viktor a écrit :
> This completely slipped of my mind... :)
> 
> I'm trying to change the:
> http://wordaligned.org/svn/etc/echo/echo.py
> 
> So if the function is method it prints ClassName.MethodName instead of
> MethodName(self|klass|cls=<... ClassName>).
> 
> But it turned out that in the decorator, the wrapped function is
> always just a TypeFunction 

s/TypeFunction/function/

> (I cannot find out if the function is
> method, classmethod, staticmethod or just a plain function

The latter, unless you decorate it with a classmethod or staticmethod 
object before.

> - tried
> with inspect also)... And what is most interesting, when I do:
> 
> def w(fn):
>     print 'fn:', id(fn)
>     return fn
> 
> class A:
>     @w
>     def __init__(self): pass
> 
> print 'A.__init__:', id(A.__init__)
> 
> It turns out that the function I receive in the wrapper (even when I
> return the same function) is not the function which will finally be
> attached to the class...

Yes it is. But A.__init__ is *not* a function, it's a method. To get at 
the function, you must use A.__dict__['__init__'] or A.__init__.im_func

> Is there a way to find out in the decorator "what will the decorated
> function be"?

Yes : the decorated function is the function you decorate with the 
decorator.

Ok, this requires some explanations (nb: only valid for new-style classes):

- First point : what you declare in the class statement are plain 
ordinary function. When the class statement is executed - that is, 
usually[1], at import time - these function objects become attributes of 
the class object.
[1] IOW : if your class statement is at the top-level of your module

- Second point: the function class implements the descriptor 
protocol[2], so when an attribute lookup resolves to a function object, 
the function's class __get__ method is invoked
[2] http://users.rcn.com/python/download/Descriptor.htm

- Third point: the function's __get__ method returns a method object, 
either bound (if lookup was done on an instance) or unbound (if the 
lookup was done on a class).

The function's class __get__ method could be implemented this way:

    def __get__(self, instance, cls):
        return types.MethodType(self, instance, cls)


- Fourth point: a method object is a thin callable wrapper around the 
function, the instance (if provided), and the class. It could 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 __repr__(self):
     if self.im_self is None:
       # unbound
       return "<unbound method %s.%s>" \
               % (self.im_class.__name__, self.im_func.__name__)
     else:
       # bound
       return "<bound method %s.%s of %s>" \
                % (self.im_class.__name__,
                   self.im_func.__name__,
                   self.im_self)

   def __call__(self, *args, **kw):
     if self.im_self is None:
       try:
         instance, args = args[0], args[1:]
       except IndexError:
         raise TypeError(
           "unbound method %s() must be called with %s instance "
           " as first argument (got nothing instead)" \
           % (self.im_func.__name__, self.im_class.__name__)
       if not isinstance(instance, self.im_class):
         raise TypeError(
           "unbound method %s() must be called with %s instance "
           " as first argument (got %s instead)" \
           % (self.im_func.__name__, self.im_class.__name__, instance)

     else:
       instance = self.im_self
     return self.im_func(instance, *args, **kw)

The classmethod and staticmethod classes (yes, they are classes...) have 
their own implementation for the descriptor protocol - 
classmethod.__get__ returns a Method instanciated with func, cls, 
type(cls), and staticmethod.__get__ returns the original function.


So as you can see - and classmethods and staticmethods set aside -, what 
you decorate is *always* a function. And you just can't tell from within 
the decorator if this function is called "directly" or from a method 
object. The only robust solution is to decorate the function with your 
own custom callable descriptor. Here's a Q&D untested example that 
should get you started (warning : you'll have to check for classmethods 
and staticmethods)

class wmethod(object):
     def __init__(self, method):
        self.method = method

     def __call__(self, *args, **kw):
        # called as a method
        # your tracing code here
        # NB : you can access the method's
        # func, class and instance thru
        # self.method.im_*,
        return self.method(*args, **kw)

class w(object):
     def __init__(self, func):
       self.func = func

     def __get__(self, instance, cls):
       return wmethod(self.func.__get__(instance, cls))

     def __call__(self, *args, **kw):
       # called as a plain function
       # your tracing code here
       return self.func(*args, **kw)


HTH



More information about the Python-list mailing list