两句话总结:
- 内部的相对引用如果用from ..Branch import module1, from .Branch import module2。。。类似这种形式,然后在最外层python -m Tree.m1,在内层用python m1.py会报错
-
文件结构如下: ------------------------ Tree\m1.py: from .Branch import m3 m3.printSelf() ------------------------ Tree\Branch\m3.py: from . import m4 def printSelf(): print('In m3') ------------------------ Tree\Branch\m4.py def printSelf(): print('In m4') ------------------------ 去Tree下执行python -m m1,报: Traceback (most recent call last): File "D:\ProgramFiles\Anaconda3\lib\runpy.py", line 193, in _run_module_as_main "__main__", mod_spec) File "D:\ProgramFiles\Anaconda3\lib\runpy.py", line 85, in _run_code exec(code, run_globals) File "D:\codes\Tree\m1.py", line 1, in <module> from .Branch import m3 ImportError: attempted relative import with no known parent package 去Tree下执行python m1.py,会报: Traceback (most recent call last): File "m1.py", line 1, in <module> from .Branch import m3 ModuleNotFoundError: No module named '__main__.Branch'; '__main__' is not a package 去Tree外层执行python -m Tree.m1,执行成功
- 内部的相对引用如果用from . import module1, from .. import module2, from Branch import module3。。。类似这种形式,直接python m1.py或者python -m m1.py都可以。
文件结构如下:
------------------------
Tree\m1.py:
from Branch import m3
m3.printSelf()
------------------------
Tree\Branch\m3.py:
from . import m4
def printSelf():
print('In m3')
------------------------
Tree\Branch\m4.py
def printSelf():
print('In m4')
------------------------
去Tree下执行python -m m1,执行成功
去Tree下执行python m1.py,执行成功
去Tree外层执行python -m Tree.m1,会报错:
Traceback (most recent call last):
File "D:\ProgramFiles\Anaconda3\lib\runpy.py", line 193, in _run_module_as_main
"__main__", mod_spec)
File "D:\ProgramFiles\Anaconda3\lib\runpy.py", line 85, in _run_code
exec(code, run_globals)
File "D:\codes\Tree\m1.py", line 1, in <module>
from Branch import m3
ModuleNotFoundError: No module named 'Branch'
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
一个Python Module(模块),是一个文件,包含了Python对象定义和Python语句(definitions and statements)。文件名就是模块名加上后缀.py,在模块内部,模块名存储在全局变量__name__中,是一个string,可以直接在module中通过__name__引用到module name。
module是为了重复使用一些对象,比如类,函数,而将这些对象定义放在一个.py文件中,或是将一个较大的工程裁缝为多个.py文件而易于维护,每一个.py文件都是一个module。
1,模块的定义和引入(import)
如下一个fibo.py文件
1 print ("__name__ :", __name__)
2 def fib(n):
3 a, b = 0, 1
4 result = []
5 print ("in fib():", __name__)
6 while (b<n):
7 result.append(b)
8 a, b = b, a+b
9 print(result)
这个fibo.py就是一个module,它有一个函数定义fib(),和一个语句(statement),第一行的print语句,我们在当前文件目录运行Python Interpreter就可以去引入这个模块,并执行模块中定义的fib()函数。
>>> import fibo
('__name__ :', 'fibo') #print语句执行
>>> fibo.fib(10)
('in fib() :', 'fibo') #fib()函数执行
[1, 1, 2, 3, 5, 8]
可以看到,在import的时候,这个module的语句(statements)执行了,所定义的函数并未执行,在通过module名引用module中的函数定义时,函数才被执行
同样可以在一个script file中引入module,我们在fibo.py所在的目录创建另一个文件calculat.py
1 from fibo import fib
2 for n in range(10, 50, 5):
3 fib(n)
然后用Python解释器运行calcute.py得到结果。
这里有两种import 语句,
一种是import module_name1 [as name1], module_name2 [as name2]
一种是from module_name import item1 [as name1], item2 [as name2]
2, module的加载
每个module都包含对象定义和一些语句(statements),这些语句应该是意图要来初始化这个module的,这些语句会在这个module第一次被import的时候执行(多次import只会执行一次,不管是以上两种import的语句中那一种),当这个module被作为一个script来运行的时候也会被执行。
每个module都有自己的private symbol table,当使用第一种import语句import一个module的时候,引入者的local symbol table就加入了这个module,其名字如果没有使用as的话就是被引入的模块名本身。使用第二种import语句这会在引入者的local symbol table中加入具体引用的item,其名称若没使用as则就为item的名称。
3,module搜索路径
当遇到一个名为xiaoyu的module需要import的时候,Python Interpreter首先搜寻built-in module中有没有叫这个名的,若是没有,则Interpreter会从一系列的目录中去搜寻这个module(也就是这个.py文件),这些目录值存储在sys.path中,而sys.path又是用这些值来初始化的:
- 当前目录,即input script所在的目录
- 环境变量PYTHONPATH中存储的值(PYTHONPATH的语法和PATH一样)
- Python包的安装目录,比如我的服务器上django就安装在 /usr/local/lib/python2.7/dist-packages/中,sys.path含有这个目录
Python有一个标准库,其中定义了一系列的module,这些module中的一部分是直接集成在Interpreter中的,这些built-in module主要提供了很重要的但是Python语言又没有提供的功能,比如跟system call有关的sys module就集成在所有平台的Python Interpreter中,在Interpreter中集成哪些module是可以配置的,并且随平台不同而有差别。
在启动Interpreter,sys.path被初始化后,我们可以对它进行修改
|
4, 把module作为script来执行
前面我们已经提到了关于module中语句的执行。这里要补充一点东西,通常一个script file指的是调用Python Interpreter时作为参数传递给Interpreter的文件,当然所有的.py文件都是一个module,这样的一个script或是module,其__name__会被Interpreter自动设置为"__main__"。以下是一个测试:
1 print ("__name__ :", __name__)
2 def fib(n):
3 a, b = 0, 1
4 result = []
5 print ("in fib() :", __name__)
6 while (b<n):
7 result.append(b)
8 a, b = b, a+b
9 print(result)
10
11 if __name__ == "__main__":
12 import sys
13 fib(int(sys.argv[1]))
用Python Interpreter直接调用这个script
|
可以看到依然module的语句都会被执行,只是__name__的值一开始就变为了"__main__",给一个模块加上
if __name == "__main__":
常常是为了测试这个模块,因为这个语句块只有当module被作为script直接传给Interpreter的时候才会被执行。
上面例子中的12行import sys可以看出,Python并没有规定import语句必须写在module的最前面,只是习惯性的我们约定都写在最前面。
5. 内置dir()函数(built-in dir() function)
dir()函数可以用来查看一个module所定义的所有names,试验
|
可以看到在import了fibo和sys后,并且fibo是用别名fibo_local来引入的,在引入者module中就定义了sys和fibo_local,可以看到dir(fibo)是抛了NameError异常的,fibo并没有被定义,定义的是fibo_local,这也可以看出import语句对local symbol table是怎样影响的。
6, 模块包(package)
包(package)可以理解为是组织起来的module的一个层次结构,也就是package是一个directory,它包含sub-package或者是module,而module是.py文件,要让Python Interpreter把一个目录作为package,则该目录下必须有__init__.py文件,__init__.py可以为空,当然也可以有对象定义和语句,用来做初始化工作,__init__.py还有个作用就是设置__all__变量。
package本身就可以来作为一个module使用,只是它所包含的sub-module或module可以通过package name用package.module的名称形式去引用,这更有利于组织一系列相关的module,避免module间定义的名称的混乱。
package在实际工程中非常常用,__init__.py也常常不会为空,而会有对象定义和初始化代码来让这个包,也就是这个module,包含其该有的item定义。以后我们会对package做更多了解。
参考:
1,http://docs.python.org/3.3/tutorial/modules.html modules
2,http://docs.python.org/3.3/reference/simple_stmts.html#the-import-statement import语句
3,http://docs.python.org/3/reference/executionmodel.html Python execution model
4,http://docs.python.org/3/reference/import.html Python import system
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
再转一篇知乎的文章:
Python用了快两年了吧,其中有些东西一直是稀里糊涂地用,import便是我一直没有明白的东西。曾经有过三次解决它的机会,我都因得过且过、一拖再拖而没能化敌为友。今天下午,它又给了我一次机会,我想我还是从了它的心愿吧。
故事是从这篇台湾同胞的博客(Python 的 Import 陷阱)开始的,然后又跳到了Python社区的PEP 328提案(PEP 328 -- Imports: Multi-Line and Absolute/Relative),再结合过去的经验以及一些测试,我想我大概懂了吧。下面是我的总结,希望内容能够言简意赅、易于理解。
import语句有什么用?import语句用来导入其他python文件(称为模块module),使用该模块里定义的类、方法或者变量,从而达到代码复用的目的。为了方便说明,我们用实例来说明import的用法,读者朋友可以跟着尝试(尝试时建议使用python3,python2和python3在import的表现有差异,之后会提到)。
首先,先建立一个文件夹Tree作为工作目录,并在其内建立两个文件m1.py和m2.py,在m1.py写入代码:
import os
import m2
m2.printSelf()
在m2.py写入代码:
def printSelf():
print('In m2')
打开命令行,进入到Tree目录下,敲下python m1.py
运行,发现没有报错,且打印出In m2
,说明这样使用import没有问题。由此我们总结出import语句的第一种用法。
import module_name
。即import后直接接模块名。在这种情况下,Python会在两个地方寻找这个模块,第一是sys.path(通过运行代码import sys; print(sys.path)
查看),os这个模块所在的目录就在列表sys.path中,一般安装的Python库的目录都可以在sys.path中找到(前提是要将Python的安装目录添加到电脑的环境变量),所以对于安装好的库,我们直接import即可。第二个地方就是运行文件(这里是m1.py)所在的目录,因为m2.py和运行文件在同一目录下,所以上述写法没有问题。
用上述方法导入原有的sys.path中的库没有问题。但是,最好不要用上述方法导入同目录下的文件!因为这可能会出错。演示这个错误需要用到import语句的第二种写法,所以先来学一学import的第二种写法。在Tree目录下新建一个目录Branch,在Branch中新建文件m3.py,m3.py的内容如下:
def printSelf():
print('In m3')
如何在m1中导入m3.py呢,请看更改后的m1.py:
from Branch import m3
m3.printSelf()
总结import语句的第二种用法:
from package_name import module_name
。一般把模块组成的集合称为包(package)。与第一种写法类似,Python会在sys.path和运行文件目录这两个地方寻找包,然后导入包中名为module_name的模块。
现在我们来说明为什么不要用import的第一种写法来导入同目录下的文件。在Branch目录下新建m4.py文件,m4.py的内容如下:
def printSelf():
print('In m4')
然后我们在m3.py中直接导入m4,m3.py变为:
import m4
def printSelf():
print('In m3')
这时候运行m1.py就会报错了,说没法导入m4模块。为什么呢?我们来看一下导入流程:m1使用from Branch import m3
导入m3,然后在m3.py中用import m4
导入m4。看出问题了吗?m4.py和m1.py不在同一目录,怎么能直接使用import m4
导入m4呢。(读者可以试试直接在Tree目录下新建另一个m4.py文件,你会发现再运行m1.py就不会出错了,只不过导入的是第二个m4.py了)
面对上面的错误,使用python2运行m1.py就不会报错,因为在python2中,上面提到的import的两种写法都属于相对导入,而在python3中,却属于绝对导入。话说到了这里,就要牵扯到import中最关键的部分了——相对导入和绝对导入。
我们还是谈论python3的import用法。上面提到的两种写法属于绝对导入,即用于导入sys.path中的包和运行文件所在目录下的包。对于sys.path中的包,这种写法毫无问题;导入自己写的文件,如果是非运行入口文件(上面的m1.py是运行入口文件,可以使用绝对导入),则需要相对导入。
比如对于非运行入口文件m3.py,其导入m4.py需要使用相对导入:
from . import m4
def printSelf():
print('In m3')
这时候再运行m1.py就ok了。列举一下相对导入的写法:
from . import module_name
。导入和自己同目录下的模块。from .package_name import module_name
。导入和自己同目录的包的模块。from .. import module_name
。导入上级目录的模块。from ..package_name import module_name
。导入位于上级目录下的包的模块。- 当然还可以有更多的
.
,每多一个点就多往上一层目录。
不知道你有没有留神上面的一句话——“上面的m1.py是运行入口文件,可以使用绝对导入”,这句话是没问题的,也和我平时的做法一致。那么,运行入口文件可不可以使用相对导入呢?比如m1.py内容改成:
from .Branch import m3
m3.printSelf()
答案是可以,但不能用python m1.py
命令,而是需要使用在Tree外层用python -m Tree.m1
来运行。为什么?关于前者,PEP 328提案中的一段文字好像给出了原因:
Relative imports use a module's _name _ attribute to determine that module's position in the package hierarchy. If the module's name does not contain any package information (e.g. it is set to '__main __') then relative imports are resolved as if the module were a top level module, regardless of where the module is actually located on the file system.
我不太懂,但是又有一点明白。我们应该见过下面一段代码:
if __name__ == '__main__':
main()
意思是如果运行了当前文件,则__name__变量会置为__main__,然后会执行main函数,如果当前文件是被其他文件作为模块导入的话,则__name__为模块名,不等于__main__,就不会执行main函数。比如对于上述更改后的m1.py,执行python m1.py
命令后,会报如下错误:
Traceback (most recent call last): File "m1.py", line 1, in from .Branch import m3 ModuleNotFoundError: No module named '_ main_.Branch'; '__main__' is not a package
据此我猜测执行python m1.py
命令后,当前目录所代表的包'.'变成了__main__。
那为什么python -m Tree.m1
就可以呢?那位台湾老师给出了解释:
执行指令中的-m是为了让Python预先import你要的package或module给你,然后再执行script。
即不把m1.py当作运行入口文件,而是也把它当作被导入的模块,这就和非运行入口文件有一样的表现了。
那反过来,如果m1.py使用绝对导入(from Branch import m3
),能使用python -m m1
运行吗?我试了一下,如果当前目录是Tree就可以。如果在其他目录下运行,比如在Tree所在的目录(使用python -m Tree.m1
运行),就不可以。这可能还是与绝对导入相关。
(之前看到了一个大型项目,其运行入口文件有一大堆的相对导入,我还傻乎乎地用python直接运行它。之后看到他给的样例运行命令是带了-m参数的。现在才恍然大悟。)
理解import的难点差不多就这样了。下面说一说import的其他简单但实用的用法。
import moudle_name as alias
。有些module_name比较长,之后写它时较为麻烦,或者module_name会出现名字冲突,可以用as来给它改名,如import numpy as np
。from module_name import function_name, variable_name, class_name
。上面导入的都是整个模块,有时候我们只想使用模块中的某些函数、某些变量、某些类,用这种写法就可以了。使用逗号可以导入模块中的多个元素。- 有时候导入的元素很多,可以使用反斜杠来换行,官方推荐使用括号。
from Tkinter import Tk, Frame, Button, Entry, Canvas, Text, \
LEFT, DISABLED, NORMAL, RIDGE, END # 反斜杠换行
from Tkinter import (Tk, Frame, Button, Entry, Canvas, Text,
LEFT, DISABLED, NORMAL, RIDGE, END) # 括号换行(推荐)
说到这感觉import的核心已经说完了。再跟着上面的博客说一说使用import可能碰到的问题吧。
问题1描述:ValueError: attempted relative import beyond top-level package。直面问题的第一步是去了解熟悉它,最好是能复现它,让它躺在两跨之间任我们去践踏蹂躏。仍然是上面四个文件,稍作修改,四个文件如下:
# m1.py
from Branch import m3
m3.printSelf()
# m2.py
def printSelf():
print('module2')
# m3.py
from .. import m2 # 复现的关键在这 #
print(__name__)
def printSelf():
print('In m3')
# m4.py
def printSelf():
print('In m4')
运行python m1.py
,就会出现该问题。问题何在?我猜测,运行m1.py后,m1代表的模块就是顶层模块(参见上面PEP 328的引用),而m3.py中尝试导入的m2模块所在的包(即Tree目录代表的包)比m1的层级更高,所以会报出这样的错误。怎么解决呢?将m1.py的所有导入改为相对导入,然后进入m1.py的上层目录,运行python -m Tree.m1
即可。
对于使用import出现的其他问题,碰到了再接着更新。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------
再转一篇台湾朋友的文章 (https://medium.com/pyladies-taiwan/python-%E7%9A%84-import-%E9%99%B7%E9%98%B1-3538e74f57e3):
在脫離 Python 幼幼班準備建立稍大型的專案的時候,學習如何組織化你的 Python 專案是一大要點。Python 提供的 module(模組)與 package(套件)是建立架構的基本元件,但在module之間為了重複使用一些 function(函數)或 class(類別)而必須互相 import(匯入),使用上一個不注意就會掉入混亂的 import 陷阱。
此篇將會從基本 module 和 package 介紹起,提點基本 import 語法及 absolute import 和 relative import 的用法與差異,最後舉出幾個常見因為錯誤 import 觀念造成的錯誤。
請注意,以下只針對 Python3 進行講解與測試。
Module與Package
基本上一個檔案就是一個 module,裡頭可以定義 function,class,和 variable。
把一個 module 想成一個檔案,那一個package就是一個目錄了。Package 可裝有 subpackage 和 module,讓你的專案更條理更組織化,最後一坨打包好還能分給別人使用。
先看看 module。假設有一個 module sample_module.py
裡頭定義了一個 function sample_func
:
def sample_func(): print('Hello!')
現在你在同一個目錄裡下有另一個 module sample_module_import.py
想要重複使用這個 function,這時可以直接從 sample_module
import 拿取:
from sample_module import sample_funcif __name__ == '__main__': sample_func()
跑 python3 sample_module_import.py
會得到:
Hello!
再來是 package。我們把上面兩個檔案包在一個新的目錄 sample_package
底下:
sample_package/ ├── __init__.py ├── sample_module.py └── sample_module_import.py
很重要的是新增那個 __init__.py
檔。它是空的沒關係,但一定要有,有點宣稱自己是一個 package 的味道。
這時候如果是進到 sample_package
裡面跑一樣的指令,那沒差。但既然都打包成 package 了,通常是在整個專案的其他地方需要用到的時候 import 它,這時候裡面的 import 就要稍微做因應。
讓我們修正一下 sample_package/sample_module_import.py
。假設這時我們在跟 sample_package
同一個 folder 底下執行下面兩種指令:
1. python3 sample_package/sample_module_import.py 2. python3 -m sample_package.sample_module_import
以下幾種不同的 import 寫法,會各有什麼效果呢?
# 不標準的 implicit relative import 寫法(Python 3 不支援) from sample_module import sample_func 1. 成功印出 Hello! 2. ModuleNotFoundError。因為 Python 3 不支援 implicit relative import (前面不加點的寫法),故會將之當作 absolute import,但第三個例子才是正確寫法。# 標準的 explicit relative import 寫法 from .sample_module import sample_func 1. 包含相對路徑的檔案不能直接執行,只能作為 module 被引用,所以失敗 2. 成功印出 Hello!# 標準的 absolute import 寫法 from sample_package.sample_module import sample_func 1. 如果此層目錄位置不在 python path 中,就會失敗 2. 成功印出 Hello!
這邊 absolute import 和 relative import 的詳細說明請稍候。
執行指令中的 -m
是為了讓 Python 預先 import 你要的 package 或 module 給你,然後再執行 script。所以這時 sample_module_import
在跑的時候,是以 sample_package
為環境的,這樣那些 import 才會合理。
另外,python path 是 Python 查找 module 時候使用的路徑,例如 standard module 所在的目錄位置。因此在第三種寫法中,Python 會因為在 python path 中找不到 sample_package.sample_module
而噴 error。你可以選擇把當前目錄加到 sys.path
,也就是 Python path(初始化自環境變數PYTHONPATH
),來讓 Python 搜尋得到這個 module ,但這個方法很髒很難維護,最多用來debug,其他時候強烈不建議使用。
基本 import 語法
前面有看過了,這邊統整介紹一下。如果你想使用在其他 module 裡定義的 function、class、variable 等等,就需要在使用它們之前先進行 import。通常都會把需要 import 的 module 們列在整個檔案的最一開始,但不是必須。
語法1:import [module]
# Import 整個 `random` module import random# 使用 `random` module 底下的 `randint` function print(random.randint(0, 5))
語法2:from [module] import [name1, name2, ...]
# 從 `random` module 裡 import 其中一個 function `randint` from random import randint# 不一樣的是,使用 `randint` 的時候就不需要先寫 `random` 了 print(randint(0, 5))
語法3:import [module] as [new_name]
# Import 整個 `random` module, # 但這個名字可能跟其他地方有衝突,因此改名成 `rd` import random as rd# 使用 `rd` 這個名稱取代原本的 `random` print(rd.randint(0, 5))
語法4(不推薦):from [module] import *
# Import 所有 `random` module 底下的東西 from random import *# 使用 `randint` 的時候也不需要先寫 `random` print(randint(0, 5))
語法4不推薦原因是容易造成名稱衝突,降低可讀性和可維護性。
Absolute Import v.s. Relative Import
Python 有兩種 import 方法,absolute import 及 relative import。Absolute import 就是完整使用 module 路徑,relative import 則是使用以當前 package為參考的相對路徑。
Relative import 的需求在於,有時候在改變專案架構的時候,裡面的 package 和 module 會拉來拉去,這時候如果這些 package 裡面使用的是relative import 的話,他們的相對關係就不會改變,也就是不需要再一一進入 module 裡更改路徑。但因為 relative import 的路徑取決於當前 package,所以在哪裡執行就會造成不一樣的結果,一不小心又要噴一堆 error;這時absolute import 就會減少許多困擾。
這邊參考PEP328提供的範例。Package 架構如下:
package ├── __init__.py ├── subpackage1 │ ├── __init__.py │ ├── moduleX.py │ └── moduleY.py ├── subpackage2 │ ├── __init__.py │ └── moduleZ.py └── moduleA.py
現在假設 package/subpackage1/moduleX.py
想要從其他 module 裡 import 一些東西,則使用下列語法([A]
表 absolute import 範例;[R]
表 relative import 範例):
# Import 同一個 package 底下的 sibling module `moduleY` [A] from package.subpackage1 import moduleY [R] from . import moduleY [Error] import .moduleY# 從同一個 package 底下的 sibling module `moduleY` 中, # import `spam` 這個 function [A] from package.subpackage1.moduleY import spam [R] from .moduleY import spam# 從隔壁 package 底下的 module `moduleZ` 中, # import `eggs` 這個 function [A] from package.subpackage2.moduleZ import eggs [R] from ..subpackage2.moduleZ import eggs# Import parent package 底下的 module `moduleA` [A] from package import moduleA [R] from .. import moduleA 或 from ... package import moduleA
要點:
- Relative import 裡,
..
代表上一層 ,多幾個.
就代表多上幾層。 - Relative import 一律採用
from ... import ...
語法,即使是從.
import也要寫from . import some_module
而非import .some_module
。原因是.some_module
這個名稱在 expression 裡無法出現。Absolute import 則無限制。
常見 import 陷阱
Trap 1: Circular Import
想像一個 module A
在一開始要 import 另一個 module B
裡的東西,但在匯入 module B
的途中也必須得執行它,而很不巧的 module B
也需要從 module A
import 一些東西。但 module A
還正在執行途中,自己都還沒定義好自己的 function 啊!於是你不讓我我不讓你,這種類似 deadlock 的情形正是常見的 circular import(循環匯入)。
讓我們看看範例。現在在 sample_package
裡有 A
和 B
兩個 module 想互打招呼,程式碼如下:
A.py
from .B import B_greet_back def A_say_hello(): print('A says hello!') B_greet_back()def A_greet_back(): print('A says hello back!') if __name__ == '__main__': A_say_hello()
B.py
from .A import A_greet_back def B_say_hello(): print('B says hello!') A_greet_back()def B_greet_back(): print('B says hello back!') if __name__ == '__main__': B_say_hello()
內容都一樣,只是A/B
互換。B
很有禮貌想先打招呼。在與 sample_package
同目錄底下執行:
$ python3 -m sample_package.B
會得到:
Traceback (most recent call last): File "/usr/local/Cellar/python3/3.6.2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/runpy.py", line 193, in _run_module_as_main "__main__", mod_spec) File "/usr/local/Cellar/python3/3.6.2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/runpy.py", line 85, in _run_code exec(code, run_globals) File "/path/to/sample_package/B.py", line 2, in <module> from .A import A_greet_back File "/path/to/sample_package/A.py", line 1, in <module> from .B import B_greet_back File "/path/to/sample_package/B.py", line 2, in <module> from .A import A_greet_back ImportError: cannot import name 'A_greet_back'
觀察到了嗎?B
試圖 import A_greet_back
,但途中先進到 A
執行,而因為 Python 是從頭開始一行一行執行下來的,於是在定義 A_greet_back
之前會先碰到自己的 import statement,於是又進入 B
,然後陷入死胡同。
常見解決這種circular import的方法如下:
- Import 整個 module 而非單一 attribute
把 B.py
更改成如下:
# from .A import A_greet_back from . import A def B_say_hello(): print('B says hello!') # A_greet_back() A.A_greet_back()...
就不會發生錯誤:
B says hello! A says hello back!
理由是,原本執行 from .A import A_greet_back
時被迫要從 load 進來的 A
module object 中找出 A_greet_back
的定義,但此時這個 module object 還是空的;而更新後的 from . import A
就只會檢查 A
module object 存不存在,至於 A_greet_back
存不存在等到需要執行的時候再去找就行了。
2. 延遲 import
把 B.py
更改成如下:
# 前面全刪def B_say_hello(): from .A import A_greet_back print('B says hello!') A_greet_back() ...
也會成功跑出結果。跟前面類似,Python 在跑到這行時才會 import A
module,這時因為 B
module 都已經 load 完了,所以不會有 circular import 的問題。但這個方法比較 hacky 一點,大概只能在 hackathon 中使用,否則正式專案裡看到這種難維護的 code 可能會有生命危險。
另一方面,把所有 import statement 擺到整個 module 最後面也是類似效果,但也會被打。
3. 好好釐清架構,避免circular import
是的,治本方法還是好好思考自己寫的 code 為什麼會陷入這種危機,然後重新 refactor 吧。
Trap 2: Relative Import above Top-level Package
還不熟悉 relative import 的人常常會見到這個 error:
ValueError: attempted relative import beyond top-level package
讓我們重現一下這個 error。把 B.py
前頭更改成如下:
# from . import A from ..sample_package import A ...
現在我們的路徑位置在與 sample_package
同目錄底下。跑:
$ python3 -m sample_package.B
會得到:
Traceback (most recent call last): File "/usr/local/Cellar/python3/3.6.2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/runpy.py", line 193, in _run_module_as_main "__main__", mod_spec) File "/usr/local/Cellar/python3/3.6.2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/runpy.py", line 85, in _run_code exec(code, run_globals) File "/path/to/sample_package/B.py", line 5, in <module> from ..sample_package import A ValueError: attempted relative import beyond top-level package
所謂的 top-level package
就是你所執行的 package 中最高的那一層,也就是 sample_package
。超過這一層的 relative import 是不被允許的,指的就是..sample_package
這行嘗試跳一層上去而超過 sample_package
了。
可以試試更改當前目錄到上一層(cd ..
),假設叫 parent_folder
,然後執行 python3 -m parent_folder.sample_package.B
,就會發現 error 消失了,因為現在的 top-level package
已經變成 parent_folder
了。
結語
Import 是各大語言必備功能,看似簡單,使用上來說陷阱卻頗多。如果搞不清楚 Python 中的 import 是怎麼運作的,除了在整體專案架構上難以靈活設計,更可能要陷入可怕的 error 海了。
我寫了一些額外的 sample code 放上 github 了,有不清楚的地方可以直接參考。