從 JavaScript 角度學 Python(15) - 模組 (Module)

前言

接下來聊聊 Python 中的模組 (Module) ,畢竟 JavaScript 也有所謂的模組觀念,

模組 (Module)

如果你有使用過 Webpack、Vue Cli 等等的工具,想必對於模組的概念就會有一定的認知與熟悉度,如果你還沒有使用過 Webpack 等工具的話,或許你有看到 export default 這段程式碼,例如:

1
2
3
4
5
6
7
8
9
10
11
/* app.js */
export default {
sayHello() {
console.log('Hello Ray');
},
myName: 'Ray',
obj: {
myName: 'Ray',
},
arr: [1, 2, 3],
};

不然就是 Node.js 開發者常用的 module.exports 語法:

1
2
3
4
5
6
7
/* exports.js */
module.exports = 'Hello World'

/* main.js */
const hello = require('exports');

console.log(hello); // Hello World

而現階段比較有名的應該是 ESM (ES6 Modules or JavaScript Modules),如果你不熟悉 ESM 的話,你也可以考慮閱讀我先前寫的文章 什麼是 ESM(ES6 Modules or JavaScript Modules) 呢? 這邊會有更詳細的介紹。

所以我這邊也簡單的做一下關於模組的小結論:

模組的概念簡單來講就是一個模組代表著一個檔案,而這個檔案內通常會包含許多可以給予我們使用的函式也可以說是功能等等,而這個檔案可以讓我們在不同的之間檔案之間引入使用。

舉例來講,我們可能有五個 Python 檔案,都會使用到加法與打招呼這個功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ch1.py
print(1 + 2)
print(f'Hello Ray!')

# ch2.py
print(10 + 23)
print(f'Hello Mike!')

# ch3.py
print(12 + 6)
print(f'Hello Ming!')

# ch4.py
print(105+ 9)
print(f'Hello Jack!')

# ch5.py
print(189 + 256)
print(f'Hello Charles!')

看起來是不是好像沒有什麼了不起的對吧?那麼你試想一下,當我們如果要使用的的頁面高達 100 頁,那麼勢必你可能要寫相同的程式碼 100 次,當然你也有可能想說,好像也還好一直複製貼上就好了,假設今天你已經複製貼完 100 頁了,然後隔天老闆跟你說要你把 Hello 改成中文是不是又要改一百次?或許你可能會想說:「哼哼,我用取代功能就好了!」,對你真棒!

你真棒

阿不是,上面的迷因只是開玩笑而已。

但是如果善加利用模組化的話,你只需要改那一隻模組的檔案就好了,所以就讓我們來了解一下該如何將上面範例程式碼模組化吧!

匯出模組

前面老樣子,我們先講講 JavaScript 的模組匯出方式,這邊先舉例 ESM 的匯出:

1
2
3
4
5
6
7
8
9
10
11
/* app.js */
export default {
sayHello() {
console.log('Hello Ray');
},
myName: 'Ray',
obj: {
myName: 'Ray',
},
arr: [1, 2, 3],
};

匯入的時候則必須使用 import 匯入模組:

1
2
3
4
5
6
7
<script type="module">
import app from './app.js';
console.log(app.myName); // 'Ray'
app.sayHello(); // Hello Ray
console.log(app.obj.myName); // Ray
console.log(app.arr[0]); // 0
</script>

oh!對了,ESM 還有一個特色,也就是作用域都是獨立的,意指你無法跨 script module 取得另一個 script module 的變數。

將鏡頭轉回到 Python 吧!

那麼 Python 該如何撰寫模組呢?其實非常非常的簡單,只需要講相關函式拆成另一個檔案,然後使用 import 引入就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
# module.py
def add(a, b):
return int(a + b)

def sayHi(name):
return f'Hello {name}!'

# example.py
import module

print(module.add(100, 25)) # 125

print(module.sayHi('Ray')) # Hello Ray!

有沒有覺得超簡單~

超簡單

關於 import 的部分我們稍後再聊。

透過上面的範例我們可以得知,Python 在模組化確實是非常方便,只需要建立一個檔案並宣告函式,然後將其結果 return 回去就可以了,相對 JavaScript 模組化時還必須使用 export 語法來匯出。

當然在做模組可能不只有函式也有可能是字典,那 Python 呢?寫法會因此有所變化嗎?

其實沒有,一樣宣告一個字典就可以了,而且不用 return 就可以直接使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# module.py
def add(a, b):
return int(a + b)

def sayHi(name):
return f'Hello {name}!'

dic = {
'myName': 'Ray',
}

# example.py
import module

print(module.add(100, 25)) # 125

print(module.sayHi('Ray')) # Hello Ray!

print(module.dic['myName']) # Ray

是不是超級方便,再來看一次海綿寶寶

超簡單

匯入模組

接下來聊聊比較簡單的東西,也就是匯入語法 import 的部分。

在上面範例中就已經有開始使用 import,而這種單純只是 import 特定檔案的方式其實就是全部匯入的一種,所以你可以直接使用 module.py 檔案中所有的函式與字典。

但是其實 import 還有其他種用法,舉例來講在前面範例中的 import module,我們可以看到每次要使用模組中的函式時,都必須 module.add or module.dic 等等,這時候 module 這個名字就會顯得很長很麻煩,因此這時候你可以使用 import 中的 as 來重新命名模組要匯入的名稱,屆時你就可以直接使用新的名稱呼叫:

1
2
3
4
5
6
7
8
# example.py
import module as mu

print(mu.add(100, 25)) # 125

print(mu.sayHi('Ray')) # Hello Ray!

print(mu.dic['myName']) # Ray

前面也有講到,如果只是單純的 import 模組,代表著你是將一整包模組匯入到這個檔案中,可是有時候我們只是要使用裡面的特定功能,所以這時候就可以使用 form 來解決這個需求。

舉例來講,我要指定只匯入 dis 字典的話就只需要這樣寫:

1
2
3
4
# example.py
from module import dic

print(dic['myName']) # Ray

上面的意思就是「從 xxx 匯入 xxx 方法」的意思,相較 JavaScript 的匯入則是:

1
import app from './app.js';

兩者寫法可以看出是不同的,一個是 form 開始,另一個則是 import 開始,從 JavaScript 角度來講大意就是:「匯入 xxx 來源是 xxx」。

我絕對不會說我在寫 Python 的時候一直寫長 import ... from ...

dir

最後我想額外補充一個我覺得滿實用的東西,也就是 dir(),這個函式有什麼用途呢?簡單來講就是它可以列出當前作用域範圍內有哪些變數與方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
# dir.py
def add(a, b):
print(a, b)
return int(a + b)

def sayHi(name):
return f'Hello {name}!'

dic = {
'myName': 'Ray',
}

print(dir()) # ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'add', 'dic', 'sayHi']

這邊先不看 __xxx__ 這種,直接看沒有雙下底線的類型,你可以看到在這個頁面上宣告的方法與字典都會被列出來,而 dir 除了用於看當前執行範圍的變數與範圍之外,你也可以拿來查看模組:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# module.py
def add(a, b):
return int(a + b)

def sayHi(name):
return f'Hello {name}!'

dic = {
'myName': 'Ray',
}

# example.py
import module

print(module) # ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'add', 'dic', 'sayHi']

除此之外 dir 還可以查看串列與字典有哪些方法可以使用:

(下述輸出結果我已經有稍微整理過了。)

1
2
3
print(dir([])) # ['append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

print(dir({})) # ['clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']

有沒有覺得 Python 的 dir 方法與 JavaScript 的 console.dir 有異曲同工之妙呢?

help

最後來講講 help 這個函式,相信看到這個函式名稱的人應該大部分都已經知道它的用途了,它可以用於查看模組與函式的詳細說明:

1
help('def')

我先說如果你查看 def 的話內容會超級無敵詳細又超級無敵的長,所以我只截取部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Function definitions
********************

A function definition defines a user-defined function object (see
section The standard type hierarchy):

funcdef ::= [decorators] "def" funcname "(" [parameter_list] ")"
["->" expression] ":" suite
decorators ::= decorator+
decorator ::= "@" assignment_expression NEWLINE
parameter_list ::= defparameter ("," defparameter)* "," "/" ["," [parameter_list_no_posonly]]
| parameter_list_no_posonly
parameter_list_no_posonly ::= defparameter ("," defparameter)* ["," [parameter_list_starargs]]
| parameter_list_starargs
parameter_list_starargs ::= "*" [parameter] ("," defparameter)* ["," ["**" parameter [","]]]
| "**" parameter [","]
parameter ::= identifier [":" expression]
defparameter ::= parameter ["=" expression]
funcname ::= identifier

A function definition is an executable statement. Its execution binds
the function name in the current local namespace to a function object
(a wrapper around the executable code for the function). This
function object contains a reference to the current global namespace

那如果用於我們的模組也可以有類似的效果,但是就不會出現這麼詳細的介紹了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# module.py
def add(a, b):
return int(a + b)

def sayHi(name):
return f'Hello {name}!'

dic = {
'myName': 'Ray',
}

# example.py
import module

print(help(module))

help

這時候或許有人會問該如何像 help(def) 一樣可以出現說明,其實非常簡單使用註解就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# NAME: 這一行會成為模組的描述說明

# FUNCTIONS: 非常簡單的計算,但回傳會是整數
def add(a, b):
return int(a + b)

# FUNCTIONS: 這是一個跟人打招呼的方法
def sayHi(name):
# 註解說明必須放在 def 之前,放在 def 之內不會出現
return f'Hello {name}!'

# DATA 類的不會出現註解說明
dic = {
# 就算寫在這裡也一樣
'myName': 'Ray',
}

# example.py
import module

print(help(module))

相信看到這邊你應該就知道 dirhelp 這兩個函式有多方便,那麼今天就先到這邊結束吧 :D

作者的話

這幾天收到動保處的通知,主要內容是在講我家狗狗沒有結紮請盡快帶去結紮,否則將會開罰,雖然我家狗狗年紀已經約 7~8 歲了,但是經過醫生專業評估後還是決定讓她結紮會比較好,不得不說我一開始是保持反對態度,但是聽完醫生建議後還是乖乖照做比較好,也是為了狗兒健康為主。

關於兔兔們

兔法無邊

Liker 讚賞 (拍手)

如果這一篇筆記文章對你有幫助,希望可以求點支持或 牡蠣 鼓勵 (ノД`)・゜・。

Liker 是一個按讚(拍手)的讚賞機制,每一篇文章最多可以按五下(拍手),按讚過程你是完全不用付費的(除非你想要每個月贊助我 :D),你只需要登入帳號就可以開始按讚。
而 Liker 會依據按讚數量分配獎金給創作者,所以如果你願意按個讚我會非常感謝你唷。

Google AD

撰寫一篇文章其實真的很花時間,如果你願意「關閉 Adblock (廣告阻擋器)」來支持我的話,我會非常感謝你 ヽ(・∀・)ノ