1. 前言
Python中有许多常用的内置类型,如列表、字典、元组等。这些类型在处理数据时非常便捷,但在某些场合下,它们的功能还不够强大。比如元组类型,它不能像列表一样动态增加和删除元素。Python提供了一个名为namedtuple的工厂函数,它可以用来创建类似于元组的类型。namedtuple实现了元组风格的不可变性,同时还提供了字段命名和访问位置固定的属性,因此namedtuple比内置元组更加强大和便捷。
2. namedtuple简介
2.1 namedtuple使用方法
namedtuple函数是Python标准库中的一个工厂函数,它位于collections模块中。namedtuple的使用非常简单,我们只需要指定所需元组的名称和字段名称即可生成一个新的namedtuple类型。
from collections import namedtuple
# 手动创建一个namedtuple类型
Point = namedtuple('Point', ['x', 'y'])
pt = Point(1, 2)
print(pt.x, pt.y) # 1 2
# 使用point子类创建namedtuple类型
class PointSubclass(namedtuple('Point', ['x', 'y'])):
def __init__(self, x, y, color):
super().__init__(x=x, y=y)
self.color = color
pt_sub = PointSubclass(1, 2, 'red')
print(pt_sub.x, pt_sub.y, pt_sub.color) # 1 2 red
注意,在上述的PointSubclass类的定义中,我们使用了namedtuple作为父类,并且在子类初始化函数中调用了父类的初始化函数。这样我们就可以定义自己的字段,同时继承父类的namedtuple特性。
2.2 namedtuple的特性
namedtuple具有以下特性:
namedtuple是一个类,因此可以像其他类一样进行继承、多态等。
命名元组是不可变的,即创建之后不能对其进行修改,但可以通过替换操作创建新的命名元组。
可以使用命名元组的名称访问各个字段,也可以通过位置进行访问。
namedtuple可以通过索引、切片等方式进行迭代。
可以使用_dict方法将namedtuple转换为字典类型。
3. namedtuple实现过程
命名元组的实现非常简单,它是一个继承自tuple的子类。具体实现方法可以参考以下代码:
def namedtuple(typename, field_names, *, verbose=False, rename=False):
cls_definition = class_def(typename, field_names, verbose, rename)
exec(cls_definition, namespace)
result = namespace[typename]
result._source = cls_definition
return result
def class_def(name, fields, verbose=False, rename=False):
field_names = parse_fields(fields)
if rename:
names = list(get_names(field_names))
else:
names = field_names
arg_list = ', '.join(names)
field_defs = ', '.join(f"{name}=0" for name in names)
if verbose:
print(f"Class: {name}", " ".join(names), sep="\n")
return f"class {name}(tuple):\n" \
f" '{name}({', '.join(names)})'\n" \
f" __slots__ = ()\n" \
f" _fields = {names!r}\n" \
f" def __new__(cls, {arg_list}):\n" \
f" return tuple.__new__(cls, ({arg_list}))\n" \
f" @classmethod\n" \
f" def _make(cls, iterable, new=tuple.__new__, len=len):\n" \
f" return cls(*iterable)\n" \
f" def _replace(_self, **kwds):\n" \
f" 'Return a new {name} object replacing specified fields with new values'\n" \
f" result = _self._make(map(kwds.pop, {names!r}, _self))\n" \
f" if kwds:\n" \
f" raise ValueError(f'Got unexpected field names: {list(kwds.keys())!r}')\n" \
f" return result\n" \
f" def __repr__(self):\n" \
f" return '{name}({0})'.format(', '.join(f'{name}={{self[{i}]!r}}' for i, name in enumerate(self._fields)))\n" \
f" def _asdict(self):\n" \
f" 'Return a new dict which maps field names to their corresponding values'\n" \
f" return collections.OrderedDict(zip(self._fields, self))\n"
namedtuple函数接收三个参数:元组名称typename、字段名称field_names和两个可选参数verbose和rename。在class_def中,它将field_names解析为一个单独的列表,并根据rename标志选择在列表中使用原始字段名称或重命名字段名称。然后,它使用元组类建立继承关系,并在__new__方法中调用tuple.__new__方法来实例化一个新对象。
namedtuple类中还定义了一些其他方法,包括_make方法、_replace方法和_asdict方法。这些方法都是用来创建并操作命名元组对象的。
3.1 _make方法
_make方法是一个类方法,它用于从可迭代对象中创建一个命名元组。具体方法如下:
class Point(namedtuple('Point', ['x', 'y'])):
pass
pt = Point._make([1,2])
print(pt) # Point(x=1, y=2)
在上面的例子中,我们使用Point._make()从列表[1, 2]中创建了一个新的Point实例。
3.2 _replace方法
_replace方法是一个实例方法,它用于创建并返回一个新的命名元组对象,该对象是当前对象的拷贝,但是具有某些新的值。例如:
pt = Point(1,2)
pt_new = pt._replace(x=3)
print(pt_new) # Point(x=3, y=2)
在上面的例子中,我们创建了一个新的Point对象pt,它的x值为1,y值为2。接着,我们使用pt._replace()创建了一个新的命名元组对象pt_new,并把pt_new的x字段值设置为3,从而创建了一个新的命名元组对象,它的x值为3,y值为2。
3.3 _asdict方法
_asdict方法是一个实例方法,它将命名元组对象转换为一个有序字典。在实际应用中,例如,您可以将它用于将命名元组对象转换为XML或JSON格式。以下是一个使用_asdict实现XML序列化的示例:
p = Point(1,2)
doc = xml.dom.minidom.Document()
point = doc.createElement('point')
for name, value in p._asdict().items():
e = doc.createElement(name)
e.appendChild(doc.createTextNode(str(value)))
point.appendChild(e)
doc.appendChild(point)
print(doc.toprettyxml())
# 输出:
#
#
# 1
# 2
#
4. 总结
Python提供的namedtuple函数是一种非常便捷和强大的实现方式,它为我们提供了一个类似于元组的定长不可变数据结构,并且可以使用字段名来代替下标访问元素。命名元组的实现非常简单,它只是一个继承自tuple的子类。我们可以使用namedtuple函数来创建一个新的命名元组类型,并且可以使用点运算符来访问其中的字段。namedtuple还提供了_make、_replace和_asdict等实例方法,用于创建和操作命名元组对象。在实际应用中,命名元组非常方便,在处理大型数据集时尤其适用。