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

Python 如何避免使用全局变量作为装饰器的参数?,python,decorator,python-decorators,Python,Decorator,Python Decorators,我正在编写一个python脚本,其中使用了一个接受参数(trys)的装饰器(retry就是我正在使用的装饰器)。我希望参数可以通过命令行参数进行配置。我能弄清楚如何为装饰器设置参数的唯一方法是将参数读入全局变量。从设计的角度来看,我讨厌这个。它使得编写单元测试和其他任何希望从脚本导入函数的操作都依赖于命令行参数 下面是我遇到的问题的一个简单例子: import argparse from functools import wraps def get_args(): parser = a

我正在编写一个python脚本,其中使用了一个接受参数(trys)的装饰器(retry就是我正在使用的装饰器)。我希望参数可以通过命令行参数进行配置。我能弄清楚如何为装饰器设置参数的唯一方法是将参数读入全局变量。从设计的角度来看,我讨厌这个。它使得编写单元测试和其他任何希望从脚本导入函数的操作都依赖于命令行参数

下面是我遇到的问题的一个简单例子:

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()
,并根据需要传递参数……

在“ifname”部分解析参数,并将它们作为参数传递给函数。通过这种方式,其他脚本可以为args指定一个值,而不是依赖命令行参数。

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

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

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_值的不满不是因为它是全局的,而是模块代码和脚本代码之间没有足够的分离
args.test\u值属于脚本代码。

问题是,在定义要应用装饰器的函数之前,您无法定义装饰器。一种解决方法是在定义值之前推迟对函数的修饰,这反过来又要求在定义值之前将函数存储在某个位置。 这也意味着将暂时需要一个全局变量,并将其使用与程序的其余部分隔离开来。下面是如何使用示例代码实现这一点:

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

我认为这里不适合做装饰。正是因为您所面临的问题,一个类似乎更合适。大概是这样的:

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

这个解决方案的一个好的方面是,它为您提供了一种显而易见的方法,可以用配置文件来补充您的参数。请注意,这实际上应该是可行的,因为在调用
test
之前,不会真正读取
config.test\u值,全局变量都命名为
args
,有时表示函数的args,有时表示程序的
argparse
args……无论如何,
args
显然必须在使用它的任何地方都可以访问。如果您在全局函数的装饰器中使用它(就像您一样),那么它必须是全局函数。同样地,
test
必须是全局的,如果您想从顶层调用它。否则它怎么可能工作呢?在
f\u示例
的正文中测试值
?@abarnet:是的,args不是最好的名称;这两项都不是测试。这只是一个简单的示例代码。我的程序更长、更复杂。@abarnert这与jpmc26的第二个建议类似。。我要试试那样的东西。谢谢。这是我想做的,但它并不能解决我在装饰器中遇到的问题。我正在尝试使用重试装饰器进行指数退避;我只想尝试的次数是可配置的。我需要在许多类中使用decorator,因此类解决方案没有意义。您提供的另一个解决方案听起来可能会有所帮助。。。我会考虑的。谢谢@robinlmorris 5年太晚了,我知道,但我突然想到,如果类接受另一个函数作为初始值设定项参数调用,那么该类将工作得很好。手动应用decorator会使我的部分代码了解decorator(我不希望这样)。不过谢谢。手动应用decorator会使我的部分代码了解decorator(这是我不想要的)。我明白你的意思。。。我应该将特定于“脚本”的代码部分移动到另一个文件中。但是,如果我的decorator需要一个全局参数,它必须将一些代码从脚本文件导入到