[Cython] Bug with inlined functions that access globals

Erik Bray erik.m.bray at gmail.com
Fri Nov 24 07:44:04 EST 2017


Hi,

I think maybe I've seen this brought up once or twice before in the
past, but with no real discussion.  There is a slightly unpythonic
problem with using cpdef inline functions defined in a .pxd file that
happen to access global variables.  For example:

$ cat foo.pxd
FOO_A = 1

cpdef inline foo():
    return FOO_A

$ cat bar.pyx
from foo cimport foo

print(foo())

Running this like python -c 'import bar' results in:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "bar.pyx", line 3, in init bar
    print(foo())
  File "foo.pxd", line 4, in foo.foo
    return FOO_A
NameError: name 'FOO_A' is not defined

Of course, if the function "foo" were not inlined this would work
fine.  But the inlining really breaks things, I think, in an
unpythonic way.  I could be misunderstanding how Cython's "inline"
should be interpreted but I feel like it's an implementation detail
that should not fundamentally change how the code in that function
works.

There are a couple issues here.  First of all, if a .pxd file exists
but not a .pyx, no actual module will be created.  This is by design
of course, but in this case either, inlined functions in a .pxd module
should be prohibited from accessing globals, or a trivial module
should be created to store the globals for that module.

The second problem boils down to the global variable lookup being
compiled to something like:

__pyx_t_1 = __Pyx_GetModuleGlobalName(__pyx_n_s_FOO_A)

Perhaps, when a function from one module gets inlined in another
module, at least in the case where it needs to access globals, the
originating module should be imported first and
__Pyx_GetModuleGlobalName replaced with an equivalent that uses the
originating module's globals.

So have a global variable like

static PyObject* __pyx_d_foo

and in the module init code something like:

_pyx_t_1 = __Pyx_Import("foo")
__pyx_d_foo = PyModule_GetDict(__pyx_t_1)

then in the inlined function, replace the call to
__Pyx_GetModuleGlobalName to an equivalent that uses __pyx_d_foo
instead of __pyx_d.

This all assumes, of course, that "foo" is an importable module.  I'm
not sure this makes sense otherwise...  If not then maybe inlining a
function should also "inline" any global variables it uses.

Thoughts?

Erik


More information about the cython-devel mailing list