Kai78
Kai78

Reputation: 47

Circular import despite absolute imports

I have got a problem with circular imports in Python 3.9. I know there are numerous articles, questions and answers about this topic and I really have read a lot of them and about how to circumvent but I cannot figure out why it happens in my case. As far as I have understood one can circumvent circular imports by doing absolute imports:

This is my Stacktrace:

partially initialized module 'myproject.myapp.service.linepattern.corner' has no attribute 'Corner' (most likely due to a circular import)

  File "/mnt/d/User/Programming/MyProject/myproject/myapp/service/linepattern/line.py", line 8, in Line
    def __init__(self, cornerRight: cc.Corner, cornerLeft: cc.Corner) -> None:
  
  File "/mnt/d/User/Programming/MyProject/myproject/myapp/service/linepattern/line.py", line 7, in <module>
    class Line(ABC):
  
  File "/mnt/d/User/Programming/MyProject/myproject/myapp/service/linepattern/corner.py", line 2, in <module>
    import myproject.myapp.service.linepattern.line as cl
  
  File "/mnt/d/User/Programming/MyProject/myproject/myapp/service/linepattern/cornermanager.py", line 2, in <module>
    import myproject.myapp.service.linepattern.corner as cc
  
  File "/mnt/d/User/Programming/MyProject/myproject/tests/myapp/test_cornermanager.py", line 3, in <module>
    import myproject.myapp.service.linepattern.cornermanager as cm

and this is my file hierarchy

/mnt/d/User/Programming/MyProject
├── manage.py
├── myproject
│   ├── myapp
│   │   ├── __init__.py
│   │   ├── service
│   │   │   └── linepattern
│   │   │       ├── cornermanager.py
│   │   │       ├── corner.py
│   │   │       ├── line.py
│   │   │       ├── __init__.py
│   ├── settings.py
│   ├── tests
│   │   ├── myapp
│   │   │   ├── __init__.py
│   │   │   └── test_cornermanager.py

and finally my code

cornermanager.py

import myproject.myapp.service.linepattern.corner as cc

class CornerManager:
    def __init__(self) -> None:
        self.cornerList: List[cc.Corner]
        for i in range(1, 2):
            self.cornerList.append(cc.Corner(i, self))
        for corner in self.cornerList:
            corner.initializeLines()

    def corner(self, cornerNr: int) -> cc.Corner:
        return self.cornerList[cornerNr - 1]

corner.py

import myproject.myapp.service.linepattern.cornermanager as cm
import myproject.myapp.service.linepattern.line as cl

class Corner:
    def __init__(self, cornerNumber: int, cornerManager: cm.CornerManager) -> None:
        self.cornerNumber = cornerNumber
        self.cornerManager = cornerManager
        self.lineSet: Set[cl.Line]
        
    def initializeLines(self) -> None:
        #Add lines to the corner including its neighbor
        if self.cornerNumber == 1:
            self.lineSet.add(cl.Line(self, self.cornerManager.corner(2)))
        elif self.cornerNumber == 2:
            self.lineSet.add(cl.Line(self, self.cornerManager.corner(2)))

    
    def addLine(self, line: cl.Line) -> None:
        #Remove line from both corners
        for corner in cl.cornerPair():
            corner.lineSet.add(line)
        
    def removeLine(self, line: cl.Line) -> None:
        #Remove line from both corners
        for corner in cl.cornerPair():
            corner.lineSet.remove(line)

line.py

import myproject.myapp.service.linepattern.corner as cc

class Line:
    def __init__(self, cornerRight: cc.Corner, cornerLeft: cc.Corner) -> None:
        self.rightCorner = cornerRight
        self.leftCorner = cornerLeft        
    
    def cornerPair(self) -> Tuple[cc.Corner, cc.Corner]:
        #Return both corners
        return (self.rightCorner, self.leftCorner)

Upvotes: 0

Views: 216

Answers (1)

Henry Tjhia
Henry Tjhia

Reputation: 752

With the code you put in the question (three separated modules), by including the following code at the top of each file should be able to fix the problems:

from __future__ import annotations

In short this code will treat annotations as strings. You can read more about this at: Type hint of the enclosing class and Evaluation of annotations.

For the errors, this actually how Python executes your modules (start from cornermanager.py):

  1. import corner. With this Python first saves the module in its internal module (sys.modules) before running the imported module code.
  2. In corner.py itself, the first two lines are import statement, so let's start with import cornermanager. Again, Python saves the module in its internal module then executes the code in cornermanager.
  3. Back to cornermanager.py. As we know first line of this module is import corner, but because Python already saved this module (corner) in its internal module, it doesn't import it the second time. That is, Python skips it and executes code below this line. Interestingly, because corner hasn't executed all its code but the first line (import cornermanager), attempt to fetch its Corner class will not succeed.

Upvotes: 1

Related Questions