不知道大家在使用 time 模組的 localtime() 時有沒有注意到這個函式傳回的是一個 struct_time:
>>> import time
>>> t1 = time.localtime()
>>> t1
time.struct_time(tm_year=2023, tm_mon=1, tm_mday=28, tm_hour=17, tm_min=26, tm_sec=48, tm_wday=5, tm_yday=28, tm_isdst=0)
>>> type(t1)
<class 'time.struct_time'>
這種物件很特別, 它既可以當成序組 (tuple) 來用, 像是這樣用索引編號取得元素:
>>> t1[0]
2023
也可以用欄位名稱來取得個別元素, 例如第 0 個元素的名稱為 "tm_year":
>>> t1.tm_year
2023
具名序組
這種可用欄位名稱取用元素的序組稱為具名序組 (named tuple), 可以幫序組的元素加上具有說明意義的名稱, 像是剛剛的 struct_time 物件, 就可以很容易區別哪一個元素是年份、哪一個是小時, 不會弄錯順序。
如果你也希望可以建立這樣的物件, 可以使用 collections 模組下的 namedtuple() 函式。
自製具名序組
namedtuple() 是所謂的工廠 (factory) 函式, 這種函式的功用就是幫你依據需求製造出新的物件, 而 namedtuple() 會製造的是一種新的類別, 可以用來產生具名序組。
假設我們想要用只有 2 個元素的序組來表示幾何平面上的一點, 那麼就可以如下利用 namedtuple() 定義新的類別:
>>> import collections
>>> P = collections.namedtuple(
... 'Point',
... ['X', 'Y']
... )
第 1 個參數是新類別的名稱, 第 2 個參數是一串字串, 代表個別元素的名稱, 上述範例的意思就是定義一個 Point 類別, 它建立的物件其實就是一個只有 2 個元素的序組, 其中第 1 個元素叫做 'X'、第 2 個元素叫做 'Y'。我們把新定義的類別取別名為 P, 即可如下建立物件:
>>> p1 = P(1,2)
>>> p2 = P(Y=3,X=5)
>>> p1
Point(X=1, Y=2)
>>> p2
Point(X=5, Y=3)
>>> type(p1)
<class '__main__.Point'>
你可以注意到建立物件的時候還可以用欄位名稱當成具名參數。建立物件後就可以使用序組或是欄位名稱的方式取用元素:
>>> p1[0]
1
>>> p2.Y
3
如果想要用字串當成像是字典的索引鍵那樣取用元素, 可以透過內建函式 getattr():
>>> getattr(p1, 'X')
1
具名序組元素的預設值
你也可以在定義具名序組類別時幫個別元素準備預設值, 例如:
>>> P = collections.namedtuple(
... 'Point',
... ['X', 'Y'],
... defaults=[1,1]
... )
>>> p3 = P()
>>> p3
Point(X=1, Y=1)
defaults 參數必須是一個可走訪物件, 由他依序提供個別元素的預設值。如果 defaults 內的資料數量少於元素個數, 就必須依循位置參數要出現在套用預設值的參數前的規則, 把 defaults 提供的預設值套用在序組中排在後面的元素, 前面的元素仍需要在建立物件時指定內容, 例如:
>>> P = collections.namedtuple(
... 'Point',
... ['X', 'Y'],
... defaults=[1]
... )
>>> p4 = P()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Point.__new__() missing 1 required positional argument: 'X'
Point.__new__() missing 1 required positional argumen'
此例由於 defaults 僅有 1 相資料, 所以套用在後面的 Y, 建立物件時沒有指定 X 值, 就會出錯。改成這樣就可以正常運作:
>>> p4 = P(3)
>>> p4
Point(X=3, Y=1)
你可以看到 Y 欄位套用了預設值 1。
具名序組的特殊成員
具名序組是衍生自 tuple 的類別, 因此可以使用在任何序組可以應用的場合, 除此之外, 具名序組也具有額外的成員:
>>> P._fields
('X', 'Y')
>>> P._field_defaults
{'Y': 1}
_fields 會以序組傳回欄位名稱清單, 而 _field_defaults 則是以字典傳回個別欄位的預設值。要特別留意的是這些成員的名稱都是以 '_' 開頭。
如果想要從串列建立具名序組, 可以用自動拆包的方式, 或是使用具名序組特有的 _make() 方法:
>>> p5 = P(*[5,6])
>>> p6 = P._make([7,8])
>>> p5
Point(X=5, Y=6)
>>> p6
Point(X=7, Y=8)
同樣的方式, 也可以從字典拆解成具名參數建立具名序組:
>>> p7 = P(**{'X':10, 'Y':11})
>>> p7
Point(X=10, Y=11)
不過要特別注意字典內的索引鍵要和具名序組內的欄位名稱相符。
具名序組也提供反向將序組內容轉換成字典的方法:
>>> p7._asdict()
{'X': 10, 'Y': 11}
要記得具名序組終究還是序組, 是不可修改內容的物件, 若是要調整內容, 可以使用具名序組特有的 _replace() 方法, 例如:
>>> p7
Point(X=10, Y=11)
>>> p8 = p7._replace(X=100)
>>> p8
Point(X=100, Y=11)
你可以看到 _replace() 會建立一個新的物件, 所以 p7 的內容不會變。
結語
具名序組可以用在固定數量且個別項目具有特定意義的一組資料上, 既可以快速透過索引取用資料, 也可以用欄位名稱識別個別元素, 在像是存取 csv 檔或是 sqllite 表格資料時會非常有用, 不會搞不清楚到底哪一個元素是什麼用途, 你也可以將之應用在自己的程式中。
Top comments (0)