Reputation: 1284
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.
Upvotes: 4
Views: 734
Reputation: 2675
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.
Upvotes: 1
Reputation: 60167
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.
Upvotes: 0