Skip to main content

從 Javascript 到 Python 的範式遷移 (Paradigm Migration)

· 13 min read
Wei Ji
info

範式沒有不見,範式只是變成其他形狀了。

緣由​

最近因為工作的關係需要學習使用 Python,不過作為一個有三年前端開發經驗的工程師,加上沒吃過 Python 也看過 Python 走路(?),因此學習的角度難免脫離一般沒有背景的人入門 Python 的方式,與其說是從頭學習一門程式語言,不如說是遷移我在 Javascript 上累積的經驗。

W3School 的教學安排我覺得就已經很清楚了:

不過對於我這樣一個有開發經驗的人而言,語法反而不是最重要的。

執行環境​

info

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 直譯器的安裝路徑外,套件的管理是另外一個問題:

因為專案之間直接使用相同的套件來源,當仰賴的套件版本衝突時就會發生相依性地獄,於是有一系列的工具為了解決這個問題而生:

目前 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 的介紹與使用這裡有一篇文章講的蠻完整的:

Python 套件管理器——Poetry 完全入門指南

型別檢查​

info

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,就決定是你了!

# 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​

基本語法​

基本語法照著 w3school 的清單一個一個看過去大概清楚 Python 的基本語法了,我只會挑一些語法或名稱差異比較大的出來。

模板文字​

info

Javascript

`My name is John, I am ${age}`

→ Python

f"My name is John, I am {age}"

在 Javascript 被稱作 Template literals 的東西,在 Python 則是透過 f-strings 達到類似的效果。

匿名函式​

info

Javascript::Arrow Function → Python::Lambda Function

解構賦值​

info

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。

字典​

info

Javascript::Map → Python::Dictionary

陣列​

info

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 之類的函式庫來達成。

進階語法​

模組 (Modules)​

從模組引入特定的函式或物件:

info

Javascript

import { name1, name2 } from "module"

→ Python

from module import name1, name2

引入整個模組:

info

Javascript

import * as new_name from "module"

→ Python

import module as new_name
import module

在 Node.js 中可以在資料夾加入進入點來表達「這個資料夾是模組」,Python 也有類似的機制:

info

Node.js::index.js → Python::__init__.py

Node.js 之父後悔 index.js 這個設計又是另外一個故事了6。

全域匯入​

info

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 匯入模組的文章:

Python 的 Import 陷阱

內建模組​

在 Node.js 中,「內建模組」的概念並不陌生,fs, path, os 都是 Node.js 的內建模組,這樣的設計可以避免過度膨脹原生的語法,而讓一些實做以模組的形式存在。

Footnotes​

  1. Node.js — Node.js Releases. Retrieved 2024-06-09, from https://nodejs.org/en/about/previous-releases ↩

  2. 新的型態提示PEP. (林信良). Retrieved 2024-06-09, from https://www.ithome.com.tw/voice/140338 ↩

  3. 使用 Python typing 模組對你的同事好一點. (Amo Chen). Retrieved 2024-06-09, from https://myapollo.com.tw/blog/python-typing-module/ ↩

  4. Unpack a tuple and list in Python. (nkmk). Retrieved 2024-06-09, from https://note.nkmk.me/en/python-tuple-list-unpack/ ↩

  5. Python Arrays. (w3school). Retrieved 2024-06-09, from https://www.w3schools.com/python/python_arrays.asp ↩

  6. Node.js 開發之父:「十個Node.js 的設計錯誤」- 以及其終極解決辦法. (David Ng). Retrieved 2024-06-09, from https://m.oursky.com/f0db0afb496e ↩