Python进阶编程:编写更高效、优雅的Python代码
上QQ阅读APP看书,第一时间看更新

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__方法的类。