在本教程中,我们将深入研究命名元组:它们是什么,如何创建和操作它们,以及何时使用它们(或不使用它们)。
命名元组是Python内置的一部分收藏模块,并且它们提供了一种将数据捆绑在一个名称下的便捷方法。
它们是 Python 内置元组数据类型的子类,但有一个特点:命名元组是元组,其中每个值(或“字段”)也可以通过唯一的、用户定义的名称进行访问。
当您需要以结构化、不可变的格式将相关数据分组在一起时,命名元组是一个不错的选择。
创建命名元组
在 Python 中创建命名元组涉及namedtuple()
用于创建元组子类的工厂函数。
nametuple 需要一个类型名和一个字段名作为其参数。类型名称将是新类的名称。
字段名称可以作为可迭代的字符串或单个空格/逗号分隔的字符串提供。
from collections import namedtuple
# Define a Car namedtuple
Car = namedtuple('Car', 'brand model year color')
# Instantiate a Car
my_car = Car('Tesla', 'Model S', 2023, 'Red')
print(my_car)
Output:
Car(brand='Tesla', model='Model S', year=2023, color='Red')
在此代码中,我们从集合模块导入namedtuple类,并定义一个带有字段名称“brand”、“model”、“year”和“color”的Carnamedtuple。
然后,我们通过传递适当的值来构造 Car 命名元组的实例。
添加默认值
从 Python 3.7 开始,namedtuple 函数包含一个 defaults 关键字参数来提供默认值。
# Define a Car namedtuple with default color and year
Car = namedtuple('Car', 'brand model year color', defaults=('Unknown', 'Black'))
# Instantiate a Car without providing color and year
my_car = Car('Tesla', 'Model S')
print(my_car)
Output:
Car(brand='Tesla', model='Model S', year='Unknown', color='Black')
在上面的示例中,我们为“年份”和“颜色”字段提供了默认值。
当我们创建一个新的 Car 命名元组而不指定这些字段时,将使用默认值。
访问元素
一旦我们有了命名元组,我们就可以通过几种不同的方式访问它的元素。
使用索引
尽管命名元组通过添加字段名称改进了常规元组,但我们仍然可以使用索引访问元素,就像常规元组一样。
from collections import namedtuple
# Define a Fruit namedtuple
Fruit = namedtuple('Fruit', ['name', 'color'])
# Create an instance of Fruit
apple = Fruit('Apple', 'Red')
print(apple[0]) # Access element by index
Output:
Apple
在此示例中,我们使用索引 0 访问水果的名称。
使用字段名称(使用点表示法)
命名元组的主要功能之一是能够使用字段名称访问元素。这通常是使用点表示法来完成的。
print(apple.name)
Output:
Apple
在这里,我们使用水果的字段名称来访问水果的名称。
使用 getattr()
Python 的内置getattr()
函数允许我们访问对象属性的值。对于命名元组,我们可以使用它来访问字段值。
print(getattr(apple, 'color'))
Output:
Red
修改namedtuple(使用_replace()方法)
由于命名元组是不可变的,我们不能直接修改它们。
但有一个解决方法,使用_replace()
方法,它返回一个带有替换字段的新实例。
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(10, 20)
# Modify the y-coordinate
p = p._replace(y=30)
print(p)
Output:
Point(x=10, y=30)
在此示例中,我们将点的 y 坐标从 20 替换为 30。
将namedtuple转换为字典
要将命名元组转换为字典,请使用_asdict()
方法。当您需要来自命名元组的键值对时,这非常有用。
d = p._asdict()
print(d)
Output:
{'x': 10, 'y': 30}
在这里,我们将命名元组转换为字典,其中字段名称作为键,相应的值作为值。
使用 _fields 方法
The _fields
属性返回一个列出字段名称的元组。当您需要迭代命名元组的所有字段时,它会很有用。
print(Point._fields)
Output:
('x', 'y')
在这里,我们打印点命名元组的字段名称。
使用_make
The _make()
方法允许我们从可迭代对象创建一个命名元组的新实例。
data = [100, 200]
new_point = Point._make(data)
print(new_point)
Output:
Point(x=100, y=200)
我们使用以下方法从值列表中创建一个名为tuple的新点_make()
method.
迭代命名元组
您可以像使用常规元组一样迭代命名元组。就是这样:
from collections import namedtuple
Person = namedtuple('Person', ['name', 'age', 'city'])
# Create an instance of the namedtuple
person = Person('John Doe', 30, 'New York')
# Iterate over the namedtuple
for field in person:
print(field)
Output:
John Doe
30
New York
在上面的代码中,我们定义了一个namedtuplePerson
并创建一个实例person
。然后我们迭代person
使用简单的 for 循环,打印命名元组中的每个字段。
迭代字段名称和值
如果您想迭代字段名称和值,可以将namedtuple转换为字典并使用items()
method:
from collections import namedtuple
Person = namedtuple('Person', ['name', 'age', 'city'])
p = Person('John Doe', 30, 'New York')
for field_name, field_value in p._asdict().items():
print(f"{field_name}: {field_value}")
Output:
name: John Doe
age: 30
city: New York
在此代码中,我们使用_asdict()
方法将namedtuple转换为字典,然后使用items()
方法来获取字段名称和值对。
使用 _fields() 和 getattr()
您还可以使用_fields
方法结合getattr()
迭代两个字段名称及其相应的值:
from collections import namedtuple
Person = namedtuple('Person', ['name', 'age', 'city'])
p = Person('John Doe', 30, 'New York')
for field_name in p._fields:
print(f"{field_name}: {getattr(p, field_name)}")
Output:
name: John Doe
age: 30
city: New York
在这里,_fields
方法提供字段名称,以及getattr()
用于从每个字段中获取相应的值person
命名元组。
对命名元组进行排序
您可以对命名元组列表进行排序,类似于对元组列表进行排序。排序基于字段的自然顺序。
但是,您可以使用以下命令自定义排序itemgetter
函数从operator
module.
from collections import namedtuple
from operator import itemgetter
Fruit = namedtuple('Fruit', ['name', 'color'])
# Create a list of fruits
fruits = [Fruit('Apple', 'Red'), Fruit('Banana', 'Yellow'), Fruit('Cherry', 'Red')]
# Sort the list of fruits by color
sorted_fruits = sorted(fruits, key=itemgetter(1))
for fruit in sorted_fruits:
print(fruit)
Output:
Fruit(name='Apple', color='Red')
Fruit(name='Cherry', color='Red')
Fruit(name='Banana', color='Yellow')
在此示例中,我们定义一个名为 tuple 的 Fruit 并创建一个 Fruit 实例列表。然后,我们使用“颜色”字段对该列表进行排序itemgetter
功能。
命名元组与元组、列表和字典
让我们比较一下这些数据类型的效率。
我们的基准测试将重点关注这些数据结构的创建、内存使用和简单访问。
我们可以采取以下方法:
- 我们将创建一个 Namedtuple、Tuple、List 和 Dictionary,每个都包含相同的元素。
- 我们将测量创建每个结构所需的时间。
- 我们将测量每个结构的内存使用情况。
- 我们将测量访问每个结构中的元素所需的时间。
import sys
import timeit
from collections import namedtuple
# The size of the data structure
SIZE = 1000000
data = [(i, 'data{}'.format(i)) for i in range(SIZE)]
# Define the namedtuple type
DataNT = namedtuple('DataNT', 'id value')
# Conversion factor from seconds to milliseconds
SEC_TO_MSEC = 1000
# Create and measure time and memory for tuple
start = timeit.default_timer()
tuple_data = tuple(data)
tuple_time = (timeit.default_timer() - start) * SEC_TO_MSEC
tuple_mem = sys.getsizeof(tuple_data)
# Create and measure time and memory for namedtuple
start = timeit.default_timer()
namedtuple_data = tuple(DataNT(*d) for d in data)
namedtuple_time = (timeit.default_timer() - start) * SEC_TO_MSEC
namedtuple_mem = sys.getsizeof(namedtuple_data)
# Create and measure time and memory for list
start = timeit.default_timer()
list_data = list(data)
list_time = (timeit.default_timer() - start) * SEC_TO_MSEC
list_mem = sys.getsizeof(list_data)
# Create and measure time and memory for dictionary
start = timeit.default_timer()
dict_data = dict(data)
dict_time = (timeit.default_timer() - start) * SEC_TO_MSEC
dict_mem = sys.getsizeof(dict_data)
# Access and measure time for tuple
start = timeit.default_timer()
_ = tuple_data[SIZE//2]
tuple_access_time = (timeit.default_timer() - start) * SEC_TO_MSEC
# Access and measure time for namedtuple
start = timeit.default_timer()
_ = namedtuple_data[SIZE//2]
namedtuple_access_time = (timeit.default_timer() - start) * SEC_TO_MSEC
# Access and measure time for list
start = timeit.default_timer()
_ = list_data[SIZE//2]
list_access_time = (timeit.default_timer() - start) * SEC_TO_MSEC
# Access and measure time for dictionary
start = timeit.default_timer()
_ = dict_data[SIZE//2]
dict_access_time = (timeit.default_timer() - start) * SEC_TO_MSEC
results = {
"tuple": {"creation_time_ms": tuple_time, "memory": tuple_mem, "access_time_ms": tuple_access_time},
"namedtuple": {"creation_time_ms": namedtuple_time, "memory": namedtuple_mem, "access_time_ms": namedtuple_access_time},
"list": {"creation_time_ms": list_time, "memory": list_mem, "access_time_ms": list_access_time},
"dict": {"creation_time_ms": dict_time, "memory": dict_mem, "access_time_ms": dict_access_time},
}
print(results)
基准测试结果:
Data Structure |
Creation Time (ms) |
Memory Usage (bytes) |
Access Time (ms) |
Tuple |
13.2023 |
8000040 |
0.0022 |
NamedTuple |
1601.5981 |
8000040 |
0.0016 |
List |
12.2202 |
8000056 |
0.0013 |
Dictionary |
74.7433 |
41943136 |
0.0021 |
列表和命名元组提供对元素的最快访问。这是因为它们将元素存储在连续的内存块中。
类中的命名元组
命名元组可以在类中使用,以增强代码的可读性和组织性。让我们创建一个使用命名元组的类。
from collections import namedtuple
Person = namedtuple('Person', ['name', 'age'])
class Classroom:
def __init__(self):
self.students = []
def add_student(self, name, age):
student = Person(name, age)
self.students.append(student)
classroom = Classroom()
classroom.add_student('John', 16)
classroom.add_student('Emma', 17)
for student in classroom.students:
print(student)
Output:
Person(name='John', age=16)
Person(name='Emma', age=17)
在此示例中,我们有一个 Classroom 类,它使用 Person 命名元组来存储有关学生的信息。
add_student 方法创建一个名为tuple 的新 Person 并将其添加到学生列表中。
函数中的命名元组
命名元组可以从函数返回,这可以通过为元组的元素命名来提高代码的可读性。
让我们创建一个返回命名元组的函数。
from collections import namedtuple
Result = namedtuple('Result', ['success', 'data'])
def fetch_data():
data = "sample data"
success = True
return Result(success, data)
result = fetch_data()
print(result)
Output:
Result(success=True, data='sample data')
在这里,我们有一个 fetch_data 函数,它返回一个名为 tuple 的结果,其中包含一个指示数据获取是否成功的布尔值以及数据本身。
嵌套命名元组
命名元组还可以嵌套以创建更复杂的数据结构。这可以提高代码的可读性。
from collections import namedtuple
Address = namedtuple('Address', ['street', 'city', 'state', 'zip_code'])
Person = namedtuple('Person', ['name', 'age', 'address'])
# Create an Address instance
home_address = Address('123 Main St', 'Springfield', 'IL', '62701')
# Create a Person instance with a nested Address
person = Person('John Doe', 30, home_address)
print(person)
Output:
Person(name='John Doe', age=30, address=Address(street='123 Main St', city='Springfield', state='IL', zip_code='62701'))
在此示例中,我们定义了一个名为地址的元组和一个包含地址字段的个人名为元组。
这允许我们将 Address 实例嵌套在 Person 实例中,从而创建更加结构化和可读的数据表示。
命名元组与元组
下面是Python中namedtuple和tuple的对比表。
Aspect |
namedtuple |
tuple |
Access method |
By field name or index or using getattr() |
By index only |
Code readability |
High (each value has a name) |
Low (need to remember order of values) |
Memory usage |
More than tuples (due to metadata) |
Less than namedtuples |
Flexibility |
Fixed once defined |
Can include any values |
Can be used as dict key |
Yes |
Yes |
Mutability |
Immutable |
Immutable |
Method support |
Has several helpful methods |
Has only count() and index() methods |
命名元组与数据类
下面是Python中namedtuple和dataclass的对比表。
Aspect |
namedtuple |
dataclass |
Introduced in |
Python 2.6 |
Python 3.7 |
Access method |
By field name or index |
By field name |
Mutability |
Immutable |
Both mutable and immutable versions |
Default values |
Not supported natively |
Supported |
Methods |
Few built-in methods |
Can define custom methods |
Inheritance |
Can be extended using subclasses |
Supports standard Python inheritance |
Code readability |
High (each value has a name) |
High (each value has a name) |
Memory usage |
Less than dataclasses |
More than namedtuples |
Type hints |
Not supported natively |
Supported |
Usage |
Better for simpler use cases |
Better for complex use cases |
何处使用命名元组的实际示例
分组数据和记录:命名元组是将相关数据分组在一起的有效方法。
例如,如果您有与汽车相关的各种数据(例如型号、品牌、年份和颜色),则可以将这些数据分组到命名元组中。
from collections import namedtuple
Car = namedtuple('Car', ['make', 'model', 'year', 'color'])
car = Car('Toyota', 'Camry', 2018, 'Blue')
print(car)
Output:
Car(make='Toyota', model='Camry', year=2018, color='Blue')
在此示例中,Carnamedtuple 用于对与特定汽车相关的数据进行分组。
从函数返回多个值:Python 中的函数可以返回多个值,并且通常这些值作为元组返回。
使用命名元组而不是常规元组可以为返回值提供更多上下文,从而提高可读性。
from collections import namedtuple
from math import pi
CircleMetrics = namedtuple('CircleMetrics', ['area', 'circumference'])
def calculate_circle_metrics(radius):
area = pi * radius * radius
circumference = 2 * pi * radius
return CircleMetrics(area, circumference)
metrics = calculate_circle_metrics(5)
print(metrics)
Output:
CircleMetrics(area=78.53981633974483, circumference=31.41592653589793)
在这个例子中,函数calculate_circle_metrics
计算圆的面积和周长并返回包含这两个值的 CircleMetrics 命名元组。
何时不使用命名元组的示例
虽然命名元组很有用且用途广泛,但在某些情况下其他数据结构可能更适合。
-
当数据需要可变时:由于命名元组是不可变的,因此如果您需要可以修改值的数据结构,那么它们不适合。在这种情况下,字典或列表可能更合适。
-
当您需要更复杂的数据结构时:虽然命名元组可以嵌套,但如果您需要更复杂或自定义的数据结构,则可能需要定义自己的类。
例如,让我们举一个例子,我们想要代表一个学生,我们需要动态添加科目。在这种情况下,字典将是更合适的选择。
student = {
'name': 'John Doe',
'age': 16,
'subjects': ['English', 'Math', 'Science']
}
# Add a new subject
student['subjects'].append('History')
print(student)
Output:
{'name': 'John Doe', 'age': 16, 'subjects': ['English', 'Math', 'Science', 'History']}
在此示例中,学生的科目存储在字典内的列表中,允许您动态添加或删除科目。