範式沒有不見,範式只是變成其他形狀了。
緣由
最近因為工作的關係需要學習使用 Python,不過作為一個有三年前端開發經驗的工程師,加上沒吃過 Python 也看過 Python 走路(?),因此學習的角度難免脫離一般沒有背景的人入門 Python 的方式,與其說是從頭學習一門程式語言,不如說是遷移我在 Javascript 上累積的經驗。
W3School 的教學安排我覺得就已經很清楚了:
不過對於我這樣一個有開發經驗的人而言,語法反而不是最重要的。
執行環境
Javascript::NVM → Python::pyenv
Javascript::NPM → Python::pip
Javascript::Yarn → Python::Poetry
Javascript
相較於其他程式語言,Javascript 的生態系除了它那容易令人誤解的名稱之外,還有規範 (Specification) 與實做 (Implementation) 分離的奇怪現象。
Javascript 本身是一團抽象規範,真正能夠執行的是實做,而開發者主要需要區分的實做分別是網頁瀏覽器 (Web Browser) 與 Node.js。
其中 Node.js 有著非常多的版本1,而這個版本也是開發者需要留意的對象,當程式碼與直譯器版本不批配時就會出現問題。
因此 Javascript 開發者通常不會直接安裝 Node.js,而是透過 NVM (Node Version Manager) 去決定與切換當前的 Node.js 版本。
接著是 Javascript 作為現代語言如何處理模組 (Module),每個專案資料夾都有各自儲存模組的路徑,並且為了減少複數個專案之間下載相同模組的時間,會在使用者的 home 留下快取。
主流的工具有 npm、 Yarn、 pnpm...,技術層面各有差異,不過在 Javascript 專案中擔任的角色是相同的。
值得一提的是,在 Javascript 的生態系中,「套件管理器」就不只一種,因此 Node.js 考慮引入 「套件管理器管理器」:Corepack。
Python
如同 Node.js 使用 NVM 解決 Node.js 版本眾多的問題,Python 使用 pyenv 解決:
# 安裝 pyenv
curl https://pyenv.run | bash
# 把 PATH 加入 `~/.bashrc`
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(pyenv init -)"' >> ~/.bashrc
除此之外 Python 有其他問題需要煩惱:
除了 Python 直譯器的安裝路徑外,套件的管理是另外一個問題:
因為專案之間直接使用相同的套件來源,當仰賴的套件版本衝突時就會發生相依性地獄,於是有一系列的工具為了解決這個問題而生:
venv
pipenv
- https://github.com/pypa/pipenv
- 24.7k ⭐
virtualenv
pyenv-virtualenv
conda
poetry
目前 Poetry 看起來是最好方案,安裝 Poetry:
curl -sSL https://install.python-poetry.org | python3 -
於是建立一個 Python 專案就變成像這樣了:
poetry new poetry-demo # 新增專案
cd peotry-demo
poetry env use python3 # 新增虛擬環境
poetry shell # 進入虛擬環境
git init
curl https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore > .gitignore
關於 Poetry 的介紹與使用這裡有一篇文章講的蠻完整的:
型別檢查
Javascript::Typescript → Python::Mypy
Javascript::tsconfig.json → Python::mypy.ini
Javascript
JavaScript 本身是一種動態型別/弱型別語言,這種特性給予了開發者極高的彈性,但容易造成很多基本的錯誤需要在執行期間 (runtime) 才能被發現。
Typescript 就是為了解決這個問題而誕生的。Typescript 的另外一個特性是「Javascript 的超集 (superset)」,不過在 ES6 已經普及的今天 (2024 年),這個特性已經不是最有價值的部份了。
透過 Typescript 定義的型別語法,加上 Linter (語法檢查器)與 IDE Hint (編輯器提示)等機制,可以在開發階段就避免或挑出低級錯誤而減少在執行期間除錯 (Debug) 的成本。
型別檢查的規則可以透過 tsconfig.json 來調整。
Python
Python 的動態型別特性是筆者一直以來對 Python 保有排斥心裡的原因之一,因此解決型別問題是筆者重返 Python 最高優先解決的問題。
值得慶幸的是 Python 確實陸續將型別標記之類的功能引入自己的標準之中,諸如:PEP3107、PEP484、PEP526、PEP561...。2
型別註釋能夠提供團隊成員對於變數型別的了解,不過仍不能真正解決型別誤用的情況,畢竟 Python 的型別註釋也只是註釋,並無法偵測誤用/錯用型別的情況。3:
雖然 Python 支援型別標記,卻仍然缺少型別檢查的實做,為了解決這個問題確實有不少型別檢查的工具:
mypy
pytype
pyre
pyright
Mypy,就決定是你了!
# Install type check tool
poetry add mypy
# Run type check
mypy .
如同 Typescript 的 tsconfig.json 一般,Mypy 可以透過 mypy.ini 來設定型別檢查的強度與規則。
更多關於型別的寫法見官方文件。
VSCode (Visual Studio Code)
Javascript
VSCode 因為自己本身就是 Typescript 寫的,所以對 Javascript/Typescript 可以說是原生支援,不需要額外掛擴充插件就能有不錯的開發體驗,不過可能需要調整一些設定之類的,例如:顯示參照。
Python
- 讓 VSCode 認得
pyproject.toml
- 讓 VSCode 認得 Python
基本語法
基本語法照著 w3school 的清單一個一個看過去大概清楚 Python 的基本語法了,我只會挑一些語法或名稱差異比較大的出來。
- 註解 (Comment)
- 變數 (Variables)
- 變數類型 (Data Types)
- https://www.w3schools.com/python/python_datatypes.asp
- Text Type:
str
- Numeric Types:
int
,float
,complex
- Sequence Types:
list
,tuple
,range
- Mapping Type:
dict
- Set Types:
set
,frozenset
- Boolean Type:
bool
- Binary Types:
bytes
,bytearray
,memoryview
- None Type:
NoneType
- 字串 (String)
- 布林值 (Boolean)
- 運算子 (Operators)
- 判斷式 (If ... Else)
- 迴圈 (While/For Loop)
- 函式 (Function)
- 作用域 (Scope)
- 資料容器
模板文字
Javascript
`My name is John, I am ${age}`
→ Python
f"My name is John, I am {age}"
在 Javascript 被稱作 Template literals 的東西,在 Python 則是透過 f-strings 達到類似的效果。
匿名函式
Javascript::Arrow Function → Python::Lambda Function
解構賦值
Javascript::Destructuring Assignment → Python:Unpack Javascript::Rest Parameters → Python::Arbitrary Arguments
W3 是把這個概念放在 Tuples 下講,但是我覺得既然是 List 和 Tuples 共用的特性應該分出來講,畢竟在 Javascript 這是一種叫做解構賦值 (Destructuring assignment) 的語法。
Javascript
let a, b;
[a, b] = [10, 20];
let t = [0, 1, 2, 3, 4];
[a, b, ...c] = t;
const func = (...args) =>
console.log(args)
Python
a, b = 10, 20
t = (0, 1, 2, 3, 4)
a, b, *c = t
def my_function(*args):
print(args)
這裡有一篇文章舉了更多 unpack 的例子4。
字典
Javascript::Map → Python::Dictionary
陣列
Javascript::Array → Python::List Javascript::TypedArray → Python::Array (NumPy)
和 Javascript 一樣,Python 的陣列不是「真的陣列」,而是一個可以變大變小的資料容器,並且這樣的容器在 Python 中被稱作 List。
在 Javascript 中可以透過 TypedArray 來創造真正意義上資料密集的陣列,但是在 Python 中這件事並不是內建在語言中的:
Python does not have built-in support for Arrays, but Python Lists can be used instead. To work with arrays in Python you will have to import a library, like the NumPy library.5
而是需要透過諸如 NumPy 之類的函式庫來達成。
進階語法
- 類別 (Class)
- 繼承 (Inheritance)
- 迭代器 (Iterators)
- Try Except
- User Input
- 檔案讀寫
- Generator Expressions as list comprehensions
模組 (Modules)
從模組引入特定的函式或物件:
Javascript
import { name1, name2 } from "module"
→ Python
from module import name1, name2
引入整個模組:
Javascript
import * as new_name from "module"
→ Python
import module as new_name
import module
在 Node.js 中可以在資料夾加入進入點來表達「這個資料夾是模組」,Python 也有類似的機制:
Node.js::index.js
→ Python::__init__.py
Node.js 之父後悔 index.js
這個設計又是另外一個故事了6。
全域匯入
Javascript
import "module"
→ Python
from module import *
Javascript 在模組機制還不發達的時期,引入函式庫的方式往往就是一團丟到全域變數去的東西,而這種方式很容易造成函式庫之間的命名衝突,對於這段歷史有興趣的同學可以了解一下一個名為 UMD (Universal Module Definition) 的工具,它身上有著 Javascript 模組大亂鬥的歷史。
在 Python 中有著類似匯入方式:
from random import *
print(randint(0, 5)) # It's random.randint
不過正如早期的 Javacsript 全域模組的問題一樣,這種語法在有成熟模組匯入機制的現在應該避免使用。
這裡有一篇講解更多關於 Python 匯入模組的文章:
內建模組
在 Node.js 中,「內建模組」的概念並不陌生,fs
, path
, os
都是 Node.js 的內建模組,這樣的設計可以避免過度膨脹原生的語法,而讓一些實做以模組的形式存在。
- Datetime
- Math
- JSON
- 正規表達式 (RegEx)
- Random
- asyncio
- Statistics
- cmath
Footnotes
-
Node.js — Node.js Releases. Retrieved 2024-06-09, from https://nodejs.org/en/about/previous-releases ↩
-
新的型態提示PEP. (林信良). Retrieved 2024-06-09, from https://www.ithome.com.tw/voice/140338 ↩
-
使用 Python typing 模組對你的同事好一點. (Amo Chen). Retrieved 2024-06-09, from https://myapollo.com.tw/blog/python-typing-module/ ↩
-
Unpack a tuple and list in Python. (nkmk). Retrieved 2024-06-09, from https://note.nkmk.me/en/python-tuple-list-unpack/ ↩
-
Python Arrays. (w3school). Retrieved 2024-06-09, from https://www.w3schools.com/python/python_arrays.asp ↩
-
Node.js 開發之父:「十個Node.js 的設計錯誤」- 以及其終極解決辦法. (David Ng). Retrieved 2024-06-09, from https://m.oursky.com/f0db0afb496e ↩