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
--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to