天天看點

1.18 映射名稱到序列元素

你有一段通過下标通路清單或元組中元素的代碼,但是這樣有時候會使得你的代碼難以閱讀,于是你想通過名稱來通路元素。

問題描述

解決方案

collections.namedtuple()

函數通過使用一個普通的元組對象來幫你解決這個問題。這個函數實際上是一個傳回Python中标準元組類型子類的一個工廠方法。你需要傳遞一個類型名和你需要的字段給它,然後它就會傳回一個類,你可以初始化這個類,為你定義的字段傳遞值等。例如:

from collections import namedtuple

Subscriber = namedtuple('Tester', ['addr', 'joined'])
sub = Subscriber('[email protected]', '2022-01-07')
"""
sub = Tester(addr='[email protected]', joined='2022-01-07')
sub.addr = [email protected]
sub.joined = 2022-01-07
"""
           

盡管

namedtuple

的執行個體看起來像一個普通的類執行個體,但是它跟元組類型是可交換的,支援所有的普通元組操作,比如索引和解壓。例如:

length = len(sub)
addr, joined = sub
"""
length = 2
addr = '[email protected]'
joined = '2022-01-07'
"""
           

命名元組的一個主要用途是将你的代碼從下标操作中解脫出來。是以,如果你通過下标去操作一個元組清單中的元素,當你在表中添加新的列的時候你的代碼可能就會出錯了。但是如果你使用了命名元組,就不會有這種問題。

為了說明這個問題,下面是使用普通元組的代碼:

def compute_cost(records):
	total = 0.0
	for rec in records:
		total += rec[1] * rec[2]
	return total
           

下标操作通常會讓代碼表意不清淅,并且非常依賴記錄的結構。下面是使用命名元組的版本:

from collections import namedtuple

Stock = namedtuple('Stock', ['name', 'shares', 'price'])
def compute_cost(records):
	total = 0.0
	for rec in records:
		s = Stock(*rec)
		total += s.shares * s.price
	return total
           

讨論

命名元組另一個作用就是作為字典的替代,因為字典存儲需要更多的記憶體空間。如果你需要建構一個非常大的包含字典的資料結構,那麼使用命名元組會更加高效。但是需要注意,一個命名元組是不可更改的。比如:

s = Stock('A', 1, 22)  # Stock(name='A', shares=1, price=22)
s.shares = 7
"""
AttributeError: can't set attribute
"""
           

如果你真的需要改變屬性的值,那麼可以使用命名元組執行個體的

_replace()

方法,它會建立一個全新的命名元組并将對應的字段用新值取代。例如:

s = s._replace(shares=7)
print(s)
"""輸出結果:
Stock(name='A', shares=7, price=22)
"""
           

_replace()

方法還有一個很有用的特性就是當你的命名元組擁有可選或者缺失字段時,它是一個非常友善的填充資料的方法。你可以建立一個包含預設值的原型元組,然後使用

_replace()

方法建立新的值被更新過的執行個體。例如:

from collections import namedtuple

Stock = namedtuple('Stock', ['name', 'shares', 'price', 'date', 'time'])
stock_instance = Stock('', 0, 0.0, None, None)


def dict_to_stock(s):
    return stock_instance._replace(**s)


if __name__ == '__main__':
    a = {'name': 'ACME', 'shares': 100, 'price': 123.45}
    print(dict_to_stock(a))
    b = {'name': 'ACME', 'shares': 100, 'price': 123.45, 'date': '12/17/2012'}
    print(dict_to_stock(b))
	
	"""輸出結果:
	Stock(name='ACME', shares=100, price=123.45, date=None, time=None)
	Stock(name='ACME', shares=100, price=123.45, date='12/17/2012', time=None)
	"""
           

最後要說的是,如果你的目标是定義一個需要更新很多執行個體屬性的高效資料結構,那麼命名元組并不是最佳選擇。這時候你應該考慮定義一個包含

__slots__

方法的類(參考8.4小節)

總結

本小節介紹了

collections.namedtuple()

方法,它可以初始化一個類,将一個元組的值依次賦予給類的各字段,這樣就可以通過字段通路值。一定程度上可以替代字典。