引言
在Python中,有时需要自定义简单但是有名字的记录类型,如果使用字典或普通元组,通常会显得有点笨重。此时,namedtuple就成为了一个很好的选择。namedtuple是Python标准库collections中的一个函数,用于创建和实例化类似于元组的对象,但这些对象有名称,可以像字典一样通过名称来访问字段。namedtuple相当于一个轻量级的类定义器,它本质上是一个Python类,可以像其他类一样继承、实例化、序列化和扩展。
正文
一、namedtuple的使用方法
namedtuple的使用方法与普通元组非常相似。首先需要导入collections库,然后使用namedtuple函数来定义一个命名元组。定义时需要提供两个参数:元组名称和元素名称列表。下面是一个简单的例子:
from collections import namedtuple
#定义一个命名元组
Person = namedtuple("Person", ["name", "age"])
#使用命名元组
person = Person(name="Tom", age=18)
print(person.name) # Tom
在上面的例子中,定义了一个Person命名元组,包含name和age两个字段。在实例化时,使用关键字参数来指定字段的值,可以像普通元组一样通过索引或名称来访问元素。但不同的是,通过名称访问元素时更直观和安全。
二、namedtuple与普通元组的比较
在使用namedtuple之前,可以先看一下普通元组的缺陷。
- 元组和其它序列一样是有序的。当元素需要赋予语义时,就需要借助位置来访问元素。这会降低程序的可读性,并让代码更加脆弱(如果代码依赖于元组中的位置,那么当元组结构发生变化时,很容易出错)。
- 在表达简单的对象时,元组常常是有用的——如果需要将其作为函数的参数,并且只有少数几个值是有意义的。但是,当元组变得更大,或者需要添加一些方法或属性来扩展元组时,这时候使用类会更好。
- 元组是不可变的,因此如果想在元素被赋值后修改它们是不可能的。这样如果需要在修改后返回相似但是不同的对象时,就需要使用其他的数据类型(如列表)来存储数据。
- 在元组中添加、删除或修改元素会非常困难。当元组项数变得非常庞大时,元组会变得笨重,在这种情况下,尽量使用更合适的数据结构。
三、namedtuple的高级用法
namedtuple本身是可以继承的,这意味着其可以和其他的类一样进行扩展,添加自己的方法和属性:
from collections import namedtuple
#定义一个命名元组
Animal = namedtuple("Animal", ["name", "age"])
#定义一个Cat类,并继承Animal类
class Cat(Animal):
def __str__(self):
return "cat: {name}, {age}".format(name=self.name, age=self.age)
cat = Cat(name="Tom", age=3)
print(cat) # cat: Tom, 3
此外,namedtuple还支持各种继承和多重继承的方式,可以方便地与需要继承的类和对象进行交互。
四、namedtuple的性能测试
对于一个类定义器来说,其性能是一个非常重要的因素,下面从创建、访问和执行效率三个方面对namedtuple进行性能测试,和普通元组进行比较。
- 创建效率 使用timeit模块测试一个10000000个元素的元组和namedtuple的创建效率:
from collections import namedtuple
import timeit
def test_normal_tuple():
t = tuple(range(10000000))
def test_named_tuple():
Person = namedtuple("Person", list(range(10000000)))
tuple = Person(*range(10000000))
if __name__ == "__main__":
normal_time = timeit.timeit("test_normal_tuple()", setup="from __main__ import test_normal_tuple", number=1000)
named_time = timeit.timeit("test_named_tuple()", setup="from __main__ import test_named_tuple", number=1000)
print("normal tuple :", normal_time)
print("named tuple :", named_time)
得到结果如下:
normal tuple : 1.4164350395202637
named tuple : 8.236930131912231
可以看到,namedtuple创建的速度比普通元组慢,因为它需要额外的计算来生成所需要的类定义,但这种差异在实际应用中并不会影响太多。 2. 访问效率 使用timeit模块测试一个10000000个元素的元组和namedtuple的访问效率:
from collections import namedtuple
import timeit
Person = namedtuple("Person", list(range(10000000)))
tuple = Person(*range(10000000))
def test_normal_tuple():
t = tuple(range(10000000))
for i in range(len(t)):
a = t[i]
def test_named_tuple():
for i in range(10000000):
a = tuple[i]
if __name__ == "__main__":
normal_time = timeit.timeit("test_normal_tuple()", setup="from __main__ import test_normal_tuple", number=1000)
named_time = timeit.timeit("test_named_tuple()", setup="from __main__ import test_named_tuple", number=1000)
print("normal tuple :", normal_time)
print("named tuple :", named_time)
得到结果如下:
normal tuple : 5.558712959289551
named tuple : 6.515319109916687
可以看到,namedtuple与元组在访问效率上并无明显区别。 3. 执行效率 使用timeit模块测试一个空操作的元组和namedtuple的执行效率:
from collections import namedtuple
import timeit
Person = namedtuple("Person", ["name", "age"])
tuple = Person(name="Tom", age=3)
def test_normal_tuple():
pass
def test_named_tuple():
pass
if __name__ == "__main__":
normal_time = timeit.timeit("test_normal_tuple()", setup="from __main__ import test_normal_tuple", number=1000)
named_time = timeit.timeit("test_named_tuple()", setup="from __main__ import test_named_tuple", number=1000)
print("normal tuple :", normal_time)
print("named tuple :", named_time)
得到结果如下:
normal tuple : 0.0016641616821289062
named tuple : 0.0016469955444335938
可以看到,namedtuple与元组在执行效率上几乎无差别。
结论
总体来说,namedtuple相比普通元组来说有更好的可读性,同时它本身也具备扩展性和继承性。尽管在创建方面不如普通元组快,但巨大的拓展性和易用性弥补了这一点缺陷。在实际项目中,使用namedtuple可以提高代码的可读性和可维护性,减少由于人为疏忽带来的错误。