如何避免使用全局变量作为装饰器的参数?

时间:2014-01-16 01:31:25

标签: python decorator python-decorators

我正在编写一个python脚本,我正在使用一个装饰器(重试是我正在使用的),它接受一个参数(尝试)。我希望参数可以从命令行参数配置。我可以弄清楚如何为装饰器设置参数的唯一方法是将我的参数读入全局变量。我从设计角度讨厌这个。它使得编写单元测试以及任何其他想要从我的脚本中导入任何函数的依赖于命令行参数都是相同的。

以下是我遇到的问题的一个愚蠢的例子:

import argparse
from functools import wraps

def get_args():
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--test_value', dest='test_value', required=True, default="sample value")
    args = parser.parse_args()
    return args
args = get_args()

def decorator_example(test_value):
    def deco_example(f):
        @wraps(f)
        def f_example(*args, **kwargs):
            print "The value I need is", test_value
            return f(*args, **kwargs) 
        return f_example 
    return deco_example

@decorator_example(args.test_value)
def test():
    print "running test"

if __name__ == '__main__':
    test()

如果有人能够想到一个更好的方法来做到这一点而不让args成为全球性的,请分享!我已经用尽互联网搜索更好的方法...我想在main中调用getargs()并根据需要传递参数....

5 个答案:

答案 0 :(得分:1)

将那些在导入时有用的内容与那些仅在作为脚本运行时相关的内容分开:

from functools import wraps

def decorator_example(test_value):
    def deco_example(f):
        @wraps(f)
        def f_example(*args, **kwargs):
            print "The value I need is", test_value
            return f(*args, **kwargs) 
        return f_example 
    return deco_example

def base_test():
    print "running test"

if __name__ == '__main__':
    import argparse

    def get_args():
        parser = argparse.ArgumentParser()
        parser.add_argument('-t', '--test_value', dest='test_value',
                            required=True, default="sample value")
        args = parser.parse_args()
        return args

    args = get_args()
    test = decorator_example(args.test_value)(base_test)
    test()

我认为全局变量的问题在这里是一个红色的鲱鱼。 常量全局变量没有任何问题。每次导入模块时,模块名称都是全局变量。每次定义函数时(在模块级别),函数名称都将变为全局变量。

仅当函数修改全局变量时才会出现问题。当发生这种情况时,理解依赖于全局的函数的行为会变得更加复杂。如果一系列函数都修改了相同的全局,那么您就不能再将每个函数理解为一个独立的单元。您必须同时了解所有功能以及它们与全局功能的交互方式。这可能很快变得复杂,这就是为什么这条路径经常导致意大利面条代码。

这就是为什么应该避免修改全局变量的原因。但是你没有在这里修改任何全局变量,所以我认为这不是问题。我对args.test_value的兴趣并不是它是全局的,而是模块代码与脚本代码之间没有足够的分离。 args.test_value属于脚本代码。

答案 1 :(得分:1)

我不认为装饰者在这里是合适的。因为你所面临的问题,一堂课似乎更合适。像这样:

class Test(object):
    def __init__(self, test_value):
        self.test_value = test_value

    def test(self):
        print "The value I need is", self.test_value
        self._inner_test()

    def _inner_test():
        print "running test"


if __name__ == '__main__':
    args = get_args()
    t = TestClass(args.test_value)
    t.test()

从你给出的例子来看,究竟如何构建这个类并不明确,这取决于你实际在做什么,但我认为这样的一些东西会为你提供一个更强大的解决方案,而不是试图把它变成装饰器。

类旨在维护状态并根据该状态提供修改后的行为。这就是你正在做的事情。您的应用程序具有修改其行为的状态。装饰器旨在围绕现有方法包装额外的静态功能。

但是,如果这不合适,另一种选择是简单地允许你的参数是全局的。有点像这样:

config.py

import argparse

test_value = None

def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--test_value', dest='test_value', required=True, default="sample value")
    args = parser.parse_args()
    return args

def configure():
    global test_value
    args = parse_args()
    test_value = args.test_value

main.py

from functools import wraps
import config

def decorator_example(f):
    @wraps(f)
    def f_example(*args, **kwargs):
        print "The value I need is", config.test_value
        return f(*args, **kwargs) 
    return f_example

@decorator_example
def test():
    print "running test"


if __name__ == '__main__':
    config.configure()
    test()

这个解决方案的一个不错的方面是它为您提供了一种明显的方法来补充您的参数与配置文件。请注意,这实际上应该有效,因为在调用config.test_value之前实际上并未读取test

答案 2 :(得分:0)

在“if name ”部分解析你的args并将它们作为arg传递给你的函数。这样,其他脚本可以为args指定值,而不是依赖于命令行参数。

答案 3 :(得分:0)

问题是,在定义要应用它的函数之后,才能定义装饰器。一种解决方法是推迟装饰函数,直到定义值为止,这反过来要求将它们存储在某处直到发生这种情况。 它还意味着一个全局变量将是必需的,并且它的使用与程序的其余部分隔离开来。以下是使用示例代码完成的操作:

from functools import wraps

class decorator_example(object):
    def __init__(self, f):
        global _funcs
        self.f = f
        try:
            _funcs.append(self)  # remember decorator_example instances
        except NameError:
            _funcs = [self]  # first one

    def __call__(self, *args, **kwargs):
        print 'running decoratored {}() function'.format(self.f.__name__)
        return self.f(*args, **kwargs)

def apply_decorator(deco, test_value):
    global _funcs
    for d in _funcs:
        print 'decorating function {}()'.format(d.f.__name__)
        d.f = deco(d.f, test_value)
    del _funcs  # no longer needed

@decorator_example
def test():
    print "running test"

def deco_example(f, test_value):
    @wraps(f)
    def f_example(*args, **kwargs):
        print "The value I need is", test_value
        return f(*args, **kwargs)
    return f_example

if __name__ == '__main__':
    import argparse

    def get_args():
        parser = argparse.ArgumentParser()
        parser.add_argument('-t', '--test_value', dest='test_value',
                            required=True, default="sample value")
        args = parser.parse_args()
        return args

    args = get_args()
    apply_decorator(deco_example, args.test_value)
    test()

答案 4 :(得分:-3)

如果我理解正确,可以使用类和成员变量来缓解全局变量的使用。但是在Python中,除非你仔细设计,否则你无法避免全局变量