Created
February 8, 2016 09:16
-
-
Save keturn/771609dcbcfa1ea9fddc to your computer and use it in GitHub Desktop.
Nested scope, but not nested visually?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Can we have a "nested function", but have definition of the inner function not lexically within its parent? | |
Question by https://www.reddit.com/r/compsci/comments/44o18l/do_any_programming_languages_allow_you_to_give/ | |
This file was written as an intellectual exercise. I do not recommend using it. Ever. | |
The implementation below works by using the descriptor protocol to return a newly bound method every time the outer | |
function makes a new reference to the inner function, and uses the outer method's *locals* as the inner method's | |
*globals*. The primary disadvantage here is that means the inner method doesn't have access to the actual globals. | |
There are a number of variations on this approach, but I've run into limitations with each of them. The primary | |
restrictions seem to be: | |
1) the only way to make the references used by a closure are to define a function inside the enclosing scope. | |
(which is what we're trying to get away without.) | |
2) the "globals" object used by a code block must be a standard dictionary object, which means we can't override its | |
__getitem__ to make a superset of the outer scope's locals and globals. | |
""" | |
import inspect | |
from types import FunctionType | |
from unittest import TestCase | |
some_module_global = "a global value" | |
class TestScope(TestCase): | |
def test_nosy_sees_local_vars(self): | |
obj = HorribleClass() | |
obj.outer_with_only_locals("applesauce") | |
self.assertEqual( | |
{ | |
'arg1': "applesauce", | |
'arg2': 'default of arg2', | |
'local1': 'thiiiings', | |
'inner_arg': 'chips' | |
}, | |
obj.collection[0]) | |
def test_nosy_also_sees_global_vars(self): | |
obj = HorribleClass() | |
obj.outer_also_uses_global() | |
self.assertEqual( | |
{ | |
'local2': 'stuuuff', | |
'global': some_module_global | |
}, | |
obj.collection[0]) | |
class NosyScope(object): | |
def __init__(self, func): | |
self.func = func | |
def __get__(self, instance, objtype=None): | |
calling_locals = inspect.currentframe().f_back.f_locals | |
new_func = FunctionType(self.func.func_code, calling_locals, self.func.func_name, self.func.func_defaults) | |
return new_func.__get__(instance, objtype) | |
nosy_scope = NosyScope | |
class HorribleClass(object): | |
def __init__(self): | |
self.collection = [] | |
def outer_with_only_locals(self, arg1, arg2="default of arg2"): | |
local1 = "thiiiings" | |
self.inner_with_only_locals("chips") | |
# noinspection PyUnresolvedReferences | |
@nosy_scope | |
def inner_with_only_locals(self, inner_arg): | |
self.collection.append({ | |
'arg1': arg1, | |
'arg2': arg2, | |
'local1': local1, | |
'inner_arg': inner_arg | |
}) | |
def outer_also_uses_global(self): | |
local2 = 'stuuuff' | |
self.inner_with_globals_too() | |
# noinspection PyUnresolvedReferences | |
@nosy_scope | |
def inner_with_globals_too(self): | |
self.collection.append({ | |
'local2': local2, | |
'global': some_module_global, | |
}) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment