0%

使用collections中的namedtuple來操作簡單的物件結構

tuple在python中是一種不可變動(immutable)的資料結構,相較於list是可變動的(mutable),兩者共同都可以使用index來讀取值,但因為tuple為不可變動,所以一經宣告即無法修改其中的值。

1
2
3
4
5
>>> drink = ('Black Tea', 'regular', 'sugar free')  # 我要一杯紅茶,正常冰量不加糖
>>> drink[0] # 可以使用index來讀取tuple中的item
Black Tea
>>> drink[0] = 'Green Tea' # tuple為不可變動,所以無法透過assign來修改其中的值
TypeError: 'tuple' object does not support item assignment

tuple很適合用來儲存一些不需要頻繁變動的資料,但是在使用上要使用index來讀取tuple中item的值,若是tuple中的item數量少也許還沒問題,但如果tuple中的item數量很多,甚至是開發一段時間又沒寫註解,往往最後這種index的讀取就變成一種魔術數字(magic number),造成維護上的困難。

namedtuple是tuple的擴展,因為namedtuple在使用上可以透過類似讀取欄位(field)名稱的方式,來取用tuple不同的item資料,所以有時候namedtuple就很適合用來代替tuple儲存資料。如果有需要頻繁讀取tuple中的item,於是你開始想建立class和物件來操作,但是資料欄位又沒有到這麼複雜到需要額外建class,這種情況很適合。

下面馬上來介紹一下namedtuple的使用方法

1
2
3
4
5
6
7
8
9
10
from collections import namedtuple

>>> Drink = namedtuple('Drink', 'product, ice, sugar') # 宣告一種名稱(Drink)且包含三個欄位(product, ice, sugar)
>>> black_tea = Drink('Black Tea', 'regular', 'sugar free') # 我要一杯紅茶,正常冰量不加糖

Drink(product='Black Tea', ice='regular', sugar='sugar free')
>>> black_tea.product # 可以使用一開始定義的欄位名稱來讀取值
Black Tea
>>> black_tea[0] # 當然也可以使用index來讀取值
Black Tea

宣告namedtuple需要給定兩個參數,第一個是tuple的名稱(name),再來是這個tuple包含的欄位名稱(field_name)。欄位名稱使用字串格式代入所有需要的欄位(其中的,可加可不加)。以上面的例子來看,tuple名稱就是Drink,欄位名稱是product, ice和sugar三個。之後透過宣告的namedtuple來儲存資料,就可以使用欄位名稱來讀取item值了!

namedtuple可以和tuple一樣作unpack

1
2
3
4
5
>>> product, ice, sugar = black_tea
>>> sugar
sugar free
>>> ice
normal

也有支援直接從list和dict轉成namedtuple

1
2
3
4
5
6
7
>>> l = ['Bubble Tea', 'regular', 'regular']
>>> bubble_tea = Drink._make(l) # 使用_make來將list轉成namedtuple
Drink(product='Bubble Tea', ice='regular', sugar='regular')

>>> d = {'product': 'Bubble Tea', 'ice': 'regular', 'sugar': 'regular'}
>>> bubble_tea = Drink(**d) # 使用拆開映射運算符(double-star-operator)來拆開dict轉成namedtuple
Drink(product='Bubble Tea', ice='regular', sugar='regular')

將namedtuple轉成dict(此dict為collection裡面OrderDict)

1
2
>>> bubble_tea_dict = bubble_tea._asdict()
OrderedDict([('product', 'Bubble Tea'), ('ice', 'regular'), ('sugar', 'regular')])

如果今天已經存在一個namedtuple,需要使用現存的namedtuple來新增一個新的namedtuple也可以使用_filed來辦到

1
2
3
4
5
6
>>> Ingredient = namedtuple('Ingredient', 'tapioca_pearls, grass_jelly, flan')
>>> Order = namedtuple('Order', Drink._fields + Ingredient._fields)

# 我要一杯奶茶,正常冰半糖加布丁
>>> drink_order = Order('Milk Tea', 'regular', 'half sugar', False, False, True)
Order(product='Milk Tea', ice='regular', sugar='half sugar', tapioca_pearls=False, grass_jelly=False, flan=True)

在開發過程中,如果可以巧妙的使用namedtuple,可以讓程式更加有可讀性,也可以讓幫助下降日後的維護成本!

Python docs - namedtuple() Factory Function for Tuples with Named Fields