Python 變數、型別與運算:從一張收據開始
用一支合購分帳小程式,帶你掌握變數賦值、動態型別、四大基本型別、算術與邏輯運算子、型別轉換與整數除法,最後深入 Python 萬物皆物件的記憶體模型。
從一張收據開始:讓程式記住每一筆數字
想像你正在寫一支小程式,幫同學算這學期合購教科書的分帳。三個人合買一本 720 元的書、運費 60 元,要算出每人該付多少、找零幾元。你大可以把 720、60、3 這些數字直接散落在算式裡,但只要書價一改,你就得在整段程式裡到處翻找、逐一修改——這正是「變數」要解決的問題。
變數讓程式可以「記住」一個值,並用一個有意義的名字稱呼它。改一處,全程式跟著變。這一篇,我們就從變數出發,一路走到型別、運算子與型別轉換,把 Python 處理資料的基本骨架建立起來。建議你打開一個 Python 直譯器(在終端機輸入 python 或 python3),每讀到一段程式就親手敲一次,感受會完全不同。

變數與賦值:名字綁到值
在 Python 裡,建立變數就是用等號 = 把一個值「賦」給一個名字:
book_price = 720 # 書價
shipping = 60 # 運費
people = 3 # 分攤人數
total = book_price + shipping
per_person = total // people # 每人付多少(整數除法,稍後會解釋)
print(total) # 輸出:780
print(per_person) # 輸出:260
這裡有幾個值得注意的地方。等號 = 不是數學上的「相等」,而是「賦值」這個動作:把右邊算出的值,綁到左邊的名字上。所以 total = book_price + shipping 是先算出右邊的 780,再把它綁給 total。
正因為如此,下面這種寫法在數學上荒謬,在程式裡卻完全合理:
count = 10
count = count + 1 # 先取出舊的 count(10)加 1,再綁回 count
print(count) # 輸出:11
變數命名請遵循 Python 的慣例(PEP 8 精神):使用小寫加底線的 snake_case,名字要能說明用途。per_person 比 pp 或 x 清楚得多,未來的你會感謝現在的你。
# 推薦:見名知意
student_count = 42
# 反模式:意義不明,請避免
sc = 42
x = 42
動態型別:值有型別,名字沒有
C、Java 這類語言要求你先宣告變數的型別(int x = 5;),之後 x 就只能裝整數。Python 不是這樣——它是動態型別(dynamically typed):型別屬於「值」,不屬於「名字」。同一個名字,前一刻綁整數、下一刻綁字串,完全合法:
data = 100 # 此刻 data 綁的是整數
data = "hello" # 現在 data 改綁字串,沒問題
data = 3.14 # 再改綁浮點數,也沒問題
這帶來彈性,但也要求你心裡清楚每個變數「現在」裝的是什麼。想知道某個值的型別,用內建函式 type():
print(type(100)) # 輸出:<class 'int'>
print(type(3.14)) # 輸出:<class 'float'>
print(type("hello")) # 輸出:<class 'str'>
print(type(True)) # 輸出:<class 'bool'>
四種最基本的型別
剛起步時,你會反覆遇到四種內建型別:整數、浮點數、字串、布林。
整數 int:沒有小數點的數,Python 的整數可以無限大(只受記憶體限制),不像許多語言有 32 位元上限:
big = 2 ** 100 # 2 的 100 次方
print(big) # 輸出:1267650600228229401496703205376
print(type(big)) # 輸出:<class 'int'>
浮點數 float:帶小數點的數。只要式子裡出現小數,結果通常就是浮點數:
height = 1.72
print(type(height)) # 輸出:<class 'float'>
print(type(10 / 3)) # 輸出:<class 'float'>(除法 / 永遠回傳 float)
字串 str:用單引號或雙引號包起來的文字。兩者效果相同,挑一種風格保持一致即可:
name = "陳同學"
greeting = '你好'
full = greeting + "," + name # 用 + 串接字串
print(full) # 輸出:你好,陳同學
布林 bool:只有 True 和 False 兩個值(注意首字母大寫),用來表達「成立與否」。它其實是整數的特例,True 等於 1、False 等於 0:
is_passed = True
print(is_passed + 1) # 輸出:2(True 當作 1 參與運算)
運算子:算術、比較、邏輯
算術運算子做數學計算:
print(7 + 3) # 輸出:10 加
print(7 - 3) # 輸出:4 減
print(7 * 3) # 輸出:21 乘
print(7 / 3) # 輸出:2.3333333333333335 除(結果是 float)
print(7 ** 2) # 輸出:49 次方
比較運算子問一個是非題,結果一定是布林值:
print(5 > 3) # 輸出:True
print(5 == 5) # 輸出:True 注意:判斷相等用 ==,不是 =
print(5 != 3) # 輸出:True != 是「不等於」
print(5 <= 4) # 輸出:False
請特別記住:= 是賦值,== 才是比較相等。把兩者搞混是初學者最常見的錯誤之一。
邏輯運算子 and、or、not 把布林值組合起來:
age = 20
has_id = True
print(age >= 18 and has_id) # 輸出:True 兩者都成立才 True
print(age < 18 or has_id) # 輸出:True 只要一個成立就 True
print(not has_id) # 輸出:False 反轉真假
當多種運算子混用時,優先順序是:算術 → 比較 → 邏輯。不確定時,用括號把意圖寫清楚,既不會算錯,也讓讀者一眼看懂:
score = 85
result = (score >= 60) and (score <= 100)
print(result) # 輸出:True
型別轉換:在型別之間搬運
不同型別有時需要互相轉換。Python 提供同名的轉換函式 int()、float()、str()、bool()。
最常見的場景是處理使用者輸入。input() 拿到的永遠是字串,要拿來計算就得先轉成數字:
text = "42"
number = int(text) # 字串轉整數
print(number + 8) # 輸出:50
price = float("3.99") # 字串轉浮點數
print(price) # 輸出:3.99
count = 7
message = "共 " + str(count) + " 人" # 數字轉字串才能串接
print(message) # 輸出:共 7 人
轉換不是萬能的。把一個不像數字的字串硬轉成 int 會直接報錯:
int("abc") # ValueError: invalid literal for int() with base 10: 'abc'
int() 把浮點數轉整數時是直接捨去小數(往零的方向截斷),不是四捨五入,這點容易踩雷:
print(int(3.9)) # 輸出:3 小數被捨去,不是 4
print(int(-2.7)) # 輸出:-2 往零截斷
bool() 的轉換規則也值得認識:0、0.0、空字串 ""、None 都會轉成 False,其餘多半是 True:
print(bool(0)) # 輸出:False
print(bool("")) # 輸出:False
print(bool(42)) # 輸出:True
print(bool("a")) # 輸出:True
None:明確表示「沒有值」
有時你需要表達「這裡目前還沒有值」,而不是 0 或空字串。Python 為此提供一個特殊物件 None,它自成一個型別 NoneType:
answer = None
print(answer) # 輸出:None
print(type(answer)) # 輸出:<class 'NoneType'>
None 常用來當變數的初始狀態,或表示「函式沒有回傳任何東西」。判斷一個變數是不是 None,慣例是用 is,而不是 ==:
selected = None
# 推薦寫法
if selected is None:
print("尚未選擇") # 輸出:尚未選擇
# 反模式:能動但不符慣例,請避免
# if selected == None:
整數除法與取餘:分得乾乾淨淨
回到開頭的分帳問題。一般除法 / 會給你帶小數的結果,但「三個人分十個雞蛋」這種問題,你要的是「每人幾個、剩幾個」,這就要用整數除法 // 和取餘 %:
eggs = 10
people = 3
print(eggs // people) # 輸出:3 每人 3 個(整數除法,捨去小數)
print(eggs % people) # 輸出:1 剩下 1 個(餘數)
這兩個運算子用途極廣。判斷奇偶、做進位、把秒數拆成分與秒,都靠它們:
seconds = 200
print(seconds // 60, "分", seconds % 60, "秒") # 輸出:3 分 20 秒
n = 17
print(n % 2 == 0) # 輸出:False(餘數不為 0,所以是奇數)
動手寫一段:合購分帳機
把這一篇學到的東西組合起來,寫一支完整的分帳小程式:
# 合購分帳機
book_price = 720 # 書價
shipping = 60 # 運費
people = 3 # 分攤人數
total = book_price + shipping # 總金額
per_person = total // people # 每人應付(整數元)
remainder = total % people # 無法整除的零頭
# 組出可讀的報告(數字要轉成字串才能串接)
report = (
"總金額:" + str(total) + " 元\n"
+ "每人應付:" + str(per_person) + " 元\n"
+ "零頭:" + str(remainder) + " 元(由發起人吸收)"
)
print(report)
# 預期輸出:
# 總金額:780 元
# 每人應付:260 元
# 零頭:0 元(由發起人吸收)
試著把 book_price 改成 700,再跑一次,看看 per_person 與 remainder 怎麼變化。你會發現:因為用變數記住了書價,整支程式只要改那一行,其餘自動跟著更新——這正是開頭那張收據想說明的價值。
重點回顧:初學者最常踩的雷
=與==不一樣。=是賦值(把值綁給名字),==是比較是否相等。在if條件裡誤用=是經典錯誤。/永遠回傳浮點數。即使10 / 2也會得到5.0而非5。需要整數結果就用//。int()是截斷不是四捨五入。int(3.9)得到3。要四捨五入請改用round()。- 數字與字串不能直接相加。
"共 " + 7會報TypeError,必須先str(7)轉成字串,或改用 f-string。 - 判斷
None用is None,而不是== None,這是 Python 社群的明確慣例。
深入探討(研究所視角):萬物皆物件
前面我們把變數說成「名字綁到值」,現在把這句話講透。在 Python 裡,萬物皆物件(everything is an object)——整數、字串、布林、函式、甚至型別本身,全都是物件。每個物件在記憶體中有三個固有屬性:身分(identity)、型別(type)、值(value)。身分可以用內建函式 id() 取得,它在 CPython 中對應到該物件的記憶體位址:
a = 256
print(id(a), type(a)) # 例如:140703... <class 'int'>
賦值 a = 256 真正發生的事,是建立(或取得)一個值為 256 的整數物件,再讓名字 a 指向它。所以變數不是「裝值的盒子」,而比較像是「貼在物件上的標籤」。多個名字可以貼在同一個物件上:
x = [1, 2, 3]
y = x # y 與 x 指向「同一個」串列物件
print(x is y) # 輸出:True(is 比較的是身分,不是值)
print(id(x) == id(y)) # 輸出:True
這就帶出 Python 最重要、也最容易出錯的分野:可變(mutable)與不可變(immutable)。
int、float、str、bool、tuple、None 都是不可變的。你無法改變一個整數物件本身;所謂「修改」其實是讓名字改指到另一個新物件:
n = 10
print(id(n)) # 假設是位址 A
n = n + 1
print(id(n)) # 換成位址 B——n 指向了一個全新的物件 11,原本的 10 沒被改動
而 list、dict、set 是可變的。你可以原地修改物件內容,身分(id)不變:
nums = [1, 2, 3]
print(id(nums)) # 假設是位址 C
nums.append(4)
print(nums) # 輸出:[1, 2, 3, 4]
print(id(nums)) # 仍是位址 C——同一個物件,只是內容變了
可變性的陷阱在於共享參考(shared reference)。承接前面的例子,因為 y = x 讓兩個名字指向同一個串列,透過任一個名字修改,另一個也會「看到」變化:
x = [1, 2, 3]
y = x
y.append(99)
print(x) # 輸出:[1, 2, 3, 99]——明明只動了 y,x 也變了
若你要的是一份獨立副本,必須明確複製,例如 y = x[:] 或 y = x.copy()。對不可變物件就沒有這個煩惱,因為它根本無法被原地修改。
最後一個常讓人困惑的細節:CPython 會快取小整數(通常是 −5 到 256)與部分短字串,讓相同的值共用同一個物件以節省記憶體。這導致 is 在小數字上「看似」可用:
a = 256
b = 256
print(a is b) # 輸出:True(256 在快取範圍內,共用物件)
c = 257
d = 257
print(c is d) # 輸出:可能 False(超出快取範圍,是兩個不同物件)
這正是為什麼比較「值是否相等」要用 ==,比較「是否為同一物件」才用 is——is None 之所以正確,恰恰因為整個程式裡 None 永遠是獨一無二的同一個物件。理解了物件模型,你對賦值、參考與可變性的直覺就會從「會用」升級為「知其所以然」,這在日後處理函式參數傳遞、預設可變參數等進階陷阱時,會是你最可靠的判斷依據。