[capi-sig]About the need for a better import C API
Hello,
Not sure this is the right forum for this, but on the Cython bug tracker, the problem of managing to use the C API for importing modules came up: https://github.com/cython/cython/issues/2854#issuecomment-466487200
If Stefan Behnel, who is one of the most experimented persons in the world with CPython's C API, has trouble getting it working, then probably it deserves improving. ;-)
(I also realize that the C API is more or less married to the dubious semantics of the standard "__import__" function, which is itself difficult to change, so perhaps a better C API isn't very easy)
CC'ing Stefan in case he isn't subscribed and wants to chime in.
Regards
Antoine.
Hi Antoine,
yep, this might be worth discussing here, although the problems I had were mostly related to trying to reproduce CPython's semantics of relative, absolute and cyclic imports in Cython.
Antoine Pitrou schrieb am 22.02.19 um 19:02:
Not sure this is the right forum for this, but on the Cython bug tracker, the problem of managing to use the C API for importing modules came up: https://github.com/cython/cython/issues/2854#issuecomment-466487200
If Stefan Behnel, who is one of the most experimented persons in the world with CPython's C API, has trouble getting it working, then probably it deserves improving. ;-)
(I also realize that the C API is more or less married to the dubious semantics of the standard "__import__" function, which is itself difficult to change, so perhaps a better C API isn't very easy)
Yes, it seems that's a major part of the issue, and also the fact that relative imports were added very late to Python, when most of the C-API import functions already existed.
The main import function, "PyImport_ImportModuleLevelObject()" (link below) mimics the Python semantic difference between (1) "import a.b.c", (2) "import a.b.c as c", and (3) "from a.b import c", where the first only assigns "a" and the second and third assign "c". Thus, its return value changes depending on whether a "fromlist" is passed or not. That is a rather weird interface.
However, none of the other import functions allow relative imports. This is the only one that allows passing a "level", and it's not even consistent with the Python semantics. In Python, a relative import like "from ..pkg import module" assigns to the module name, whereas a relative C-API import of "pgk.module" with level 2 returns the top-level package, which wasn't even mentioned anywhere in the import (there might actually be a reason why I'm trying a relative import here).
Basically, if I want to execute a relative import to get another module, I have to jump through hoops like passing the non-empty fromlist ['*'], just to have it return the module that I wanted to import instead of the top-level module. Or, since there is no simple way to get from the top-level module back to the imported module, I'd have to keep looking up submodule attributes. The functionality for that is implemented in byte code in CPython, not as a C-API function.
And then, I can run into corner cases like circular imports. While one module is initialising, it might not have been assigned to its package as an attribute yet (even though it's already in sys.modules). Looking up the attribute chain in that case risks to fail. So, in order to avoid that AttributeError, I could first execute the relative import, and then throw away the return value and call "PyImport_GetModule()" to get the module from sys.modules.
It really shouldn't be that hard to do these things.
Stefan
[*] https://docs.python.org/3/c-api/import.html#c.PyImport_ImportModuleLevelObje...
On Sat, 23 Feb 2019 at 05:34, Stefan Behnel <python_capi@behnel.de> wrote:
It really shouldn't be that hard to do these things.
Agreed. Something we may want to consider is exposing C APIs corresponding more directly to the Python level syntax.
Absolute imports (this already exists):
"import module.ref as m" -> m = PyImport_Import("module.ref")
Submodule-or-attribute imports (currently requires making your own fromlist, and dealing with circular imports yourself):
"from module import ref as m" -> m = PyImport_FromModule("module", "ref")
Relative imports (currently requires messing with both fromlist and level, and dealing with circular imports yourself):
"from .module import ref as m" -> m =
PyImport_FromRelativeModule(0, "module", "ref") "from ..module import ref as m" -> m = PyImport_FromRelativeModule(1, "module", "ref") "from . import module.ref as m" -> m = PyImport_FromRelativeModule(0, NULL, "module.ref") "from .. import module.ref as m" -> m = PyImport_FromRelativeModule(1, NULL, "module.ref")
The preferred parameter orders could be debated, but I've picked the ones above so that the left-to-right order matches between the Python code and the C API call.
And those would also then have "*Object" variants that accepted Python objects instead of C strings.
Cheers, Nick.
-- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
participants (3)
-
Antoine Pitrou
-
Nick Coghlan
-
Stefan Behnel