簡體   English   中英

Python中的循環導入依賴

[英]Circular import dependency in Python

假設我有以下目錄結構:

a\
    __init__.py
    b\
        __init__.py
        c\
            __init__.py
            c_file.py
        d\
            __init__.py
            d_file.py

a包的__init__.py ,導入了c包。 但是c_file.py導入abd

程序失敗,說當c_file.py嘗試導入abdb不存在。 (而且它真的不存在,因為我們正在導入它。)

如何解決這個問題?

您可以推遲導入,例如在a/__init__.py

def my_function():
    from a.b.c import Blah
    return Blah()

也就是說,將導入推遲到真正需要時。 但是,我也會仔細查看我的包定義/用途,因為像所指出的那樣的循環依賴可能表明存在設計問題。

如果 a 取決於 c 而 c 取決於 a,那么它們實際上不是同一個單位嗎?

您應該真正檢查為什么將 a 和 c 拆分為兩個包,因為要么您有一些代碼,您應該將其拆分到另一個包中(使它們都依賴於那個新包,而不是相互依賴),或者您應該合並它們成一包。

我曾多次想過這個問題(通常是在處理需要相互了解的模型時)。 簡單的解決方案就是導入整個模塊,然后引用你需要的東西。

所以而不是做

from models import Student

在一個,和

from models import Classroom

另一方面,就做

import models

在其中之一中,然后在需要時調用models.Classroom

類型提示導致的循環依賴

使用類型提示,有更多機會創建循環導入。 幸運的是,有一個使用特殊常量的解決方案: typing.TYPE_CHECKING

下面的示例定義了一個Vertex類和一個Edge類。 一條邊由兩個頂點定義,一個頂點維護它所屬的相鄰邊的列表。

沒有類型提示,沒有錯誤

文件:頂點.py

class Vertex:
    def __init__(self, label):
        self.label = label
        self.adjacency_list = []

文件:edge.py

class Edge:
    def __init__(self, v1, v2):
        self.v1 = v1
        self.v2 = v2

類型提示導致導入錯誤

導入錯誤:無法從部分初始化的模塊“邊緣”導入名稱“邊緣”(很可能是由於循環導入)

文件:頂點.py

from typing import List
from edge import Edge


class Vertex:
    def __init__(self, label: str):
        self.label = label
        self.adjacency_list: List[Edge] = []

文件:edge.py

from vertex import Vertex


class Edge:
    def __init__(self, v1: Vertex, v2: Vertex):
        self.v1 = v1
        self.v2 = v2

使用 TYPE_CHECKING 的解決方案

文件:頂點.py

from typing import List, TYPE_CHECKING

if TYPE_CHECKING:
    from edge import Edge


class Vertex:
    def __init__(self, label: str):
        self.label = label
        self.adjacency_list: List['Edge'] = []

文件:edge.py

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from vertex import Vertex


class Edge:
    def __init__(self, v1: 'Vertex', v2: 'Vertex'):
        self.v1 = v1
        self.v2 = v2

帶引號與不帶引號的類型提示

在 Python 3.10 之前的版本中,有條件導入的類型必須用引號括起來,使它們成為“前向引用”,從而將它們隱藏在解釋器運行時之外。

在 Python 3.7、3.8 和 3.9 中,解決方法是使用以下特殊導入。

from __future__ import annotations

這允許使用不帶引號的類型提示與條件導入相結合。

Python 3.10(參見PEP 563 - 注釋的推遲評估

在 Python 3.10 中,將不再在定義時評估函數和變量注釋。 相反,字符串形式將保留在相應的注釋字典中。 靜態類型檢查器在行為上沒有區別,而在運行時使用注釋的工具將不得不執行延遲評估。

字符串形式是在編譯步驟期間從 AST 獲得的,這意味着字符串形式可能不會保留源的確切格式。 注意:如果一個注解已經是一個字符串文字,它仍然會被包裹在一個字符串中。

問題是當從目錄運行時,默認情況下只有子目錄的包作為候選導入可見,所以你不能導入 abd 但是你可以導入 bd,因為 b 是 a 的子包。

如果你真的想在c/__init__.py導入 abd 你可以通過將系統路徑更改為a/__init__.py上方的一個目錄並將a/__init__.py的導入更改為 import abc 來實現

你的a/__init__.py應該是這樣的:

import sys
import os
# set sytem path to be directory above so that a can be a 
# package namespace
DIRECTORY_SCRIPT = os.path.dirname(os.path.realpath(__file__)) 
sys.path.insert(0,DIRECTORY_SCRIPT+"/..")
import a.b.c

當您想在 c 中將模塊作為腳本運行時,會出現一個額外的困難。 這里包 a 和 b 不存在。 您可以破解 c 目錄中的__int__.py以將 sys.path 指向頂級目錄,然后在 c 中的任何模塊中導入__init__以便能夠使用完整路徑導入 abd 我懷疑這是一個好習慣導入__init__.py但它適用於我的用例。

我建議以下模式。 使用它將允許自動完成和類型提示正常工作。

循環導入a.py

import playground.cyclic_import_b

class A(object):
    def __init__(self):
        pass

    def print_a(self):
        print('a')

if __name__ == '__main__':
    a = A()
    a.print_a()

    b = playground.cyclic_import_b.B(a)
    b.print_b()

循環導入b.py

import playground.cyclic_import_a

class B(object):
    def __init__(self, a):
        self.a: playground.cyclic_import_a.A = a

    def print_b(self):
        print('b1-----------------')
        self.a.print_a()
        print('b2-----------------')

您不能使用此語法導入類 A 和 B

from playgroud.cyclic_import_a import A
from playground.cyclic_import_b import B

您不能在類 B __ init __ 方法中聲明參數 a 的類型,但您可以通過這種方式“強制轉換”它:

def __init__(self, a):
    self.a: playground.cyclic_import_a.A = a

另一種解決方案是為 d_file 使用代理。

例如,假設您想與 c_file 共享 blah 類。 d_file 因此包含:

class blah:
    def __init__(self):
        print("blah")

這是您在 c_file.py 中輸入的內容:

# do not import the d_file ! 
# instead, use a place holder for the proxy of d_file
# it will be set by a's __init__.py after imports are done
d_file = None 

def c_blah(): # a function that calls d_file's blah
    d_file.blah()

在 a 的init .py 中:

from b.c import c_file
from b.d import d_file

class Proxy(object): # module proxy
    pass
d_file_proxy = Proxy()
# now you need to explicitly list the class(es) exposed by d_file
d_file_proxy.blah = d_file.blah 
# finally, share the proxy with c_file
c_file.d_file = d_file_proxy

# c_file is now able to call d_file.blah
c_file.c_blah() 

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM