【问题标题】:How to use decorator to bind a argument to a staticmethod function?如何使用装饰器将参数绑定到静态方法函数?
【发布时间】:2016-01-21 03:27:04
【问题描述】:

我的问题如下所示:

class foo(obj):

    def __init__(self, st='123'):
       self.st = st

    def process(self, x):
       self.st += x

    @staticmethod
    def do_foo(x, myfoo=None):
        if myfoo is None:
           myfoo = foo()
        myfoo.process(x)

def wrapper(fn, st):

    foo_func = foo(st)
    foo.do_foo = functools.partial(foo.do_foo, myfoo=foo_func)
    fn()
    print foo_func.st

    return wrap

@wrapper('stst')
def pro():
    foo.do_foo('double')

def pro2():
    foo.do_foo('double')

pro2()   # <--- normal foo.do_foo
pro()    # <--- partialed foo.do_foo
pro2()   # <--- partialed foo.do_foo

我想创建wrapper 装饰器来包装带有自定义foo 类的静态方法foo.do_foo,并且在pro() 执行之后,这个装饰器能够跨foo 对象做一些工作。即保存变量值。

在上层代码中,包装器如何在全局范围内永久更改foo.do_foo,而不仅仅是在装饰器范围内更改它。

那么如何让foo.do_foo 只在装饰器范围内改变而不是全局?

【问题讨论】:

    标签: python static-methods python-decorators


    【解决方案1】:

    根据您评论中链接到的gist.github.com code,这是一个略有不同的答案(这似乎是基于我的答案的第一个版本)。

    正如我最初所说,在我看来,您需要做的是将 wrapper() 设为 装饰器工厂 函数而不是装饰器本身 - 换句话说,使其成为创建和根据其参数返回一个装饰器。

    正如我在回复您的评论时提到的,问题在于 Prof.do staticmethod 本质上是一个全局变量,如果包装函数更改了它,这将影响对其的所有后续调用 - 这是您的根本原因问题。

    解决方法是让装饰器创建的wrapped() 函数在调用装饰函数之前保存Prof.do 的值,然后再将其恢复,因此更改只会影响对通过它进行的函数的调用。这可以防止它对Prof.do 所做的事情在可能创建的其他包装函数中搞乱对它的其他调用。它还可以防止它们的影响累积。

    我通过将静态方法放入contextmanager 辅助函数来封装更改和恢复静态方法。需要这样做的一个缺点是它增加了调用包装函数所涉及的开销。

    import contextlib
    import functools
    
    class Prof(object):
        def __init__(self, p='s'):
            self.p = p
    
        @staticmethod
        def do(x, obj=None):
            if obj is None:
                obj = Prof()
            obj.dprint(x)
            print
    
        def dprint(self, x):
            print self.p, x
            self.p += x
    
    def wrapper(st):
        @contextlib.contextmanager
        def prof_context(obj):  # could also be defined outside of wrapper function
            # save current staticmethod and replace it with partial below
            saved_method, Prof.do = Prof.do, functools.partial(Prof.do, obj=obj)
            yield
            # undo staticmethod modification
            Prof.do = staticmethod(saved_method)
    
        def decorator(fn):
            @functools.wraps(fn)
            def wrapped():
                obj = Prof(st)
                print 'current: obj.p is %r' % obj.p
                with prof_context(obj):
                    fn()
    
            return wrapped
    
        return decorator
    
    def do_p():
        Prof.do('do')
    
    @wrapper('do_p2')
    def do_p2():
        Prof.do('do2')
    
    print '#A do_p():'
    do_p()
    print '#B do_p2():'
    do_p2()
    print '#C do_p():'
    do_p()
    print '#D do_p2():'
    do_p2()
    print '#E do_p():'
    do_p()
    print '#F do_p():'
    do_p()
    

    输出:

    #A do_p():
    s do
    
    #B do_p2():
    current: obj.p is 'do_p2'
    do_p2 do2
    
    #C do_p():
    s do
    
    #D do_p2():
    current: obj.p is 'do_p2'
    do_p2 do2
    
    #E do_p():
    s do
    
    #F do_p():
    s do
    

    【讨论】:

    • 请查看此代码gist.github.com/tywtyw2002/c885bd917430e0563288,为什么C 打印do_p2do2 do 而不是s do。如果有装饰器用装饰器中的参数更改字符串,我想要这样的代码,没有装饰器的字符串应该提醒默认值。
    • 问题在于类及其静态方法实际上是全局变量,当您使用functools.partial() 更改后者时,所有后续调用都会使用最后分配的值。如果我想出一个解决方案,我会更新我的答案。
    • 查看我认为可以完成您想做的最新更新。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-29
    • 2011-04-03
    • 1970-01-01
    • 2018-06-07
    • 2021-04-24
    相关资源
    最近更新 更多