Python的ttuple
写一个最简单的tuple例子a是一个typle有三个元素,它的主要使用方式与listi没有太多的区别,主要的区别是tuple不能修改。
a = 1,2,3
print(type(a))
- 元组tuple
- 列表 ls =[]
- 字典dc={}
这些知识可以看我之前写的文章或者网上搜索
namedtuple有名字的元组
Python 的 namedtuple() 是集合中可用的工厂函数。它允许您创建具有命名字段的子类。您可以使用点表示法和字段名称访问给定命名元组中的值,
为了提高代码的可读性,它提供了一种使用描述性字段名称而不是整数索引来访问值的方法,大多数时候,整数索引不会提供有关值的任何上下文。此功能还使代码更简洁、更易于维护。namedtuple
相比之下,将索引用于常规元组中的值可能会很烦人、难以阅读且容易出错。如果元组有很多字段。
正常情况的元组使用的是索引:
a = 1,2,3
print(type(a))
print(a[0], a[1],a[2])
<class 'tuple'>
1 2 3
namedtuple与tuple的区别
它除了命名元组的这一主要特征外, :
- 是不可变的数据结构
- 具有一致的哈希值
- 可以用作字典键
- 可成套存取
- 根据类型和字段名称提
- 提供有用的字符串表示形式,以格式打印元组内容name=value
- 支持索引
- 提供其他方法和属性,例如 ._make()、_asdict()、._fields 等
- 向后兼容常规元组
- 具有与常规元组相似的内存消耗
实际上它的一个核心功能是对元素的存储可以通过key云获取,这一点有点像字典类型。
创建nametuple
>>> # Create a 2D point as a tuple
>>> point = (2, 4)
>>> point
(2, 4)
>>> # Access coordinate x
>>> point[0]
2
>>> # Access coordinate y
>>> point[1]
4
>>> # Try to update a coordinate value
>>> point[0] = 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
可以看到point这个tuple是通过索引值取值,但是不能修改其中的元素。
像创建class一样创建namedtuple
>>> from collections import namedtuple
>>> # Create a namedtuple type, Point
>>> Point = namedtuple("Point", "x y")
>>> issubclass(Point, tuple)
True
>>> # Instantiate the new type
>>> point = Point(2, 4)
>>> point
Point(x=2, y=4)
>>> # Dot notation to access coordinates
>>> point.x
2
>>> point.y
4
>>> # Indexing to access coordinates
>>> point[0]
2
>>> point[1]
4
>>> # Named tuples are immutable
>>> point.x = 100
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
Point = namedtuple("Point", "x y")中有两个属性x,y可以通过point.xpoint.y云取得这个数据。
print("=====namedtuple=====")
Point = namedtuple("Point", "x y")
print(Point.x)
print(Point)
point = Point(2, 4)
print(point)
print(point.x,point.y)
print(point[0],point[1],)
print("===========")
=====namedtuple=====
<_collections._tuplegetter object at 0x00000279F62F2D60>
<class '__main__.Point'>
Point(x=2, y=4)
2 4
2 4
===========
如果仔细观察可以发现
- Point = namedtuple("Point", "x y")创建了一个类似于Point的class
- 有两个属性x,y
- point = Point(2, 4)些时创建了一个Point类型的实例point属性x,y分别赋值
如何获取这个typle的值:
- 通过索引:print(point[0],point[1],)
- 通过自定义索引 print(point.x,point.y)
最后得到的结果是一样的。但是元组的每个元有了对应的名字x,y 这样元素值都有了实际的含义。
namedtuple的嵌套赋值
>>> from collections import namedtuple
>>> Person = namedtuple("Person", "name children")
>>> john = Person("John Doe", ["Timmy", "Jimmy"])
>>> john
Person(name='John Doe', children=['Timmy', 'Jimmy'])
>>> id(john.children)
139695902374144
>>> john.children.append("Tina")
>>> john
Person(name='John Doe', children=['Timmy', 'Jimmy', 'Tina'])
>>> id(john.children)
139695902374144
>>> hash(john)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
- Person = namedtuple("Person", "name children")有两个属性name children
- john = Person("John Doe", ["Timmy", "Jimmy"])分别给两个属性赋值其中children是一个列表
Person = namedtuple("Person", "name children")
john = Person("John Doe", ["Timmy", "Jimmy"])
print(john )
print("======")
===========
Person(name='John Doe', children=['Timmy', 'Jimmy'])
======
实际上你仍然可以使用索引获取数据。
带有默认值的namedtuple
Developer = namedtuple( "Developer",
"name level language",
defaults=["Junior", "Python"]
)
print(Developer("John"))
=====
Developer(name='John', level='Junior', language='Python')
=====
可以看到我们只是给 Developer设置了一个名字,其他的两个属性levellanguage使用的默认值
给namedtuple设置模块信息
>>> from collections import namedtuple
>>> Point = namedtuple("Point", "x y", module="custom")
>>> Point
<class 'custom.Point'>
>>> Point.__module__
'custom'
- module="custom"
- <class 'custom.Point'> 此时 Point变成custom模块下的一个类了
使用 _make函数创建namedtuple的一个对象实例
>>> from collections import namedtuple
>>> Person = namedtuple("Person", "name age height")
>>> Person._make(["Jane", 25, 1.75])
Person(name='Jane', age=25, height=1.75)
使用_asdict 函数把namedtuple转换成dict字典类型
>>> from collections import namedtuple
>>> Person = namedtuple("Person", "name age height")
>>> jane = Person("Jane", 25, 1.75)
>>> jane._asdict()
{'name': 'Jane', 'age': 25, 'height': 1.75}
使用_replace函数替换nmaetuple实例中的属性值
>>> from collections import namedtuple
>>> Person = namedtuple("Person", "name age height")
>>> jane = Person("Jane", 25, 1.75)
>>> # After Jane's birthday
>>> jane = jane._replace(age=26)
>>> jane
Person(name='Jane', age=26, height=1.75)
使用_fields来扩展namedtuple
>>> from collections import namedtuple
>>> Person = namedtuple("Person", "name age height")
>>> ExtendedPerson = namedtuple(
... "ExtendedPerson",
... [*Person._fields, "weight"]
... )
>>> jane = ExtendedPerson("Jane", 26, 1.75, 67)
>>> jane
ExtendedPerson(name='Jane', age=26, height=1.75, weight=67)
>>> jane.weight
67
可以看到Person中有三个属性,ExtendedPerson通过使用Person中的属性,动态 扩展一个 weight属性,这样ExtendedPerson具备了四个属性。这个功能有点像class面向对象编程中的继续,子类拥有父类的属性民。然而这两种实际上是不同的概念,只是表现形式上有点类似。
使用nametuple写更好的代码
可以比较以下两段程序的逻辑,发现使用namedtuple列好的表示出不同字字段的函数
>>> from collections import namedtuple
>>> Pen = namedtuple("Pen", "width style beveled")
>>> pen = Pen(2, "Solid", True)
>>> if pen.width == 2 and pen.style == "Solid" and pen.beveled:
... print("Standard pen selected")
...
>>> pen = (2, "Solid", True)
>>> if pen[0] == 2 and pen[1] == "Solid" and pen[2]:
... print("Standard pen selected")
...
Standard pen selected
使用namedtuple实现函数返回多个值
小知识: divmod函数
- python divmod() 函数把除数和余数运算结果结合起来,
- 返回一个包含商和余数的元组(a // b, a % b)
>>> from collections import namedtuple
>>> def custom_divmod(a, b):
... DivMod = namedtuple("DivMod", "quotient remainder")
... return DivMod(*divmod(a, b))
...
>>> custom_divmod(8, 4)
使用namedtuple减少函数的参数个数
User = namedtuple("User", "username client_name plan")
user = User("john", "John Doe", "Premium")
def create_user(db, user):
db.add_user(user.username)
db.complete_user_profile(
user.username,
user.client_name,
user.plan
)
使用namedtuple从文件数据库中读取数据
如下有一段CSV文件的数据模式
name,job,email
"Linda","Technical Lead","linda@example.com"
"Joe","Senior Web Developer","joe@example.com"
"Lara","Project Manager","lara@example.com"
"David","Data Analyst","david@example.com"
"Jane","Senior Python Developer","jane@example.com"
>>> import csv
>>> from collections import namedtuple
>>> with open("employees.csv", "r") as csv_file:
... reader = csv.reader(csv_file)
... Employee = namedtuple("Employee", next(reader), rename=True)
... for row in reader:
... employee = Employee(*row)
... print(employee.name, employee.job, employee.email)
...
Linda Technical Lead linda@example.com
Joe Senior Web Developer joe@example.com
Lara Project Manager lara@example.com
David Data Analyst david@example.com
Jane Senior Python Developer jane@example.com
namedtuple是否能直接替换字典
两者看起来是一样的,但是字典可以直接使用,动态扩展,而namedtuple只是一个功能扩展,两者解决的问题是不一样的。
>>> from collections import namedtuple
>>> jane = {"name": "Jane", "age": 25, "height": 1.75}
>>> jane["age"]
25
>>> # Equivalent named tuple
>>> Person = namedtuple("Person", "name age height")
>>> jane = Person("Jane", 25, 1.75)
>>> jane.age
25
>>> from collections import namedtuple
>>> jane = {"name": "Jane", "age": 25, "height": 1.75}
>>> jane["age"] = 26
>>> jane["age"]
26
>>> jane["weight"] = 67
>>> jane
{'name': 'Jane', 'age': 26, 'height': 1.75, 'weight': 67}
>>> # Equivalent named tuple
>>> Person = namedtuple("Person", "name age height")
>>> jane = Person("Jane", 25, 1.75)
>>> jane.age = 26
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>> jane.weight = 67
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Person' object has no attribute 'weight
比较namedtuple和dict的性能
可以用下面的程序云测试一下两者的性能
# namedtuple_dict_time.py
from collections import namedtuple
from time import perf_counter
def average_time(structure, test_func):
time_measurements = []
for _ in range(1_000_000):
start = perf_counter()
test_func(structure)
end = perf_counter()
time_measurements.append(end - start)
return sum(time_measurements) / len(time_measurements) * int(1e9)
def time_dict(dictionary):
"x" in dictionary
"missing_key" in dictionary
2 in dictionary.values()
"missing_value" in dictionary.values()
dictionary["y"]
def time_namedtuple(named_tuple):
"x" in named_tuple._fields
"missing_field" in named_tuple._fields
2 in named_tuple
"missing_value" in named_tuple
named_tuple.y
Point = namedtuple("Point", "x y z")
point = Point(x=1, y=2, z=3)
namedtuple_time = average_time(point, time_namedtuple)
dict_time = average_time(point._asdict(), time_dict)
gain = dict_time / namedtuple_time
print(f"namedtuple: {namedtuple_time:.2f} ns ({gain:.2f}x faster)")
print(f"dict: {dict_time:.2f} ns")
namedtuple与@dataclass
实际上这两者的表现形式其实非常的相似
>>> from dataclasses import astuple, dataclass
>>> @dataclass
... class Person:
... name: str
... age: int
... height: float
... weight: float
... country: str = "Canada"
... def __iter__(self):
... return iter(astuple(self))
...
>>> for field in Person("Jane", 25, 1.75, 67):
... print(field)
...
Jane
25
1.75
67
Canada
namedtuple与typing.NamedTuple
Python 3.5 引入了一个名为 typing 的临时模块来支持函数类型注释或类型提示。此模块提供 NamedTuple,它是 的类型化版本。使用 ,您可以创建带有类型提示的类。按照示例,您可以创建一个等效的类型化命名元组,如下所示:namedtupleNamedTuplenamedtuplePerson
>>> from typing import NamedTuple
>>> class Person(NamedTuple):
... name: str
... age: int
... height: float
... weight: float
... country: str = "Canada"
...
>>> issubclass(Person, tuple)
True
>>> jane = Person("Jane", 25, 1.75, 67)
>>> jane.name
'Jane'
>>> jane.name = "Jane Doe"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
使用 ,您可以创建支持类型提示和通过点表示法进行属性访问的元组子类。由于生成的类是元组子类,因此它也是不可变的。NamedTuple
在上面的示例中需要注意的一个微妙细节是,子类看起来更类似于数据类,而不是命名元组。NamedTuple
通过继而的方式来使用namedtuple
定义一个类继承nametuple
>>> from collections import namedtuple
>>> from datetime import date
>>> BasePerson = namedtuple(
... "BasePerson",
... "name birthdate country",
... defaults=["Canada"]
... )
>>> class Person(BasePerson):
... """A namedtuple subclass to hold a person's data."""
... __slots__ = ()
... def __repr__(self):
... return f"Name: {self.name}, age: {self.age} years old."
... @property
... def age(self):
... return (date.today() - self.birthdate).days // 365
...
>>> Person.__doc__
"A namedtuple subclass to hold a person's data."
>>> jane = Person("Jane", date(1996, 3, 5))
>>> jane.age
25
>>> jane
Name: Jane, age: 25 years old.
总结namedtuple一个让元素有name的元组
- 它是一个tuple类
- 有点像字典
- 有点像dataclass
- 可以被继承