Reputation: 44525
Given
I have a function, scope
, which can be declared inside an enclosing namespace, i.e. a module, class or function. Variables of interest are located immediately outside this function.
How do I generically access the variables/parameters and their values declared within any enclosing namespace?
Example
Sample pseudo-code for capturing function variables (b
) and parameters (c
):
a = 1
def func(c=3)
b = 2
def scope():
return ...
return scope()
Expected Output
func()
# {'c': 3, 'b': 2}
Attempts
I've had moderate success at the module-level, e.g. a = 1
:
# module-level
a = 1
def scope():
return {k: v for k, v in globals().items() if not k.startswith("_")}
scope()
# {'a': 1, ...}
I can also access class attributes from methods, e.g. b = 2
:
# class-level
a = 1
class Klass:
b = 2
def scope(self):
obj = self.__class__
return obj.__dict__
Klass().scope()
# {'b': 2, ...}
I can only partially access variables and parameters inside an enclosing function:
# function-level
a = 1
def func(c=3):
b = 2
def scope():
obj = func
return obj.__code__.co_varnames
return scope()
func()
# ('c', 'b', 'scope')
While __code__.co_varnames
successfully gives the enclosing variables (excluding a
), I am interested the values as well (e.g. {'c': 3, 'b': 2}
).
I have made many unmentioned attempts including inspect
functions, other code object methods, dir()
, and object special methods. My preference is to implement more generic and idiomatic code for detecting variables in an enclosing namespace, although any advice is appreciated.
I am also aware of Python idioms and the nature of this question. I am still intrigued by its possibilities, and I thank anyone willing to stretch beyond the norm.
Upvotes: 1
Views: 1224
Reputation: 9994
It's kinda cheating, but
def scope(outer=locals()):
return outer
works. (It's cheating because the default argument is evaluated at definition time by the defining code, thus locals()
is run in the enclosing scope and thus it's not actually the scope
function reaching out to the enclosing scope.)
Note that the directory returned by locals()
may or may not change when the corresponding scope is modified after the call. If you want consistent behavior across Python implementations (or even across your different use-cases), make a deep copy either in the default argument or in the body of the scope
function.
# module-level
a = 1
def scope(outer=locals()):
return outer
e = 5
result = scope()
f = 6
public_result = {k: v for k, v in result.items() if not k.startswith('_')}
assert (
# allowed:
public_result == dict(a=1)
or
# C-Python 3.6 behavior:
public_result == dict(a=1, scope=scope, e=5, result=result, f=6)
)
# class-level
a = 1
class Klass:
b = 2
@staticmethod
def _scope(outer=locals()): # Hidden from public result, because I have
return outer # no idea how to refer to it from the outside.
d = 4
e = 5
result = Klass._scope()
f = 6
public_result = {k: v for k, v in result.items() if not k.startswith('_')}
assert (
# allowed:
public_result == dict(b=2)
or
# CPython 3.6 behavior:
public_result == dict(b=2, d=4) # would also contain `scope` if it wasn't hidden
)
# function-level
a = 1
def func(c=3):
b = 2
def scope(outer=locals()):
return outer
return scope(), scope
d = 4
e = 5
result, scope_fu = func()
f = 6
assert (
# C-Python 3.6 behaviour:
result == dict(b=2, c=3)
or
# also allowed:
result == dict(b=2, c=3, scope=scope_fu, d=4)
)
Upvotes: 2
Reputation: 1648
While python will give you ways to do this, you REALLY don't want to. Function/classes/etc should not expose their internals to the calling code, as that breaks abstraction and makes code fragile. Functions should take in arguments and return output values, but the internal algorithm and especially variable names shouldn't be exposed.
Upvotes: 2