python, confused in decorate and closure

I have some test code:

def num(num):
    def deco(func):
        def wrap(*args, **kwargs):
            inputed_num = num
            return func(*args, **kwargs)
        return wrap
    return deco


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

print test(1)

when run this code, I got an error shows that 'inputed_num' is not defined

My question is: In wrap function, is there not a closure that func can got 'inputed_num' ?

Anyway, If not, how should I do to got my aim: Initialize some value, and use this value directly in the main function.

Thinks.


No, there isn't a closure like that. Functions can close over variables that are present in the surrounding lexical context, not in the calling context. In other words, if you actually write one function in another, then the inner one can have access to variables in the outer one:

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

But functions never have access to variables inside the function that called them.

In general there isn't a way to do what you want, viz., set an arbitrary variable in a function from outside the function. The closest thing is you could use a global inputed_num in your decorator to assign inputed_num as a global variable. Then test would access the global value.

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

But of course the variable setting is then global, so multiple concurrent uses (eg, recursion) wouldn't work. (Just for fun, you can also see here for a very arcane way to do this, but it is way too crazy to be useful in a real-world context.)


My question is: In wrap function, is there not a closure that func can got 'inputed_num' ?

Sorry, that's not the way decorators work. They get applied after the function is initially defined. By then, it's too late.

When you write:

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

That is the equivalent of:

def test(a):
    return a + inputed_num

test = num(5)(test)       # note that num(5) is called after test() is defined.

To achieve your goal, let inputed_num be the first argument to test. Then, have your decorator pass in that argument:

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

Hope that clears everything up for you :-)


As @Raymond puts it - decorators are applied after the function is defined. Which means that while compiling the function body itself, Pythn sees the inputed_num variable, and as i it snod a localy defined variable, it generates code to try access it a as a global variable instead.

Which means you can make a work-around for it in your decorator: Your decorator can set a global variable with the desired name in the function globals() space, and then call the function. It should work reliably in single-threaded code:

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

and:

>>> print test(1)
6
链接地址: http://www.djcxy.com/p/28564.html

上一篇: 类方法的python装饰器

下一篇: python,在装饰和关闭时感到困惑