6

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.

asked Sep 13, 2012 at 3:08
0

4 Answers 4

7

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 (e.g., 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.)

answered Sep 13, 2012 at 3:16
Sign up to request clarification or add additional context in comments.

2 Comments

Thinks, As you mentioned, This code indeed running concurrent. :)
Using one global variable to communicate between multiple distinct functions is far from being a best practice. I don't recommend it unless nothing else will work.
7

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

answered Sep 13, 2012 at 3:12

5 Comments

Just because the decorator applies after the function is defined doesn't necessarily mean it's too late. The error isn't raised until the function is actually called, and since it's the decorated one that's called, it could manipulate global variables to alter the behavior of the original function when it is called.
The OP wasn't asking for a global variable -- that would have its own issues (especially, if he used the decorator more than once). Instead, he wanted a closure (a.k.a a cell variable). The semantics of Python require cell-variables be statically referenced (lexically scoped) at the time test() is initially defined. When the decorator runs afterwards, it really is too late to create a cell-variable.
Thinks. I understood my error after reading your post. But I don't wanna do some modify of the main func
@RaymondHettinger: Well, it's true the OP used the word "closure", but the end of the post indicates he's more generally looking for a way to modify a function's variables from outside, be that by a closure or by other means.
@RaymondHettinger Sorry, still problem. in wrap, i added this code: if args[0] == 1: return 0 before return func(*args, **kwargs) then, i call test(1), the result is 0. This seams that the decorate has call and running before the target function called. Right?
4

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
answered Sep 13, 2012 at 3:22

1 Comment

Cool! But maybe not meets my needs. This decorate will serve different functions, and running concorrent. Thinks :)
1

It's not the way decorator should be used, I think your purpose may be done with this

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) 
answered Sep 13, 2012 at 3:20

1 Comment

Not this The code is just a example. But still thanks :)

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.