跳至主要内容

變數

變數

變數是什麼?為什麼要使用變數?

想像一下,在教室裡面有 50 個學生,如果你想點名某位同學來問答問題,你會怎麼做?是「那位紅色外套長頭髮的同學」還是「第 3 排的第 2 位同學」?如果每位同學的桌上都有名牌或座號,應該就能更精準的點到指定的同學。

電腦程式也是類似的概念,在電腦的世界裡有很多的資料或數值,如果能適當的給這些資料一個標籤或名字,在取用的時候會方便的多,變數(Variable)大概就差不多這個意思。

在 Python 裡要使用變數的話,可以這樣寫:

a = 1450
b = "hello"

上面這兩行的意思就是有個名為 a 的變數,它的值是「數字」 1450,另外有個名為 b 的變數,它的值是 "hello" 這幾個「文字」。幫資料或數值指定一個名稱或標籤,這個行為稱為「宣告(Declaration)」。有些程式語言在宣告變數的時候要在前面加個 letvar,或是指定這個變數的型別,但在 Python 可以不用做這件事,就像這樣直接就能使用,不需要特別指定它是數字或文字型態(其實要也行,在本章後半段會再另外介紹)。

在後面的章節還會介紹更多除了數字跟文字之外的資料型態,不過變數就只是個名牌、標籤或是指標的概念而已,變數本身是沒有型別的。除了一次命名一個變數外,你也可以一次處理好幾個:

x, y, z = 1, 2, 3

這樣一口氣就可以產生 3 個變數 xyz,分別指向數字 12 以及 3

你知道嗎?

Python 的變數多重指定寫法,背後的原理其實是 Tuple(元組)資料型態,所以上面的寫法也可以寫成:

x, y, z = (1, 2, 3)

然後再利用 Tuple 的「開箱(Unpacking)」功能來把 Tuple 的值分別指定給 xyz 這三個變數,在後續介紹到 Tuple 你就會知道這是怎麼一回事了。

是說,如果想要用這種方式指定變數,但只想要有 xz 變數卻不想要有 y 變數的話,可以使用 _ 來跳掉它:

x, _, z = 1, 2, 3

這個 _ 在 Python 又稱之匿名變數(Anonymous Variable)或虛擬變數(Dummy Variable),通常用來表示這個變數「I don't care」的意思。不要誤會,底線 _ 在 Python 是個合法的變數名稱,所以如果你要拿它來用是沒問題的:

x, _, z = 1, 2, 3
print(_) # 印出 2

通常對這種我們不在乎或懶得想名字的變數,會給它一個底線 _ 來表示。如果用在 Python 的 REPL 的話,_ 還能用來代表「上一次的結果」:

>>> 1 + 2
3
>>> _ + 123
126

好啦,現在我們有了變數之後,後續就可以透過變數來取用這些值:

print(a)  # 印出 1450
print(b) # 印出 hello

雖然一開始變數 a 是數字 1450,但如果要改變的話,就直接這樣寫:

a = "world"  # 把原本變數 a 變成 "world"
print(a) # 印出 world

直接改就行了,就算從數字改成文字也行。

雖然在 Python 宣告變數不需要什麼特別的語法,但變數需要先宣告才能用,如果突然莫名其妙的使用一個不存在的 girl_friend 變數,像這樣:

print(girl_friend)

執行的時候就會發生錯誤了:

$ python hello.py
Traceback (most recent call last):
File "/demo/hello.py", line 1, in <module>
print(girl_friend)
^^^^^^^^^^^
NameError: name 'girl_friend' is not defined

哇!出現了好多沒看過的東西...別擔心,仔細看的話應該會發現錯誤訊息還算清楚,它跟你在 hello.py 這個檔案的第 1 行,這個 girl_friend 變數還沒有定義,醒醒吧,它是不存在的,所以才會出現 NameError 這個錯誤訊息。

常數(Constant)

有些程式語言有「常數」的設計,常數的目的是希望這個變數的值一經宣告之後就不要再修改,但是在 Python 並沒有常數的設計,如果在 Python 裡如果想要做出類似常數的效果的話,慣例上可以在宣告變數的時候用全大寫的方式來命名,例如:

PI = 3.14159
GRAVITY = 9.8
BOOK_TITLE = "為你自己學 Python"

但這個只是個「慣例」,要改隨時都能改,全大寫只是看起來比較顯眼,目的是希望其他人如果要改動到它的時候多看兩眼。

變數命名

命名規則

在 Python 你想要用什麼名字來命名你的變數都沒關係,用英文、日文或中文都行,雖然是這樣,但還是有一些限制:

1. 變數名稱必須是一般文字或底線 _ 開頭

一般文字可以是英文字母,使用中文或日文也都可以,例如:

_name = "5xCampus"
書名 = "為你自己學 Python"
なまえ = "悟空"

Python 的變數名稱有支援多國語言,但還是建議使用英文字母為主。

2. 變數名稱可以使用數字,但不能放在開頭

data2Value = 1        # 這個沒問題
5xcampus = "五倍學院" # 這不行,會造成語法錯誤

3. 大小寫是不一樣的名字

ace = 100
Ace = "黑桃 A"

print(ace) # 印出 100
print(Ace) # 印出 黑桃 A

4. 不能使用保留字(Reserved Keyword)

Python 本身有留一些名字給自己用,目前使用的 3.12.7 版總共有以下這 35 個:

FalseNoneTrueandasassert
asyncawaitbreakclasscontinuedef
delelifelseexceptfinallyfor
fromglobalifimportinis
lambdanonlocalnotorpassraise
returntrywhilewithyield

這些保留字大多是 Python 本身會用到的語法,如果你直接拿來當變數名稱的話會造成語法錯誤(SyntaxError):

if = 123     # 不行,這會出錯
pass = True # 這也不行

如果你好奇 Python 有哪些關鍵字的話,可以打開啟終端機進到 REPL 環境,透過內建的 keyword 模組查到目前使用的 Python 有哪些保留字:

>>> import keyword
>>> keyword.kwlist
['False', 'None', 'True', 'and', ...略..., 'yield']
>>> len(keyword.kwlist)
35

這裡可以暫時先不用太在意 import 語法或是 keyword 模組是什麼意思,相關的細節在後續的章節都會有詳細的說明。透過 Python 內建的 len() 函數可以算出來目前的保留字總共有 35 個。

除了用 keyword 模組列出關鍵字之外,在 REPL 環境中也可以用 help 函數查詢:

>>> help("keywords")

Here is a list of the Python keywords. Enter any keyword to get more help.

False class from or
None continue global pass
...略...
await finally nonlocal yield
break for not

除了一般的關鍵字之外,在 Python 3.10 版之後有加入了「軟」的關鍵字(Soft Keyword),同樣也可以透過 keyword 模組查到:

>>> keyword.softkwlist
['_', 'case', 'match', 'type']

Python 的關鍵字在任何情況下都不能用來當變數名稱,使用的話會直接出現語法錯誤,不過 Soft Keyword 一般情況下不算是關鍵字,只有在特定的情況下,例如後面章節會介紹到模式匹配(Pattern Matching)會用到的 matchcase,這些 Soft Keyword 就會被解析成特殊的意義,平常想要拿來當做變數名稱是可以的,例如:

>>> match = 123
>>> print(match)
123

>>> _ = "hello"
>>> print(_)
hello

這樣不會發生語法錯誤。是說,可以是可以,但程式能不能正常運作就是另一回事了:

>>> type = 123

# 想看看 "hello" 字串是什麼型別...
>>> type("hello")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable

另外有些 Python 內建函數的名字也都不是關鍵字,用來當做變數的名稱雖然不會出錯,但應該也不會是你想要的結果,例如:

print = "Hello World"
print(123) # 這裡會...?

這裡我故意用 print() 這個內建函數來命名變數,同樣也會造成原本的 print() 函數沒辦法正常運作:

TypeError: 'str' object is not callable

經過我們這樣的操作之後,print 變數的值是 "Hello World",而不是原本的 print() 函數了。所以在變數命名的時候,最好也盡量避開這些內建的函數。以目前 Python 的 3.12.7 版本,內建函數大概有 70 個左右,詳細的列表可參考官網文章說明。

網站連結

命名慣例

同樣是幫變數取名字,在電腦程式世界的命名方式也有幾種常見的慣例:

1. 駝峰式(CamelCase)

開頭小寫,中間會使用大寫字母來突顯單字與單字之間的區隔,在視覺上更容易辨識,例如:

firstName
bookStore
myStudentScore

也有另一種是一開始就大寫:

FirstName
BookStore
MyStudentScore

這種首字大寫的駝峰式命名法,又稱「Pascal 命名法(Pascal Case)」

2. 蛇式命名法(snake_case)

以小寫字母為主,單字與單字之間會使用底線分隔,例如:

first_name
book_store
my_student_score

正因為使用底線做為分隔,上上下下的看起來像一條蛇的形狀,故稱之蛇式命名法。

3. 烤肉串命名法(Kebab-Case)

這種命名法是使用 - 符號來做為分隔,例如:

first-name
book-store
my-student-score

為什麼叫烤肉串?因為 first-name 中間的 - 看起來就像是烤肉串裡的那根竹籤,而 firstname 就像是烤肉被串在一起。雖然這個命名方式看起來挺有趣的,但大部份的程式語言的命名都不支援使用 - 字元,倒是比較常在寫 HTML 或 CSS 的時候看到這種命名方式。

不管是哪一種命名慣例就都只是慣例,不照著做也沒關係,只是看起來可能有點奇怪而已。以 Python 來說,通常變數以及函數的命名會使用蛇式命名法(這跟 Python 的中文翻譯是蟒蛇無關),而類別的命名則是使用駝峰式命名法。如果各位以後有機會在 Python 專案裡看到變數或函數使用駝峰式的命名法,大概就可以從這裡猜出來程式碼的作者原本是寫什麼程式的。

命名與程式碼可讀性

先給大家看一下這段程式碼:

a = 18
print(a) # 印出 18

上面這段程式碼很簡單也沒什麼問題,可是這個變數 a 是什麼用途?你剛寫完沒多久的當下可能還記得這個變數是什麼用途,或如果程式碼比較簡單也能猜的出來,但隨著時間或程式碼行數的增加,你可能需要吃點銀杏才能想起這個變數的用途是什麼,要想想為什麼當初這個變數要設定成數字 18

There are only two hard things in Computer Science: cache invalidation and naming things

Phil Karlton

幫東西取名字很重要但也很不容易,舉個例子,如果你有一隻白色的貓,你會不會想幫牠取名叫「小黑」?身為主人的你想要這麼做當然可以,而且牠本貓可能也不介意,但旁邊的人可能會覺得這個主人有點...嗯,特別。對電腦程式來說,變數用什麼名字都無所謂,只要不要出錯就好,但如果有好的名字,例如:

age = 18
print(age) # 印出 18

不管是變數還是後面才會介紹到的函數或方法,有個容易理解、容易識別的名字是很重要的,程式碼雖然可能只寫一次,但是後續可能會被修改很多次,對以後看這段程式碼的同事(或是幾個星期後的自己)來說有個好名字會更容易理解,程式碼的可讀性(Readability)會更好一些。

《練習》變數交換

假設目前有兩個變數 gokuginyu

goku = "悟空"
ginuy = "基紐"

我希望把這兩個變數的值交換過來,該怎麼做?想像一下你面前有兩個杯子,一個杯子裡放了紅色藥丸,另一個放了藍色藥丸。你希望把這兩個杯子裡的藥丸換過來,但一次只能拿一個藥丸,同時一個杯子裡也規定只能裝一顆藥丸。如果你沒有像賭聖一樣的特異功能的話,基本上是做不到的,但如果你有第三個杯子就可以輕鬆做到了。一樣的概念,我這裡用一個暫時的變數來幫忙:

goku = "悟空"
ginuy = "基紐"

frog = goku # 先把 goku 移轉到 frog
goku = ginuy # 再把 ginuy 移轉到 goku
ginuy = frog # 最後把 frog 移轉到 ginuy

print("goku =", goku)
print("ginuy =", ginuy)

這裡我額外使用 frog 變數來當做暫存區,幾次輪轉之後就能達到交換的效果,這算是滿常見的交換變數值的手法。但在 Python 可以寫的更簡單:

goku = "悟空"
ginuy = "基紐"

goku, ginuy = ginuy, goku

print("goku =", goku)
print("ginuy =", ginuy)

不需要額外的暫存變數,一行就搞定了,相當方便。這裡可以偷偷劇透一下,這跟前面可以一次宣告多個變數是一樣的概念,也是利用 Python 的 Tuple 的特性辦到的。別急,後續有專門介紹 Tuple 的章節,現在就先有個印象就好。

刪除變數

變數宣告之後,如果不需要的話應該怎麼處理?因為「宣告」這個行為表示你要請電腦分一小塊記憶體給你存放這個變數,本著「有借有還再借不難」的好習慣,照理說如果確定之後不會再用到的話,應該要把佔用的記憶體還給系統。有些程式語言是需要手動做這件事的,但在 Python 不用我們操心,Python 有自己的資源回收機制(Garbage Collection)會幫我們搞定這些事情。

如果你宣告了變數但因為某些原因現在就不想用了,你可以使用 Python 內建的 del 關鍵字來把變數刪掉:

sister = "小花"
print(sister) # 印出 小花

del sister
print(sister) # 這裡會...?

因為刪除了 sister 變數,所以 sister 變數就變成沒有定義,如果要使用它就會出現 NameError 的錯誤訊息:

NameError: name 'sister' is not defined. Did you mean: 'iter'?

不過大家不要誤會,del 並不是刪除 "小花" 字串,使用 del 關鍵字也不是歸還記憶體的意思。當使用 del 關鍵字的時候,實際上做的事是把 sister 這個變數跟 "小花" 字串之間的連結給斷開,然後把 sister 這個變數刪掉,所以變成無法使用。接下來由於這個 "小花" 字串在沒有其他變數參照(reference)到它的時候,這個字串再過不久就會被 Python 的資源回收機制收掉,原本佔用的記憶體就會讓出來給其他有需要的資料使用。但如果這個字串還有其他變數在參照到它,Python 的資源回收機制會等所有的參照都消失之後才會把這個字串給清掉。

如果你覺得這個有點難或太複雜也沒關係,因為大部份時候你並不需要手動做這件事。

使用者輸入

我們撰寫的程式,偶爾會需要跟使用者有一些「互動」,如果是網頁程式的話,比較多是透過表單(Form)之類的方式,但如果是一般的程式,可能會是透過文字介面(Command-Line Interface,簡稱 CLI)。透過 Python 內建的 input() 函數可以取得使用者輸入的內容:

檔案:demo.py
print("請輸入你的名字:")
user_name = input()
print("哈囉," + user_name)

這裡我先印出一行提示訊息,希望使用者輸入名字,接著使用內建函數 input() 取得使用者輸入的內容並且把內容存到 user_name 變數裡面,最後再印出一行歡迎詞。我把上面這幾行程式碼存成 demo.py 檔案,然後在終端機執行這段程式碼之後,你會先在畫面上看到一行提示訊息,然後畫面會暫時卡住,這時候你可以試著敲打鍵盤,隨便打幾個字,結束之後再按下 Enter 鍵,就會看到:

$ python demo.py
請輸入你的名字:
凱蒂
哈囉,凱蒂

你剛剛輸入的內容就會變成 user_name 變數的內容,並且被印在畫面上了。

宣告型別?

剛剛前面也看到了,我們在 Python 宣告變數不需要事先講它是什麼內容,這是 Python 的特性之一,專有名詞稱為「動態型別(Dynamic Typing)」。這表示你可以在程式中隨時變更變數所指向的資料的型別,例如:

age = 18
age = "hello"

print(age) # 印出 hello

原本一開始 age 是數字 18,但後來又它改成文字 "hello",這在 Python 是沒問題的。在 Python 3.5 版本之後提供了「型別註記(Type Annotation)」的功能,讓你可以在宣告變數的時候明白的告訴 Python 變數所指向的資料是什麼型別,像這樣:

age: int = 18

print(age)

age 後面的 int 就是型別註記,這表示 age 變數的型別是整數,int 是 Integer 的意思,對有些人來說這樣的寫法可以讓程式碼更容易閱讀,一看就知道這個變數是什麼型別,不過更大的好處是可以讓一些開發工具或程式碼檢查套件更容易找出潛在的錯誤。舉個例子,以 VSCode 來說,如果沒有加上型別註記,當我寫到 age 並按下小數點 . 的時候,Python 大概只能從後面接的數字猜它應該是個數字,所以會提示數字型態可以用的方法:

VSCode 提示數字型態可以用的方法

如果加上 :str 的型別註記,VSCode 就會認為這個變數應該是文字,給出來的提示就會更精準的只給文字型態可以使用的方法:

VSCode 提示字串型態可以用的方法

這樣的確會方便一些。不只是變數,函數的參數和回傳值也可以加上型別註記:

def add(a: int, b: int) -> int:
return a + b

不過註記就只是註記,即使這樣寫,我們也只能期待這個變數「應該」是個數字或這個參數「應該」是整數型態。Python 並不會因為你這樣寫就強制變數一定要是指定的型別而在幫你檢查出錯誤,所以這段程式碼跟之前的程式碼的效果其實是一樣的,如果你想要的話,還是可以把 age 設定成文字:

age: int = 18
age = "hello"

print(age) # 印出 hello

也就是說這種型別註記就只是僅供參考而已,加上型別註記比較像是在撰寫技術文件給自己或其他隊友看的,不是給 Python 看的。Python 雖然不會檢查,但可以透過其他外部工具來幫忙檢查,目前比較有人氣的型別檢查工具 mypy 就可以幫我們做這件事,有興趣的話可以透過 pip 安裝一下:

$ pip install mypy

然後就可以請它幫我們檢查程式碼型別是否正確:

$ mypy demo.py
demo.py:2: error: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment]
Found 1 error in 1 file (checked 1 source file)

這樣就能抓出型別不正確的地方。

加註型別的寫法可能有人在其他程式語言看過了,不是什麼新鮮的事情,但補充一個可能比較少人知道的冷知識,在 Python 的型別註記不一定只能乖乖的寫 intstr 這樣的內建型別,根據 Python 的規格說明,它可以是任何合法的 Python 表達式(Expression),例如:

# 奇怪的型別註記
age: "hello world" = 18

print(age) # 印出 18

這可以也能順利執行,甚至再惡搞一點:

# 這已經不知道在寫什麼了...
def add(a: (1 > 2), b: [1, 2, 3]) -> 1 + 2:
return a + b

print(add(10, 20)) # 印出 30

這都是合法的 Python 程式碼。這樣寫雖然不會出錯,但這樣寫在太過於奇葩,我自己是不會這樣寫啦,只有在想要展示自己懂一些別人不懂的冷知識的時候才會這樣寫(例如現在),如果要加型別註記還是乖乖的用 intstr 之類的內建型別就好,別拿石頭砸自己或同事的腳。

我自己過去經手過的專案用的程式語言大多都沒有這麼嚴格的型別檢查,而是靠自動化測試的方式來確保程式是否正確。對我來說,這二十幾年來已經自由自在習慣了,所以我個人不會特別偏好加註型別的寫法,特別在 Python 的型別註記又只是僅供參考,沒有實際的強制力,因此在本書的範例大部份也不會使用型別註記的寫法。

你不需要認同我對於型別註記的看法,如果你認為加註型別對你來說有幫助,不妨可試試看,更多關於型別註記的資訊可以參考官方文件說明。

工商服務

想學 Python 嗎?我教你啊 :)

想要成為軟體工程師嗎?這不是條輕鬆的路,除了興趣之外,還需要足夠的決心、設定目標並持續學習,我們的ASTROCamp 軟體工程師培訓營提供專業的前後端課程培訓,幫助你在最短時間內建立正確且扎實的軟體開發技能,有興趣而且不怕吃苦的話不妨來試試看!