【问题标题】:Python decorator with instantiation-time variable?具有实例化时间变量的 Python 装饰器?
【发布时间】:2010-01-05 17:19:05
【问题描述】:

我想创建一个装饰器来创建一个使用对象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的方法,如果它不存在,然后使用@ 987654330@。然后,如果传递给装饰器的对象没有 self 参数,只需在装饰器内实例化 obj 并返回一个使用它的函数。

但是...我所说的对我来说并不真正有效,因为在我真正的装饰器中,我返回一个派生自 list 并具有 __call__ 属性的对象。此外,在真正的装饰器中,self 甚至没有在被它装饰的对象中定义,因为它们没有使用它们的实例变量(我 真正 装饰的只是事件被外部对象订阅,事件有记录的签名)。

编辑:实际上,如果有办法让 list 子类实例绑定到一个实例,以便它的 __call__ 属性隐式接收类实例(就像在任何普通实例方法中一样),这将是一个完美的选择解决方案,这就是我最初想弄清楚的方法。但也许有一个更好的解决方案,这样我就不必使用 self 属性定义装饰方法?两者都是完美的。

【问题讨论】:

    标签: python decorator


    【解决方案1】:

    因为装饰器只是语法糖

    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)
    

    【讨论】:

    • 我明确表示这不是我想要的。我的装饰器更改了被装饰函数的文档,如果你把装饰放在__init__中就不会发生这种情况。
    • @Longpoke:为什么__doc__ 如此重要?为什么不使用元类来生成 __doc__ 的部分内容
    • 装饰器将函数转换为list的子类,并为其添加__call__。这会破坏__doc__,所以我将它复制到新创建的对象中。我没有使用元类的经验,但我不确定它在这里会有所帮助。我已经在使用 abc.ABCMeta,我可以同时使用两个元类吗?我真正需要的是__call__ 方法(我添加到列表子类)接收self,所以我可以保持状态。
    【解决方案2】:

    要弄清楚你到底在追求什么有点困难。清单和 __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
    

    【讨论】:

    • 当使用时间作为 sig 时,代码运行得如此之快以至于时间总是一样的......所以我改用 id 但后来我意识到这不能正确地演示发生了什么因为 func/instance 的 id 将是相同的,即使这段代码没有做它应该做的事情。我决定只创建一个空白列表并打印出每个“sig”实例的 id。
    • 嘿,这几乎行得通。但是,第一个参数的比较是相当冒险的,并且您的代码正在破坏,因为 isinstance(x, InstanceType) 仅对旧式类的实例返回 True。删除该比较可以修复它,因此 A 的每个实例都将打印一个不同的“sig”id。我现在正在尝试使用嵌套装饰器来解决这个问题,我将关闭对装饰方法内的装饰方法类的引用,以便它可以使用它进行比较。此外,由于您使用了wraps,因此您不再需要复制文档。谢谢你,为我节省了一些代码:)
    • 第一个参数的比较没有风险,因为如果 args 是 [],则 if args 会使 if 语句短路。请注意 myfuncs 没有参数,但代码不会中断。你是对的关于新式打破它,很高兴知道。我只是复制了文档,因为你正在用它做一些时髦的事情。我已经更新了上面的代码以进行工作演示。
    • 我所说的“冒险”的意思是,只要被检查的类有一个与被修饰的类同名的方法,该语句就会产生 True。我在第一段中的意思是,我希望每个包含装饰方法的类实例都有一个不同的 obj 实例。抱歉没有解释文档修改代码,它只是将方法签名并粘贴在新对象的文档中。事实证明我确实需要这个。
    【解决方案3】:

    你的写作风格真的很难读。正常的句子是你的一半: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)
    

    【讨论】:

    • 这将不起作用,因为对装饰函数的后续调用将具有不同的 obj 实例。我只使用time() 来表明在我的示例中引用了同一个对象。我想我应该使用id()
    【解决方案4】:

    好的,根据清单的代码,我有一个解决方案。

    如您所见,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 仅用于获取类的标识,因此被装饰的函数可以确定它是函数还是方法。不过,我不确定这与 [多重] 继承的效果如何……也许 manifest 的检查该部分的想法更好。

    【讨论】:

      猜你喜欢
      • 2020-07-08
      • 2017-07-26
      • 2021-08-20
      • 1970-01-01
      • 2021-10-10
      • 2015-04-18
      • 2021-05-15
      • 2021-05-24
      • 2021-08-22
      相关资源
      最近更新 更多