[Python-ideas] Allow function __globals__ to be arbitrary mapping not just dict
Terry Reedy
tjreedy at udel.edu
Sun Mar 18 18:26:02 CET 2012
On 3/18/2012 8:27 AM, Steven D'Aprano wrote:
> Currently, if you try to construct a function from parts, the mapping
> that becomes func.__globals__ must be an actual dict:
>
>
> py> class Mapping:
> ... def __getitem__(self, key):
> ... if key == 'y':
> ... return 42
> ... raise KeyError(key)
> ...
> py> from types import FunctionType
> py> f = lambda x: x + y
> py> g = FunctionType(f.__code__, Mapping(), 'g')
> Traceback (most recent call last):
> File "<stdin>", line 1, in <module>
> TypeError: function() argument 2 must be dict, not Mapping
The API of internal classes is intentionally not documented in the types
module doc. I take this to mean that the api, if not the existence, of
such classes, is implementation specific. Hence any such call is a
'cpython' rather than generic python call.
Someone pointed out on python-list that FunctionType checks and rejects
non-dict second args because the CPython ceval loop code for CPython
LOAD_GLOBAL and STORE_GLOBAL opcodes directly access dicts rather than
using the generic mapping interface.
> I propose to allow function.__globals__ to accept any mapping type.
The important question is whether the current ceval code in merely a
holdover from ancient days when dict was the only mapping type and the
user function type was not accessible to users, or whether the direct
dict access is an important speed optimization that affects essentially
all code run on cpython. If not done already, experiments are needed to
assess the degree of slowdown.
Something to keep in mind: LOAD_GLOBAL is *also* used to access builtins
that are not in globals() itself via globals['__builtins__']. So not
just any mapping will work as a replacement. See 'Alternative proposal'
below.
> That, plus the new collections.ChainMap class in Python 3.3, would allow
> some interesting experiments with namespaces and scoping rules.
>
> E.g. if I want to write a function with a custom namespace, I have to do
> something like this:
>
> ns = ChainMap( ... ) # set up a namespace
> def func(a, ns=ns):
> x = a + ns['b']
> y = ns['some_func'](ns['c'])
> z = ns['another_func'](x, y)
> ns['d'] = (x, y, z)
> return ns['one_last_thing'](d)
>
> which is not a very natural way of writing code. But if we could use
> non-dict mappings as __globals__, I could write that function like this:
>
> ns = ChainMap( ... ) # set up a namespace
> def func(a):
> global d
> x = a + b
> y = some_func(c)
> z = another_func(x, y)
> d = (x, y, z)
> return one_last_thing(d)
>
>
> # This could be a decorator.
> func = FunctionType(func.__code__, ns, func.__name__)
>
> (By the way, ChainMap is only one possible example namespace.)
Alternative proposal: write a function to replace LOAD_GLOBAL and
STORE_GLOBAL for non-builtin names with the opcodes to access a mapping
passed in as an arg to the rewrite function or the function itself. The
latter would be perhaps easier since the name of the replacement mapping
would already be in the code object.
def f(a, _globals = {}):pass
f = reglobalize(f) # assume '_globals' is the replacement
# now call f with whatever 'global' dict you want on a per-call basis.
--
Terry Jan Reedy
More information about the Python-ideas
mailing list