Python namedtuple命名元组实现过程解析

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等实例方法,用于创建和操作命名元组对象。在实际应用中,命名元组非常方便,在处理大型数据集时尤其适用。

后端开发标签