理解Python的 relative 和 absolute import

2018/03/18 posted in  python comments

在開發python project時,通常都會以package分開各部件,貫徹"Separation of concerns"。可是如果對python modules的概念不清晰,各部件之間的import可以成為project的大問題。

以下會以case study的形式解釋python import的常見問題:

Python module 基本概念

Definition

  • Module: 任何*.py
  • Package: a collection of modules

What's the difference between a Python module and a Python package?
Module vs. Package?

為方便起見,以下可能把modulepackage混合使用。

把directory變成package

建立一個__init__.py檔,如:

.
└── some_package
    ├── some_module.py
    └── __init__.py

然後可以import some_package.some_module

Import自己旁邊的.py files

假設以下folder:

.
├── file_a.py
└── file_b.py
  • file_a.py:
from file_b import hello

hello()
  • file_b.py:
def hello():
    print('Hello world!')

執行:

$ python file_a.py
Hello world!

一切如預期一樣。 from file_b import hello 其實用的是 Absolute import,在Python的權威--PEP 328提到:

import foo
...
it is proposed that foo will always be a module or package reachable from sys.path. This is called an absolute import.

所以由python 2.5開始,所有直接import xxx都會是top-level module。在上述的case中,因為user是在current directory執行script,所以所有同level的files全都被當作top-level module,可以被直接import

file_b.py變成package

.
├── file_a.py
└── file_b
    └── __init__.py

這樣file_b就成為了一個package。

Absolute import的問題

Absolute import有一個很大的限制,就是所有module的名稱要unique,才不會有ambiguity。因為sys.path裡的module不止你的project files,還有在 environment 中安裝了的所有library。

Relative import (Case 1)

於是當然是用relative import解決啊,不然要幹嘛?

把整個directory變成module:

.
├── __init__.py
├── file_a.py
└── file_b.py

使用relative import:

from .file_b import hello

然後......就Error了

# python3
Traceback (most recent call last):
  File "file_a.py", line 1, in <module>
    from .file_b import hello
SystemError: Parent module '' not loaded, cannot perform relative import

# python2
Traceback (most recent call last):
  File "file_a.py", line 1, in <module>
    from .file_b import hello
ValueError: Attempted relative import in non-package

因為from .file_b == 從當前module中import另一個叫file_b的submodule

但執行python file_a.py時,python是不會把current directory當作一個module看的。即是說__init__.py在這裡並沒有甚麼卵用。

Relative import from parent (Case 2)

假設掉過頭來,你想在file_b取用file_a

.
├── file_a.py
└── file_b
    └── __init__.py

file_b/__init__.py:

from ..file_a import hello  # ".."代表parent directory

hello()

然後:

$ python file_b/__init__.py

一切好像很合理,可是還會error

__init__.py要怎麼用呢?

正確的Relative import

然而並沒有正確的做法,因為以上兩個case都沒有錯!!!

只是根本不應該這樣run!Relative Import只能用在package裡面,而package裡面的scripts是不應該直接run的

例如可以加一層parent:

.
├── main.py
└── some_package
    ├── __init__.py
    ├── file_a.py
    └── file_b.py

main.py

from some_package import file_a

然後執行:

$ python main.py
hello world!

總結:

  1. Modules之間可以用、也應該要用relative import,來避免absolute import撞名的問題。
  2. 不可以直接run package裡面的script。

其他注意事項

避免module裡有definition以外的東西

在上面的例子中,只是在main.pyimport file_a,就直接顯示了hello world!。這是因為file_a.py裡面直接執行了hello()

在寫一個modular的python project時,要盡量避免這些"Side effects",否則在使用modules時很容易出現各種bug。

修正

if __name__ == '__main__':
    hello()

這樣file_a.py只會在直接run的時候才會執行hello.py(通常都是用來debug)。