views:

285

answers:

4

I want to make a decorator that creates a new function/method that makes use of an object obj. If the decorated object is a function, obj must be instantiated when the function is created. If the decorated object is a method, a new obj must be instantiated and bound to each instance of the class whose method is decorated. I can't put the decoration in __init__ because the decorator modifies the function documentation. I have something like this now, but it only instantiates time once, which is not what I want:

__all__ = ['dec', 'A']

from time import time
import inspect

def dec(f):
    obj = time() # want to set on object instantiation
    def new(*args, **kwargs):
        f(*args, **kwargs) # Validate against definition so it doesn't go
                           # out of sync
        print obj
        # ...
    try:
        d = inspect.getsourcelines(f)
    except IOError:
        d = "<unable to fetch definition>"
    else:
        d = d[0][1].rstrip('\n').rstrip(':').lstrip(' ').lstrip('def')
    new.__doc__ = d + "\n" + (f.__doc__ or '')
    return new

class A(object):
    @dec
    def f(self, x):
        """something"""
        print '%s.f(%s)' % (self, x)

if __name__ == '__main__':
    A().f(123)
    A().f(123)
    A().f(123)

My idea to solve this is to check if the object passed to the decorator takes an argument self, if so, return a method that binds obj to self if it's not already there, and then uses self.obj. And then if there is no self argument to the object passed to the decorator, just instantiate obj inside the decorator and return a function that makes use of that.

However... what I said doesn't really work for me because in my real decorator, I return an object that is derived from list and has a __call__ attribute. Furthermore in the real decorator, self is not even defined in the objects that get decorated by it because they don't make use of their instance variables (what I'm really decorating are just events to be subscribed to by external objects, the events have documented signatures).

Edit: Actually, if there's a way to make a list subclass instance get bound to an instance so that it's __call__ attribute implicitly receives the class instance (like in any normal instance method), this would be a perfect solution, this was what I was originally trying to figure out how to do. But maybe there is an even better solution such that I don't have to define the decorated methods with the self attribute? Either is perfect.

+1  A: 

Since a decorator is just syntactic sugar for saying

def func():
   ...
func = decorator(func)

Why not do that in the object constructor?

class A(object):
    def __init__(self):
        # apply decorator at instance creation
        self.f = dec(self.f)

    def f(self, x):
        """something"""
        print '%s.f(%s)' % (self, x)
Ian Clelland
I explicitly said that is not what I want. My decorator changes the documentation of the decorated function, which will not happen if you put the decoration in `__init__`.
Longpoke
@Longpoke: Why is the `__doc__` so important? Why not use a metaclass to generate parts of the `__doc__`
S.Lott
The decorator converts the function into a subclass of `list` and adds a `__call__` to it. That destroys the `__doc__`, so I copy it over to the newly made object. I don't have experience with metaclasses, but I'm not sure it will help here. I already am using abc.ABCMeta, will I be able to use two metaclasses at once? All I really need is for the `__call__` method (which I add to a list subclass) to receive `self`, so I can keep state.
Longpoke
A: 

Your writing style is really hard to read. Normal sentences are half as long as yours :P

Do you want this or what?

__all__ = ['dec', 'A']

from time import time, sleep
import inspect

def dec(f):
    def new(self, *args, **kwargs):
        print self.initiated # print the time the objecte was initiated ...
        return f(self, *args, **kwargs) # Validate against definition so it doesn't go
                           # out of sync
    try:
        d = inspect.getsourcelines(f)
    except IOError:
        d = "<unable to fetch definition>"
    else:
        d = d[0][1].rstrip('\n').rstrip(':').lstrip(' ').lstrip('def')
    new.__doc__ = d + "\n" + (f.__doc__ or '')
    return new

class A(object):
    def __init__(self):
        self.initiated = time() # save the time the object was initiated


    @dec
    def f(self, x):
        """something"""
        print '%s.f(%s)' % (self, x)

if __name__ == '__main__':
    A().f(123)
    sleep(1)
    A().f(123)
    sleep(1)
    A().f(123)
THC4k
This will not work because subsequent calls to the decorated function will have different instances of `obj`. I only used `time()` to show that the same object is being referenced in my example. I guess I should have just used `id()`.
Longpoke
+1  A: 

It's a little difficult to figure out what you're after exactly. The list and __call__ stuff is confusing me so I'm mainly sticking with your first paragraph:

__all__ = ['dec', 'A']

from types import InstanceType
from functools import wraps
import inspect

def dec(func):

   #get the sig of the function
   sig = []
   @wraps(func)
   def wrapper(*args, **kwargs):
      ret = None
      #if this is a method belonging to an object...
      if args and getattr(args[0], func.__name__, None):
         instance, args = args[0], args[1:]
         #if sig of object is not already set
         if not hasattr(instance, "sig"):
            instance.sig = []
         ret = func(instance, *args, **kwargs)
         print "Sig of %s is %s" % (func.__name__, id(instance.sig))
      #else this is a function
      else:
         ret = func(*args, **kwargs)
         print "Sig of %s is %s" % (func.__name__, id(sig))
      return ret

   #modify the doc string
   try:
      docs = inspect.getsourcelines(func)
   except:
      docs = "<unable to fetch defintion>"
   else:
      docs = docs[0][1].rstrip('\n').rstrip(':').lstrip(' ').lstrip('def')
   wrapper.__doc__ = docs + "\n" + (func.__doc__ or '')
   return wrapper

class A(object):
   def __init__(self):
      super(A, self).__init__()

   @dec
   def f(self, x):
      """something"""
      print '%s.f(%s)' % (self, x)


@dec
def myfunc():
   print "myfunc"

@dec
def myfunc2():
   print "myfunc2"

@dec
def myfunc3():
   print "myfunc3"

if __name__ == "__main__":
   list = []
   for x in xrange(3):
      list.append(A())

   [a.f(123) for a in list]
   myfunc()
   myfunc()
   myfunc2()
   myfunc2()
   myfunc3()
   myfunc3()

Output:

<__main__.A object at 0x00B9F2D0>.f(123)
Sig of f is 11932616
<__main__.A object at 0x00B9F430>.f(123)
Sig of f is 11925464
<__main__.A object at 0x00B9F450>.f(123)
Sig of f is 11918112
myfunc
Sig of myfunc is 11925624
myfunc
Sig of myfunc is 11925624
myfunc2
Sig of myfunc2 is 11794592
myfunc2
Sig of myfunc2 is 11794592
myfunc3
Sig of myfunc3 is 11925144
myfunc3
Sig of myfunc3 is 11925144
manifest
When using time as the sig, the code ran so fast that the time was always the same...So I switched to using id but then I realized that won't properly demonstrate what is going on because the id of the func/instance will be the same even if this code isn't doing what it's suppose to.I settled on just creating a blank list and printing out the id of each instance of the "sig".
manifest
Heh, this almost works. The comparison of the first argument is rather risky though, and your code is breaking because isinstance(x, InstanceType) only returns True for instances of old-style classes. Removing that comparison fixes it, so each instance of A will print a different "sig" id. I'm trying to work this out with a nested decorator now, I'll close a reference to the class of the decorated method inside the decorated method so it can use that to compare.Also, you don't need to copy the doc anymore since you used `wraps`. Thanks for that one, saves me some code :)
Longpoke
Comparison of first argument is not risky due to the if args short-circuiting the if statement if args is []. Notice myfuncs do not have args but the code does not break. You are right about new-style breaking it, good to know. I only copied the doc because you were doing something funky with it. I've updated the code above for a working demonstration.
manifest
What I meant by "risky" was that the statement will yield True as long as a the class being checked has a method with the same name as the one that was decorated. What I meant in the first paragraphs was that I want a different instance of obj for each instance of the class that holds the decorated method. Sorry for not explaining the doc modifying code, it just takes the method signature and sticks it in the doc of the new object. It turns out I actually did need this.
Longpoke
A: 

Okay based on manifest's code, I've got a solution.

As you can see, pydoc will still have decent documentation about the decorated thing:

class A(__builtin__.object)
 |  Methods defined here:
 |  
 |  f(*args, **kwargs)
 |      f(self, x)
 |      something

As well, each instance of the class with the decorated method will have a different obj. Also functions each have their own obj.

 <__main__.A object at 0x7c209bee4a50>.f(123)
 obj of <__main__.A object at 0x7c209bee4a50>.f is 136479497243752
 <__main__.A object at 0x7c209bee4a10>.f(123)
 obj of <__main__.A object at 0x7c209bee4a10>.f is 136479497250720
 <__main__.A object at 0x7c209bee4a90>.f(123)
 obj of <__main__.A object at 0x7c209bee4a90>.f is 136479497446824
 myfunc
 obj of myfunc is 136479497243392
 myfunc
 obj of myfunc is 136479497243392
 myfunc2
 obj of myfunc2 is 136479497245688
 myfunc2
 obj of myfunc2 is 136479497245688
 myfunc3
 obj of myfunc3 is 136479497246408
 myfunc3
 obj of myfunc3 is 136479497246408

Here is the code:

__all__ = ['dec', 'A']

from functools import wraps
import inspect

def dec(cls=None):
    # cls will be closed in subdec
    def subdec(func):
        # closed in wrapper, guaranteed to be unique per decorator evaluation
        obj = []

        @wraps(func)
        def wrapper(*args, **kwargs):
            if (args and type(args[0]) == cls):
                instance = args[0]
                # We will attach to instance a dict _objs of
                # function_name:obj. This way we don't pollute the namespace
                # when decorating many functions.

                # Alternatively, you could make a dict external to instance
                # of instance:{function_name:obj}, but that takes more work
                # because you need to make it not prevent garbage collection
                # of instance.
                if not hasattr(instance, "_objs"):
                    instance._objs = {}
                if func.__name__ not in instance._objs:
                    instance._objs[func.__name__] = []
                func(*args, **kwargs) # This is only used to check the arity.
                                      # My real code is all to do with
                                      # manipulating obj.
                print "obj of %s.%s is %s" % (
                    instance,
                    func.__name__,
                    id(instance._objs[func.__name__])
                )
            else:
                # Functions are identified by the closed obj
                func(*args, **kwargs)
                print "obj of %s is %s" % (func.__name__, id(obj))

        # Find function/method signature and prepend it to the new object's doc
        try:
            doc = inspect.getsourcelines(func)
        except IOError:
            line = "<unable to fetch definition>"
        else:
            line = '@'
            i = 0
            while line.lstrip(' ').startswith("@"):
                try:
                    line = doc[0][i]
                except IndexError:
                    line = "<unable to fetch definition>"
                i += 1
        line = line.rstrip('\n').rstrip(':').lstrip(' ').lstrip('def')
        wrapper.__doc__ = line + "\n" + (func.__doc__ or '')

        return wrapper
    return subdec

class A(object):
    def f(self, x):
        """something"""
        print '%s.f(%s)' % (self, x)

A.f = dec(A)(A.f)

@dec()
def myfunc():
    print "myfunc"

@dec()
def myfunc2():
    print "myfunc2"

@dec()
def myfunc3():
    print "myfunc3"

if __name__ == "__main__":
    a, b, c = A(), A(), A()
    # a, b, and c each have their own instance of obj:
    a.f(123)
    b.f(123)
    c.f(123)
    myfunc()
    myfunc()
    myfunc2()
    myfunc2()
    myfunc3()
    myfunc3()

The added outer decorator, cls is just used to get the identity of the class, so the decorated function can tell for sure weather it is a function or method. I'm not sure how well this will go with [multiple] inheritance though... Maybe manifest's idea for checking that part is better.

Longpoke