In Python, I have a decorator that has to skip any real work if a function is defined locally in the one that calls it. I made a simple testing script:
def fn1():
# @my_decorator will be here
def fn2():
pass
print(fn2)
return fn2
x = fn1()
print(x)
print(x.__module__)
It prints this:
<function fn1.<locals>.fn2 at 0x7fd61bdf3ae8>
<function fn1.<locals>.fn2 at 0x7fd61bdf3ae8>
__main__
As I see, Python sees that the function is defined in a local space (<locals>
in the printed text), but I can't see how I can find that bit of data. I walked through the inspect
module, and don't see anything similar.
I can't rely on whether the function is in globals or not.
What do I use?
First off, the direct approach is to check if the CO_NESTED
flag is set on the function's code object:
import inspect
...
def is_nested(func):
return func.__code__.co_flags & inspect.CO_NESTED
def deco(func):
if is_nested(func):
# This is a nested function, return it unchanged
return func
... otherwise, do your decoration here ...
That said, there is another approach if what you care about is whether you've actually closed over anything. A function that doesn't use anything from the enclosing scope is nested, but not a closure, and that distinction is often important. So for example:
def foo(x):
def bar(y):
pass
return bar
is not making a closure because bar
makes use of no variables from the scope of the foo
call. By contrast, even though it's a garbage reference, this is making a closure simply by reading the value of x
from the enclosing scope:
def foo(x):
def baz(y):
x
return baz
You can tell the difference between bar
and baz
by testing the __closure__
attribute (which is None
if no nested variables have been closed over) or by checking the co_freevars
attribute of the __code__
object (which is a tuple of names closed on, so if it's empty, then it's not a closure, though it may still be a nested function):
def is_closure(func):
return func.__closure__ is not None
# Or using documented names, since __closure__ isn't for some reason,
# co_freevars is a tuple of names captured from nested scope
return bool(func.__code__.co_freevars)
# Or on 3.3+, you even get a function to aid you:
return bool(inspect.getclosurevars(func).nonlocals)
Well, here's a hacky approach:
'<locals>' in f.__qualname__
It seems brittle to me, though.
Another approach is to play with the Frame
, but I like that even less, I think:
In [1]: import inspect
In [2]: def deco(f):
...: try:
...: frame = inspect.currentframe()
...: print(frame.f_back.f_locals is globals())
...: finally:
...: del frame
...: return f
...:
In [3]: @deco
...: def g(): pass
...:
True
In [4]: def f():
...: @deco
...: def g(): pass
...:
In [5]: f()
False
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With