Skip to content

[BUG] Partially loaded circular imports don't import #4390

@da-woods

Description

@da-woods

Copied from the users mailing list (https://groups.google.com/g/cython-users/c/VNuQKATGuss)

I'm running cython (cloned from github mid-May 2021) and Python 3.6/3.7.9. I've encountered an issue with circular imports, one that Python runs without a problem. At its simplest, it's the following sequence of operations:

import A.B         
  from . import C      # <-- in A/B.py
    from . import D    # <-- in A/C.py
      from . import C  # <-- in A/D.py

If you run this scenario in CPython, it runs without error, but if I compile all the .py files into C extensions using Cython, the last import fails with the error:

ImportError: cannot import name C

Each of the above statements seems to be implemented by Cython with a pair of __Pyx_Import and __Pyx_ImportFrom calls. The __Pyx_Import call attempts to do the import, and returns the parent package (in all cases above, it returns A). The __Pyx_ImportFrom call just looks inside the returned module for an attribute with the requested name (C, or D in the above examples). If the requested name ('C') in __Pyx_ImportFrom is actually a module that has not finished initializing yet, then it is not yet set as an attribute in its parent ('A') so this fails.

In CPython, I think, if the above attribute access (i.e. not hasattr(A, 'C')) fails, then it checks if it is a module (i.e. __import__('A.C')) probably to address this exact case. Any chance __Pyx_ImportFrom could do this too to mimic CPython behavior? Obviously the above scenario is silly, but the pattern shows up in an existing library that I'm trying to embed into a static Python interpreter and it seems like it is supported by the language.

Below is a list of commands that will generate the source files and test the above scenario on CentOS/RedHat 7, presumably minimal changes for other Linux variants. Thanks for your help!

mkdir circular_import
cd circular_import
mkdir -p src/A
mkdir -p install/A
echo "from . import C" > src/A/B.py
echo "from . import D" > src/A/C.py
echo "from . import C" > src/A/D.py
for i in A/{B,C,D} ; do cython src/"${i}".py ; gcc -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing -I/usr/include/python3.6m -o install/"${i}".so src/"${i}".c ; done

(cd src && echo "import A.B" | python3 )  # succeeds
(cd install && echo "import A.B" | python3 )  # fails

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions