post / 2023.06.27

Python基礎

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。

字串

  1. 單引號和雙引號等價,不區分單字元與字串(沒有獨立的 char 型別)。
  2. 三引號可以寫多行字串。
  3. 引號內使用同類引號需要轉義 \"。

三種格式化方式

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 走訪的物件。滿足下面任一條即可:

  1. 實作了 __iter__(),回傳一個迭代器實例;
  2. 沒有 __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 的內建特殊方法,不要自己造這種名字。

本文作者: 孤筝

本文標題: Python基礎

本文發佈時間: 2023-06-27

本文更新時間: 2026-10-05

本文版權: CC BY-NC-SA 4.0

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

搜尋