3.1.6 序列元素名称映射
对于很多初学者来说,访问列表或元组时习惯使用下标,这样编写的代码不但可读性差,而且可维护性差。建议使用更优雅的方式实现,比如通过名称访问。
collections.namedtuple()函数通过使用一个普通的元组对象来解决这个问题。实际上,该函数是一个返回Python中标准元组类型的子类的工厂函数,给它传递一个类型名和需要的字段,会返回一个类。初始化这个类可以为定义的字段传递值,示例如下:
from collections import namedtuple UserInfo = namedtuple('UserInfo', ['email', 'date']) user_info = UserInfo('test@ai.com', '2012-10-19') print(user_info) print(user_info.email) print(user_info.date)
尽管namedtuple实例看起来像一个普通的类实例,但它与元组类型是可交换的,支持所有的普通元组操作,如索引和解压,示例如下:
print(len(user_info)) email, date = user_info print(email, date)
命名元组的一个主要用途是将代码从下标操作中解脱出来。
如果从数据库调用中返回一个很大的元组列表,我们可通过下标去操作其中的元素。当在普通元组列表中添加新的列的时候,代码可能会出错。但若使用了命名元组,则不会有这样的顾虑。
先看使用普通元组的代码示例:
def calculate_cost_1(record_list): total = 0.0 for record in record_list: total += record[1] * record[2] return total
下标操作通常会让代码表意不清晰,并且非常依赖记录的结构。更改为使用命名元组的示例如下:
from collections import namedtuple Course = namedtuple('Course', ['name', 'class_hour', 'score']) def calculate_cost(record_list): total = 0.0 for rec in record_list: course = Course(*rec) total += course.class_hour * course.score return total
命名元组另一个用途是作为字典的替代,因为字典存储需要更多的内存空间。如果需要构建一个非常大的包含字典的数据结构,那么使用命名元组会更加高效。
注意 命名元组是不可更改的。
如果需要改变属性值,那么可以使用命名元组实例的_replace()方法。它会创建一个全新的命名元组并将对应的字段用新的值取代,示例如下:
course = Course('xiao meng', 20, 0.3) print(course) course = course._replace(class_hour=30) print(course)
当命名元组拥有可选或者缺失字段时,_replace()方法是一个非常方便的填充数据的方法。首先创建一个包含缺省值的原型元组,然后使用_replace()方法创建新的值被更新过的实例,示例如下:
from collections import namedtuple Course = namedtuple('Course', ['name', 'class_hour', 'score', 'date', 'time']) # Create a prototype instance course_prototype = Course('', 0, 0.0, None, None) # Function to convert a dictionary to a Course def dict_to_course(course): return course_prototype._replace(**course)
上述定义的dict_to_course()函数的使用方式如下(list_mapping_exp.py):
course_a = {'name': 'xiao meng', 'class_hour': 20, 'score': 0.3} print(dict_to_course(course_a)) course_b = {'name': 'xiao meng', 'class_hour': 20, 'score': 0.3, 'date': '04/19/2020'} print(dict_to_course(course_b))
如果目标是定义一个需要更新很多实例属性的高效的数据结构,那么命名元组并不是最佳选择,这时候应该考虑定义一个包含__slots__方法的类。