Question

In trying to create a Python function similar to Sage's var() or function(), I've encountered an apparently not-so-trivial problem in Python. Essentially, calling var('x') in Sage not only returns a Sage symbolic expression, but does the equivalent of x = SR.var('x'), i.e. it assigns the expression object to a variable in the current global namespace (the namespace of the calling module).

My question is, how the heck does it do that? If I do something like this:

in B.py:

def func():
    globals()['x'] = something

in A.py

from B import func
func()

I can only affect the variables in module B's global namespace, not the calling module A's global namespace.

Yet, the file var.pyx distributed with my version of Sage looks like this:

...

def var(*args, **kwds):
    if len(args)==1:
        name = args[0]
    else:
        name = args
    G = globals()  # this is the reason the code must be in Cython.
    if 'ns' in kwds:
        # ...
        # not relevant
    v = SR.var(name, **kwds)
    if isinstance(v, tuple):
        for x in v:
            G[repr(x)] = x
    else:
        G[repr(v)] = v
    return v

...

In particular, the comment about Cython seems intriguing. I don't know much about Cython, so maybe that's my problem. If this is some special aspect of Cython, how would one go about replicating this function in "regular Python"/CPython?

PS: Yes, I realize that in general, such behavior is a bad idea. I'm mainly asking out of curiosity.

Was it helpful?

Solution

The explanation was found in https://groups.google.com/d/topic/sage-devel/J-kDHlnT4/discussion

I quote Volker Braun:

In src/setup.py we set

Cython.Compiler.Options.old_style_globals = True

which causes Cython to fall back to the old behavior.

OTHER TIPS

Looking at Cython 1.5's changelog, we can see that

globals() now returns a read-only dict of the Cython module's globals, rather than the globals of the first non-Cython module in the stack

Therefore this is a trick that only works on really old Cython compilers.

You can use this code to emulate it:

import inspect

def run():
    outer_frame = inspect.stack()[1][0]
    outer_frame_locals = inspect.getargvalues(outer_frame).locals

    outer_frame_locals["new_variable"] = "I am new"

although note that it is very implementation-defined.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top