Круговая зависимость в Python

У меня есть два файла, node.py а также path.py, которые определяют два класса, Node а также Pathсоответственно.

До сегодняшнего дня определение Path ссылался на Node объект, и поэтому я сделал

from node.py import *

в path.py файл.

Однако на сегодняшний день я создал новый метод для Node что ссылается на Path объект.

У меня были проблемы при попытке импортировать path.py: Я попробовал, и когда программа запустилась и вызвал Path метод, который использует NodeВозникло исключение Node не определяется

Что я делаю?

5 ответов

Решение

Импорт модулей Python - это отличная статья, в которой объясняется циклический импорт в Python.

Самый простой способ исправить это - перенести путь импорта в конец модуля узла.

Еще один подход - импортировать один из двух модулей только в той функции, в которой он вам нужен, в другом. Конечно, это работает лучше всего, если вам это нужно только в одной или нескольких функциях:

# in node.py 
from path import Path
class Node 
    ...

# in path.py
class Path
  def method_needs_node(): 
    from node import Node
    n = Node()
    ...

Возможно, вам не потребуется импортировать Path в node.py Для того чтобы Path а также Node использовать друг друга.

# in __init__.py  (The order of imports should not matter.)
from .node import Node
from .path import Path

# in path.py 
from . import Node
class Path
  ...

  def return_something_pathy(self): 
    ...

# in node.py
class Node
  def __init__(self, path): 
    self.path = path
    ...

  def a_node_method():
    print(self.path.return_something_pathy())

Чтобы было ясно, что Node использует Path, добавьте подсказку типа. Начиная с Python 3.7 доступна функция поддержки прямых ссылок в аннотациях типов, описанная в PEP 563.

# in node.py  (Now with type hinting.)
from __future__ import annotations

class Node
  def __init__(self, path: Path): 
    self.path = path
    ...

  def a_node_method():
    print(self.path.return_something_pathy())

Я наткнулся на еще одно решение, чтобы выбраться из круговой дыры для импорта в Python - отличный пост в блоге, который научил меня этому.

Я предпочитаю разорвать циклическую зависимость, объявив одну из зависимостей в конструкторе другого зависимого класса. На мой взгляд, это делает код более аккуратным и обеспечивает легкий доступ ко всем методам, которым требуется зависимость.

Так что в моем случае у меня есть CustomerService и UserService, которые зависят друг от друга. Я нарушаю круговую зависимость следующим образом:

class UserService:

    def __init__(self):
        # Declared in constructor to avoid circular dependency
        from server.portal.services.admin.customer_service import CustomerService
        self.customer_service = CustomerService()

    def create_user(self, customer_id: int) -> User:
        # Now easy to access the dependency from any method
        customer = self.customer_service.get_by_id(customer_id)

Другой способ - определить их оба в одном модуле и отложить определение типов. Примерно так:

      class Node:
   _path_type: type = None
   
   def method_needs_path(self):
       p = self._path_type()
       ...


class Path:
    def method_needs_node(self):
       n = Node()

Node._path_type = Path

Может быть, лучше проявить симметричность по этому поводу:

      class Node:
   _path_type: type = None
   
   def method_needs_path(self):
       p = self._path_type()
       ...


class Path:
    _node_type: type = None

    def method_needs_node(self):
       n = Node()

Node._path_type = Path
Path._node_type = Node

Это также можно сделать в нескольких модулях:

      # in node.py
class Node:
   _path_type: type = None
   
   def method_needs_path(self):
       p = self._path_type()
       ...

# in path.py
from .node import Node

class Path:
    _node_type: type = None

    def method_needs_node(self):
       n = self._node_type()

Node._path_type = Path
Path._node_type = Node

# in __init__.py (note that order is important now)
from .node import Node
from .path import Path
Другие вопросы по тегам