带有实例化变量的Python装饰器?

0 投票
4 回答
3234 浏览
提问于 2025-04-15 17:40

我想做一个装饰器,它可以创建一个新的函数或方法,并使用一个对象 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 甚至没有被定义,因为它们并不使用自己的实例变量(我真正装饰的只是一些事件,这些事件会被外部对象订阅,事件有文档说明)。

补充:其实,如果有办法让一个 list 的子类实例绑定到一个实例上,这样它的 __call__ 属性就能隐式地接收到类实例(就像普通实例方法那样),那将是一个完美的解决方案,这也是我最初想要弄明白的。不过,也许还有更好的解决办法,这样我就不需要用 self 属性来定义被装饰的方法了?这两种方法都可以。

4 个回答

0

你的写作风格真让人难以阅读。普通的句子长度只有你的一半 :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)
1

要搞清楚你到底想要什么,有点困难。你提到的列表和__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
2

因为装饰器其实就是一种语法上的简化,意思是说

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)

撰写回答