post / 2023.06.27
Python基礎
本文合併了原先的四篇 Python 筆記,去掉了重複與無關內容,作為日常速查使用。範例基於 Python 3。
變數與物件
- 變數不需要宣告型別,但使用前必須賦值,否則拋出
NameError。 - Python 沒有
const,也沒有任何機制阻止變數被重新賦值,所謂「常數」只是約定用全大寫命名。 =是賦值,==是比較。
>>> age
NameError: name 'age' is not defined
用物件導向的方式理解:物件是資料實體,變數是貼在物件上的標籤。
a = "Jack" # 建立字串物件 "Jack",再把標籤 a 貼上去
b = a # 把標籤 b 貼到同一個物件上,而不是複製
id(a) == id(b) # True,兩者指向同一個物件
a = "Ross" # 換一個物件,貼上 a 標籤;b 仍指向 "Jack"
- 物件可以掛多個標籤,一個變數同時只能指向一個物件。
- 變數本身沒有型別,型別屬於物件。所以
a = 1之後a = "1"完全合法。
賦值與切片
= 是右結合的,a = b = c = 1 表示三者都指向整數物件 1。
a, b, c = 1, 2, 3 # 分別賦值
age = 17 + 1 # 先算右邊再賦值
切片適用於字串、元組、串列,語法為 obj[a:b:c]:
| 寫法 | 含義 |
|---|---|
obj[a:b] | 取索引 $[a, b)$,不含 b;省略 a 從 0 開始,省略 b 到結尾 |
obj[a:b:c] | 每 c 個取一個,c 省略時為 1 |
c 為負數 | 由後往前反向取 |
obj[::-1] | 反轉(得到新物件,原物件不變) |
>>> name = "polaris"
>>> name[1:3]
'ol'
>>> name[::-3]
'sap'
>>> name[::-1]
'siralop'
切片還可用於複製:y = x[:] 得到一份淺拷貝,再對 y 排序不會影響 x。
字串
- 單引號和雙引號等價,不區分單字元與字串(沒有獨立的 char 型別)。
- 三引號可以寫多行字串。
- 引號內使用同類引號需要轉義
\"。
三種格式化方式
name, age = "布洛妮娅", 18
print('My wife is %s, she is %d.' % (name, age)) # % 方法,最古老
print('My wife is {}, she is {}.'.format(name, age)) # format 方法
print(f'My wife is {name}, she is {age}.') # f-string,推薦
%方法後面接 tuple 或 dict,按順序或按名字取值。format的{}內可用索引{0}或關鍵字{name}。- f-string 需要 Python 3.6+,可讀性最好;三者都支援寬度、對齊、精度等格式控制。
數字與布林
| 型別 | 說明 |
|---|---|
int | 整數,支援 0x 十六進位、0o 八進位、0b 二進位 |
float | 浮點數,支援科學記號 1.23e-6 |
complex | 複數,寫作 a + bj 或 complex(a, b) |
bool | 布林值,True 本質是 1,False 本質是 0 |
NoneType | 空值,None 的首字母必須大寫 |
- 整數與浮點數混合運算時,整數會被提升為浮點數:
1 + 2.0得3.0。 - 浮點數存在精度誤差:
0.1 + 0.2 == 0.3結果是False(實際為0.30000000000000004)。需要精確計算時用decimal模組。 round()採用銀行家捨入(四捨六入五取偶):round(0.5)得0,round(1.5)得2,round(2.5)得2。
假值:0、0.0、None、空字串、空串列、空元組、空字典、空集合、False。
真值:其他一切,包括非空字串(哪怕內容是 "False")。
運算子
算術運算子
| 運算子 | 含義 | 範例 |
|---|---|---|
+ - * | 加減乘 | |
/ | 除法,結果一定是浮點數 | 6 / 3 → 2.0 |
// | 整除,向下取整 | 7 // 2 → 3,-7 // 2 → -4 |
% | 取餘數(模) | 7 % 2 → 1 |
** | 次方,右結合 | 2 ** 2 ** 3 → 256 |
- | 一元負號,優先序低於 ** | -2 ** 2 → -4 |
其他運算子
| 類別 | 運算子 |
|---|---|
| 比較 | == != < <= > >=,可以連用:a > b == c 等價於 a > b and b == c |
| 邏輯 | and or not |
| 成員 | in not in |
| 身分 | is is not,比較是否引用同一物件(比較 id,不是比較值) |
| 位元運算 | & | ^ ~ << >> |
| 複合賦值 | += -= *= //= **= &= |= ^= <<= >>= |
and / or 回傳的是運算元本身,不是 True / False:
>>> True and 0 + 3
3
>>> False or 31
31
>>> 0 or 0
0
優先序(由高到低)
() → 索引 [] → 屬性 . → ** → 一元 ~ + - → * / % // → + - → << >> → & → ^ → | → 比較(< == in is 等同一級)→ not → and → or → 賦值
記不住就加括號,代價最低。
輸入輸出與內建函式
print(self, *args, sep=' ', end='\n', file=None) # sep 分隔符,end 結尾字元
age = input("請輸入年齡:") # 回傳值永遠是字串
int(age) # 需要數字時顯式轉換
常用內建函式:
| 函式 | 作用 |
|---|---|
id(x) | 物件的記憶體位址(身分識別) |
type(x) | 物件的型別,如 <class 'int'> |
len(x) | 元素個數 |
abs() round() bool() | 絕對值、四捨五入、布林轉換 |
enumerate(seq, start=0) | 迴圈時同時取索引和值 |
range() zip() sum() max() min() sorted() | 常用序列工具 |
型別轉換函式:int(x, base)、float()、complex()、str()、repr()、chr()、ord()、hex()、oct()、eval()、list()、tuple()、set()、frozenset()、dict()。
轉換不是無條件的,取決於原資料是否含有足以表達目標型別的資訊:int("123") 可行,int("Hello") 會報錯;串列可以轉集合,但轉不成一個數字。
隱式轉換遵循精度順序:bool < int < float < complex,混合運算時向高精度靠攏。
galgames = ['咖啡馆', '千恋万花', '天使骚骚']
for index, galgame in enumerate(galgames, 1):
print(f'今天玩第{index}部作品{galgame}。')
判斷與迴圈
if condition1:
...
elif condition2:
...
else:
...
for 用於走訪任何可迭代物件(串列、字典、字串、元組、生成器……):
for x in 序列:
...
for i in range(5):
...
break:跳出當前這一層迴圈。continue:跳過本次剩餘部分,進入下一次迴圈。for ... else/while ... else:迴圈正常結束時執行else;被break中斷則不執行。
while 判斷條件:
...
寫 while 時務必確認迴圈體最終能改變判斷條件或觸發 break,否則會無窮迴圈。
四種容器
| 容器 | 字面量 | 有序 | 可變 | 元素可重複 | 元素要求 |
|---|---|---|---|---|---|
串列 list | [1, 2] | 是 | 是 | 是 | 任意 |
元組 tuple | (1, 2) | 是 | 否 | 是 | 任意 |
字典 dict | {k: v} | 插入序 | 是 | 鍵不可重複 | 鍵必須可雜湊 |
集合 set | {1, 2} | 否 | 是 | 否(自動去重) | 必須可雜湊 |
list
a = [] # 空串列
a.append(x) # 尾端追加
a.insert(i, x) # 在索引 i 處插入,原元素後移
a.extend(b) # 把 b 的元素逐個接到 a 後面
a.pop() # 彈出尾端元素(回傳該元素)
a.pop(i) # 彈出索引 i 的元素
a.remove(x) # 刪除第一個值為 x 的元素,不存在則報錯
a.index(x) # 第一個值為 x 的索引
a.count(x) # 值為 x 的元素個數
a.reverse() # 原地反轉
a.sort(key=..., reverse=False) # 原地排序,無回傳值
del a[i] # 刪除索引 i
del a[1:3] # 刪除切片範圍
排序範例:
random = [(2, 2), (3, 4), (4, 1), (1, 3)]
random.sort(key=lambda e: e[1]) # 按每個元組的第二個元素排序
# [(4, 1), (2, 2), (1, 3), (3, 4)]
注意:
sort()只有key和reverse兩個參數,cmp參數在 Python 3 中已被移除。
tuple
- 元組建立後不可修改,沒有增刪改。
- 單元素元組必須帶逗號:
(1,)是元組,(1)只是整數。 - 轉換:
list(t)得到串列,tuple(l)得到元組,原物件不變,回傳的是新物件。
dict
profile = {'name': '孤筝', 'age': 19}
profile2 = dict(name='孤筝', age=19) # 用等號時鍵不帶引號
profile3 = dict([('name', '孤筝'), ('age', 19)]) # 由鍵值對序列建構
profile4 = {i: i ** 2 for i in range(2, 5)} # {2: 4, 3: 9, 4: 16}
取值與增改:
profile['name'] # 鍵不存在時拋 KeyError
profile.get('gender') # 不存在回傳 None
profile.get('gender', 'male') # 不存在回傳預設值
profile.setdefault('gender', 'male') # 不存在則寫入該鍵值對並回傳
profile['gender'] = 'male' # 新增或修改
del profile['age']
profile.pop('age')
關鍵區別:
get()只讀,不會改動字典;只有setdefault()會在鍵缺失時寫入。
判斷鍵是否存在用 in / not in;has_key() 是 Python 2 的寫法,Python 3 已刪除。
set
s = {1, 2, 2, 3} # 自動去重為 {1, 2, 3}
s = set() # 空集合只能這樣寫,{} 是空字典
| 操作 | 方法 | 運算子 |
|---|---|---|
| 聯集 | a.union(b) | a | b |
| 交集 | a.intersection(b) | a & b |
| 差集(在 a 不在 b) | a.difference(b) | a - b |
| 對稱差(只屬於其中一個) | a.symmetric_difference(b) | a ^ b |
- 增:
add(x)(只能加不可變型別)、update(seq)(可接字串、串列、元組、集合;接字典只取其鍵)。 - 刪:
remove(x)元素不存在會報錯;discard(x)不會報錯;pop()刪除並回傳任意一個元素,不能傳參數;clear()清空。 - 判斷:
x in s、a.isdisjoint(b)(無交集回傳 True)、b.issubset(a)。 - 集合無序無索引,既不能按索引取值,也不能修改元素,只能增刪。
常見誤解:
pop()刪的是「任意」元素,不是「隨機」元素。集合的儲存順序由雜湊值決定,看起來隨機,但並不可控。
迭代器與生成器
可迭代物件:能用 for 走訪的物件。滿足下面任一條即可:
- 實作了
__iter__(),回傳一個迭代器實例; - 沒有
__iter__(),但實作了__getitem__()且按索引取元素可行。
迭代器物件:iter(可迭代物件) 的回傳值,用 next() 逐個取值,取完後拋出 StopIteration。
from collections.abc import Iterable, Iterator
isinstance([], Iterable) # True,串列是可迭代物件
isinstance([], Iterator) # False,但串列本身不是迭代器
isinstance(iter([]), Iterator) # True
也就是說:串列是可迭代物件,不是迭代器,一個串列可以反覆走訪;而迭代器只能往前走一次。
生成器是「用函式寫的迭代器」:
# 串列推導式得到串列
alist = [i for i in range(5)] # [0, 1, 2, 3, 4]
# 換成圓括號得到生成器
agen = (i for i in range(5)) # <generator object ...>,惰性求值
注意:
(i for i in ...)是生成器表達式,不是元組推導式——Python 裡沒有元組推導式。要得到元組請寫tuple(i for i in ...)。
yield 與 return 的區別:函式執行到 yield 會暫停並交出值,但函式沒有結束;下次取值時從暫停處繼續,直到函式結束。yield 不帶值則回傳 None。
def generator():
i = 0
while i < 5:
i += 1
yield i
gen = generator()
next(gen) # 1
for v in generator():
print(v) # 1 2 3 4 5
生成器的價值在於惰性:不會一次性把全部結果算出來放進記憶體,處理大資料流時比串列更省記憶體。
其他筆記
註解
# 單行註解
'''
三引號嚴格來說是「多行字串」而不是註解:
它會被解譯器當作一個字串物件建立出來,只是一個未被使用的表達式。
'''
正因為它本質是字串而非註解,所以在程式碼中間隨意插入三引號會破壞語法,可能直接報錯。正規做法是只用 # 寫註解。
編碼格式
- Python 3 原始檔預設就是 UTF-8,通常不需要宣告編碼。
- 只有 Python 2 需要檔頭宣告,現在基本用不到:
# -*- coding: UTF-8 -*-
識別字
- 由字母、數字、底線組成,不能用數字開頭,區分大小寫。
- Python 3 允許用中文命名,但不建議。
- 底線的特殊約定:
_name:單底線開頭,表示「內部使用」,from module import *不會匯入。__age:雙底線開頭,觸發名稱改寫(name mangling),用於避免子類別衝突。__init__:雙底線開頭和結尾,是 Python 的內建特殊方法,不要自己造這種名字。
贊助 / 我一個人打車六七十回家!


何時一樽酒,重與細論文。