Python декоратор с переменной времени создания? - PullRequest
0 голосов

Я хочу создать декоратор, который создает новую функцию / метод, который использует объект obj. Если декорированный объект является функцией, то при создании функции необходимо создать экземпляр obj. Если декорированный объект является методом, новый экземпляр obj должен быть создан и привязан к каждому экземпляру класса, чей метод декорирован. Я не могу поместить украшение в __init__, потому что декоратор изменяет документацию по функциям. У меня сейчас что-то вроде этого, но оно создается только time один раз, а это не то, что я хочу:

__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)

Моя идея решить эту проблему - проверить, принимает ли объект, передаваемый декоратору, аргумент self, если так, вернуть метод, который связывает obj с self, если его еще нет, и затем использовать self.obj. И затем, если нет аргумента self для объекта, переданного декоратору, просто создайте экземпляр obj внутри декоратора и верните функцию, которая использует это.

Однако ... то, что я сказал, на самом деле не работает для меня, потому что в моем реальном декораторе я возвращаю объект, полученный из list и имеющий атрибут __call__. Более того, в реальном декораторе self даже не определено в объектах, которые им декорируются, потому что они не используют свои переменные экземпляра (то, что я действительно украшаю, это просто события, которые нужно подписанные внешними объектами, события имеют задокументированные подписи).

Edit: На самом деле, если есть способ заставить экземпляр подкласса list связываться с экземпляром, чтобы его атрибут __call__ неявно получал экземпляр класса (как в любом обычном методе экземпляра), это было бы идеально Решение, это было то, что я изначально пытался выяснить, как это сделать. Но, может быть, есть еще лучшее решение, так что мне не нужно определять украшенные методы с атрибутом self? Либо идеально.

Ответы [ 4 ]

2 голосов
/ 05 января 2010

Поскольку декоратор - это просто синтаксический сахар для высказывания

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

Почему бы не сделать это в конструкторе объектов?

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)
1 голос
/ 05 января 2010

Немного трудно понять, что именно вы ищете. Список и __call__ сбивают меня с толку, поэтому я в основном придерживаюсь вашего первого абзаца:

__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()

Выход:

<__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
0 голосов

Хорошо, основываясь на коде манифеста, у меня есть решение.

Как видите, у pydoc все еще будет приличная документация по украшенной вещице:

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

Кроме того, каждый экземпляр класса с декорированным методом будет иметь различный obj. Также у каждой функции есть свои 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

Вот код:

__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()

Добавленный внешний декоратор, cls просто используется для получения идентификатора класса, поэтому декорированная функция может точно сказать, что это функция или метод. Я не уверен, насколько хорошо это пойдет с [множественным] наследованием, хотя ... Возможно, идея манифеста для проверки этой части лучше.

0 голосов
/ 05 января 2010

Ваш стиль письма действительно трудно читать. Нормальные предложения в два раза длиннее ваших: P

Хочешь это или что?

__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)
...