【问题标题】:python, confused in decorate and closurepython,在装饰和关闭中感到困惑
【发布时间】:2012-09-06 02:31:56
【问题描述】:

我有一些测试代码:

定义数(数): 定义装饰(功能): def wrap(*args, **kwargs): 输入的_num = num 返回函数(*args,**kwargs) 返回包装 返回装饰 @数字(5) 定义测试(一): 返回一个 + input_num 打印测试(1)

运行此代码时,我得到一个错误,表明 'inputed_num' is not defined

我的问题是: 在 wrap 函数中,是否没有 func 可以得到 'inputed_num' 的闭包?

无论如何,如果没有,我该怎么做才能达到我的目的:初始化一些值,并直接在主函数中使用这个值。

思考。

【问题讨论】:

    标签: python closures decorator


    【解决方案1】:

    不,没有这样的闭包。函数可以关闭存在于周围 lexical 上下文中的变量,而不是调用上下文中的变量。换句话说,如果你真的在另一个函数中编写了一个函数,那么内部函数可以访问外部函数中的变量:

    def f():
        g = 2
        def f2():
            print g
        f2()
    

    但函数永远无法访问调用它们的函数内部的变量。

    一般来说,没有办法做你想做的事,即从函数外部在函数中设置任意变量。最接近的是您可以在装饰器中使用global inputed_num 将inputed_num 分配为全局变量。然后test 将访问全局值。

    def num(num):
        def deco(func):
            def wrap(*args, **kwargs):
                global outsider
                outsider = num
                return func(*args, **kwargs)
            return wrap
        return deco
    @num(5)
    def test(a):
        print a+outsider
    
    >>> test(2)
    7
    

    当然,变量设置是全局的,因此多个并发使用(例如递归)将不起作用。 (只是为了好玩,您还可以看到 here 了解一种非常神秘的方法,但它太疯狂了,无法在现实世界中使用。)

    【讨论】:

    • 认为,正如你所提到的,这段代码确实是并发运行的。 :)
    • 使用一个全局变量在多个不同的函数之间进行通信远非最佳实践。除非没有其他方法,否则我不推荐它。
    【解决方案2】:

    我的问题是:在 wrap 函数中,没有闭包可以让 func 得到 'inputed_num' 吗?

    抱歉,装饰器不是这样工作的。它们在函数最初定义后被应用。到那时,为时已晚。

    当你写作时:

    @num(5)
    def test(a):
        return a + inputed_num
    

    相当于:

    def test(a):
        return a + inputed_num
    
    test = num(5)(test)       # note that num(5) is called after test() is defined.
    

    为了实现您的目标,让 inputed_num 成为 test 的第一个参数。然后,让您的装饰器传入该参数:

    def num(num):
        def deco(func):
            def wrap(*args, **kwargs):
                inputed_num = num
                return func(inputed_num, *args, **kwargs)  # this line changed
            return wrap
        return deco
    
    @num(5)
    def test(inputed_num, a):                              # this line changed
        return a + inputed_num
    
    @num(6)
    def test2(inputed_num, a):
        return a + inputed_num
    
    print test(10)   # outputs 15
    print test2(10)  # outputs 16
    

    希望能为您解决一切问题 :-)

    【讨论】:

    • 仅仅因为装饰器在函数定义后应用并不一定意味着为时已晚。在实际调用函数之前不会引发错误,并且由于调用的是修饰函数,因此它可以操纵全局变量以改变调用原始函数时的行为。
    • OP 没有要求全局变量——这会有其自身的问题(特别是,如果他多次使用装饰器)。相反,他想要一个闭包(也就是一个单元变量)。 Python 的语义要求在最初定义 test() 时静态引用单元变量(词法范围)。当装饰器随后运行时,创建单元变量确实为时已晚。
    • 想。阅读您的帖子后,我理解了我的错误。 但是我不想对主函数做一些修改
    • @RaymondHettinger:好吧,OP 确实使用了“闭包”这个词,但帖子的结尾表明他更普遍地寻找一种从外部修改函数变量的方法,即通过关闭或通过其他方式。
    • @RaymondHettinger 抱歉,还是有问题。最后,我在return func(*args, **kwargs) 之前添加了以下代码:if args[0] == 1: return 0 然后,我调用test(1),结果为0。这表明装饰已调用并运行之前调用的目标函数。对吗?
    【解决方案3】:

    正如@Raymond 所说 - 在定义函数后应用装饰器。这意味着,在编译函数体本身时,Pythn 会看到 inputed_num 变量,并且当我使用本地定义的变量时,它会生成代码以尝试将其作为全局变量来访问。

    这意味着你可以在你的装饰器中解决它: 您的装饰器可以在函数 globals() 空间中设置一个具有所需名称的全局变量,然后调用该函数。它应该在单线程代码中可靠地工作:

    def num(num):
        def deco(func):
            def wrap(*args, **kwargs):
                glob = func.func_globals
                marker = object()
                original_value = glob.get("inputed_num", marker)
                glob["inputed_num"] = num
                result = func(*args, **kwargs)
                if original_value is marker:
                    del glob["inputed_num"]
                else:
                    glob["inputed_num"] = original_value
                return result
            return wrap
        return deco
    
    
    @num(5)
    def test(a):
        return a + inputed_num
    

    和:

    >>> print test(1)
    6
    

    【讨论】:

    • 酷!但可能不符合我的需求。这种装饰将提供不同的功能,并且运行一致。认为:)
    【解决方案4】:

    这不是decorator应该使用的方式,我想你的目的可能是用这个来完成的

    def outwrapper(n):
        def wrapper(func):
            def f(*args, **argw):
                return n + func(*args, **argw) 
            return f
        return wrapper 
    
    
    @outwrapper(4)
    def test(n):
        return n
    
    
    print test(1) 
    

    【讨论】:

    • 不是这个代码只是一个例子。不过还是谢谢:)
    猜你喜欢
    • 1970-01-01
    • 2020-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-01
    • 1970-01-01
    • 2018-09-02
    • 2017-02-28
    相关资源
    最近更新 更多