Skip to content

Instantly share code, notes, and snippets.

@keturn
Created February 8, 2016 09:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save keturn/771609dcbcfa1ea9fddc to your computer and use it in GitHub Desktop.
Save keturn/771609dcbcfa1ea9fddc to your computer and use it in GitHub Desktop.
Nested scope, but not nested visually?
"""
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