13 | 搭建積木:Python 模塊化

目錄

 

一、簡單模塊化

二、項目模塊化

三、神奇的if__name__=='__main__'

四、總結

五、思考題


一、簡單模塊化

可以把函數、類、常量拆分到不同的文件,把它們放在同一個文件夾,然後使用 from your_file import function_name,class_name 的方式調用,之後,這些函數可以在文件內直接使用。

# utils.py

def get_sum(a, b):
    return a + b
# class_utils.py

class Encoder(object):
    def encode(self, s):
        return s[::-1]

class Decoder(object):
    def decode(self, s):
        return ''.join(reversed(list(s)))
# class_utils.py

class Encoder(object):
    def encode(self, s):
        return s[::-1]

class Decoder(object):
    def decode(self, s):
        return ''.join(reversed(list(s)))

以上代碼,get_sum()函數定義在utils.py,Encoder和Decoder類則在class_utils.py可以直接在main函數直接調用from import,就可以將我們需要的東西import過來。

文件結構如下:

.
├── utils
│   ├── utils.py
│   └── class_utils.py
├── src
│   └── sub_main.py
└── main.py

很容易可以看出,main.py調用目錄的模塊時,只需要使用.代替/來表示子目錄,utils.utils表示utils子文件理的utils.py模塊。

除了一些特殊的情況,import必須位於最前端。

我們還需要在模塊所在的文件夾新建一個__init__.py,內容可以爲空,也可以用來表述包對外暴露的模塊接口,不過,事實上,這是python2的規範。python3中,__init__.py並不是必須的。

二、項目模塊化

在Linux中,以/開頭,來表示從根目錄到葉子節點的路徑,eg:/home/ubuntn/Desktop/my_project/test.py這種方法叫做絕對路徑。

另外,對於任意二個文件,我們都有一條通路可以從一個文件到另一個文件。eg:/home/ubuntu/Downloads/example.json。如果從test.py訪問,需要寫成../../Downloads/example.json,表示上一層目錄,這種方法表示相對路徑。

通常一個python文件在運行時,都會有一個位置,最開始爲這個文件所在的文件夾,當然,這個路徑可以被改變。運行sys.path.append("..")則可以改變當前python解釋器的位置。相對路徑不是一種好的選擇,因爲如果代碼遷移,相對位置會使得重構,也會出錯。對於一個獨立的項目,所有的模塊的追尋方式,最好從項目的根目錄開始,這叫絕對路徑。

絕對路徑的優點:

(1)簡化依賴管理

(2)版本統一,不存在使用一個新模塊,卻導致一系列函數崩潰的情況,並且所有的升級都需要通過單元測試纔可以繼續。

(3)代碼追溯,可以容易的追溯,一個API是從哪裏被調用的,它的歷史版本是怎麼迭代開發的,產生變化的。

做項目時,不可能把全世界的代碼都放在一個文件下,但是類似模塊化的思想還是要有的,那就是以項目作爲最基本的目錄,所有的模塊調用,都要通過根目錄一層層向下索引的方式來import.

可以使用pycharm來創建一個項目,項目結構爲:

.
├── proto
│   ├── mat.py
├── utils
│   └── mat_mul.py
└── src
    └── main.py

 

# utils/mat_mul.py

from proto.mat import Matrix

def mat_mul(matrix_1: Matrix, matrix_2: Matrix):
    assert matrix_1.m == matrix_2.n
    n, m, s = matrix_1.n, matrix_1.m, matrix_2.m
    result = [[0 for _ in range(n)] for _ in range(s)]
    for i in range(n):
        for j in range(s):
            for k in range(m):
                result[i][k] += matrix_1.data[i][j] * matrix_2.data[j][k]

    return Matrix(result)
# src/main.py

from proto.mat import Matrix
from utils.mat_mul import mat_mul


a = Matrix([[1, 2], [3, 4]])
b = Matrix([[5, 6], [7, 8]])

print(mat_mul(a, b).data)

########## 輸出 ##########

[[19, 22], [43, 50]]

這個例子和上面的像,但注意utils/mat_mul.py,會發現,它import Matrix的方式是from proto.mat這種做法,直接從項目目錄中導入,並依次向下導入模塊mat.py中和Matrix,而不是使用..導入上一級文件夾。

接下來和項目都是使用pycharm來構建。把不同模塊放在不同文件裏,跨模塊調用是從頂層直接索引,一步到位,非常方便。

實際上,python解釋器遇到到import時,它會在一個特定的列表中尋找模塊,,這個特定的列表,可以在下面方式得到:

import sys  

print(sys.path)

########## 輸出 ##########

['', '/usr/lib/python36.zip', '/usr/lib/python3.6', '/usr/lib/python3.6/lib-dynload', '/usr/local/lib/python3.6/dist-packages', '/usr/lib/python3/dist-packages']

第一項爲空,是因爲將第一項高爲根目錄的絕對地址。這樣,每次運行main.py,import函數在執行時,都會去項目根目錄中找相應的包。

使一般的python環境楞也能做到,兩種方法

import sys

sys.path[0] = '/home/ubuntu/workspace/your_projects'

三、神奇的if__name__=='__main__'

python也可以直接寫代碼,if __name__ == '__main__'。

項目結構如下:

.
├── utils.py
├── utils_with_main.py
├── main.py
└── main_2.py
# utils.py

def get_sum(a, b):
    return a + b

print('testing')
print('{} + {} = {}'.format(1, 2, get_sum(1, 2)))
# utils_with_main.py

def get_sum(a, b):
    return a + b

if __name__ == '__main__':
    print('testing')
    print('{} + {} = {}'.format(1, 2, get_sum(1, 2)))
# main.py

from utils import get_sum

print('get_sum: ', get_sum(1, 2))

########## 輸出 ##########

testing
1 + 2 = 3
get_sum: 3
# main_2.py

from utils_with_main import get_sum

print('get_sum: ', get_sum(1, 2))

########## 輸出 ##########

get_sum_2: 3

import 在導入文件時,會自動把所有暴露在外面的代碼全都執行一遍,如果你要把一個東西封裝成模塊,又想讓它可以執行,必做將執行代碼放在if __name__=='__main__'下面。

其實,__name__作爲python的魔術內置參數,本質上是模塊對象的珍上屬性,使用import語句時,__name__就會賦值爲該模塊的名字,自然不等於__main__了。

四、總結

1.通過絕對路徑和相對路徑,我們可以import模塊。

2.在大型工程中模塊化非常重要,模塊的索引通過絕路徑來做,而絕對路徑從程序的根目錄開始

3.記着巧用if __name__ == ‘__main__’來避開import時執行。

五、思考題

from module_name import *和import module_name有什麼區別?

from module_name import *是導入module_name內所有內容,可以直接調用內部方法;import module_name,則是導入module_name在代碼中必須寫成module_name.function的形式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章