前言
在軟件工程中,我們從大的宏觀方向,要看業務目標、工程架構,到具體實施時就要選擇適合工程實現的編程語言和配套組件。在選擇編程語言時根據項目的不同,我們可能會有很多需要考慮的因素,從編程語言本身的角度來看,他們是“大同小異”的,但如果從差異角度看,每種編程語言除了語法體現不同外,執行方式、性能、內存管理、模塊組織、組件、第三方庫等又會有很大差異,但這些卻又是我們要需要考慮的關鍵點。
下面我們聊一下python語言的模塊引入方式,當我們知道了其引入方式,那么組織模塊就是自己的事了~~
當然,本文中不一定會列出所有引入方式,我們只關注常用的、個人認為較好的方式。
在開始聊這個話題前,我們首先要區分的概念是 package、module、class,我們粗略總結:
1. package是一個層級的概念,它通常是一個目錄,在package中可能會包含多個module;
2. module是一個文件(通常是package中的文件),在文件中我們可以定義類或函數等;
3. class 類,它被包含在module文件中;
然后,對於引用格式,我們做一個“感性”的總結就是:package要用“from”,module和class要用import,如:
from package import module
import package.module # 注意,不能這樣引用:import package.module.class
基本的模塊引入方式
為方便舉例說明,現假設有如下目錄結構及文件:
$ tree . ├── package1 │ ├── module1.py │ └── module2.py └── test.py $ cat package1/module1.py class class1: def hello(self): print('I am class1 in module1') $ cat package1/module2.py class class2: def hello(self): print('I am class2 in module2') $ cat test.py from package1 import module1 # 方式1 from package1.module2 import class2 # 方式2 注意,不能這樣引入:import package1.module2.class2 module1.class1().hello() class2().hello()
可以看到,test.py中展示了兩種基本的引入模塊和類的方式,並調用了hello方法。我們執行test.py,結果如下:
$ python3 test.py I am class1 in module1 I am class2 in module2
當然我們還可以用其他方式引入模塊,例如我們修改test.py文件為 :
import package1.module1 import package1.module2 from package1 import * # 注意,前面一定要有兩個import語句,否則是不正確的 module1.class1().hello() module2.class2().hello()
這種方式沒有語法錯誤,但個人不建議用這種方式,因為不太直觀。
進一步理解模塊的概念
細心的你可以已經注意到,上面的目錄結構中不能直接引入package1中的所有模塊(from package1 import * ),這是為什么呢?
其實原因很簡單:因為現在的package1還是一個普通的目錄,還算不上是一個真正意義上的package,需要在package下放一個__init__.py文件來標識當前目錄為一個package!
這里要說一下,我們在設計python程序的時候,只要是我們認為應該是一個package的目錄都應該包含一個__init__.py文件(即使該文件為空)。
下面我們在package1目錄下添加空的__init__.py文件,並修改test.py文件內容:
$ tree . ├── package1 │ ├── __init__.py │ ├── module1.py │ └── module2.py └── test.py
$ cat test.py
from package1 import *
module1.class1().hello()
module2.class2().hello()
如果此時執行test.py會發現依然報錯!
這是因為,雖然此時package1是一個“package”了,但__init__.py文件內容為空(我們可以把__init__.py文件看做是一個package的“對外接口”,可以起到屏蔽“包”內細節的做用)。使用__init__.py文件一般需要注意以下兩點:
1. __init__.py文件內一般使用 __all__ 關鍵字來限定package內對外的模塊。
2. __init__.py文件盡量“輕”,即最好不要在里面定太多代碼(如定義類等),雖然這樣做不會報錯。
下面通過修改__init__.py文件和test.py文件來分別說明__init__.py文件的使用方式:
$ cat package1/__init__.py __all__ = ['module1', 'module2'] $ cat test.py from package1 import * module1.class1().hello() module2.class2().hello()
# 運行腳本
$ python3 test.py
I am class1 in module1
I am class2 in module2
當然,如果__init__.py文件內包含某個類,package外面也可以直接訪問到,如:
$ cat package1/__init__.py __all__ = ['module1', 'module2'] class class3: def hello(self): print('I am class3 in __init__.py') $ cat test.py from package1 import module1, module2, class3 module1.class1().hello() module2.class2().hello() class3().hello() $ python3 test.py I am class1 in module1 I am class2 in module2 I am class3 in __init__.py
注意,test.py 文件中指明了需要引入的內容 (from package1 import module1, module2, class3),如果將這個引入方式改為全部引入(from package1 import * )則會報錯,因為 import * 引入的package內容需要__init__.py文件中__all__關鍵字指定!
當然,如果要引入具體module的所有內容,可以通過 import * 的方式,如:from package1.module1 import *
更復雜的引用
我們可以想到,引用還有很多其他情況,如不同package間、不同目錄下或不同層級目錄下的相互引用等。
其實,所有的引用,在了解了以上原理后都是很好了解的,不過這里還要先提一下”環境變量“,關於環境變量概念和作用這里略過。python添加環境變量的方法:
sys.path.append('user_path') # 其中 user_path 即我們要添加的路徑
添加到環境變量中的路徑,在本工程中各文件中都可以直接訪問到。下面我們利用和之前相似的例子進行說明。
假設我們現在有一個和package1同級的包,package2,且package2中有一個moudle3.py文件,此時package1中的module2文件需要訪問package2的module3.py,文件目錄及內容如下:
$ tree . ├── package1 │ ├── __init__.py │ ├── module1.py │ └── module2.py ├── package2 │ ├── __init__.py │ └── module3.py └── test.py $ cat package1/__init__.py __all__ = ['module1', 'module2'] $ cat package1/module1.py class class1: def hello(self): print('I am class1 in module1') $ cat package1/module2.py from package2.module3 import class3 # 這里我們引用了package2的class3 class class2: def hello(self): print('I am class2 in module2') $ cat package2/__init__.py __all__ = ['module3'] $ cat package2/module3.py class class3: def hello(self): print('I am class3 in module3 which of package2') $ cat test.py import sys, os curr_dir = os.path.dirname(os.path.realpath(__file__)) sys.path.append(curr_dir) # 這里我們添加了環境變量 from package1 import module1, module2 module1.class1().hello() module2.class2().hello() module2.class3().hello() # 通過package1的module2引用到了package2中module3下的class3
請注意其中的注釋。test.py 腳本運行結果如下:
$ python3 test.py I am class1 in module1 I am class2 in module2 I am class3 in module3 which of package2
根據我們工程的實際情況可能會有很多更復雜的情況,其原理是不變的。