• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

如何在 Python 中实现自己的数据结构

python 为使用类和自定义运算符实现您自己的数据结构提供了全面的支持。在本教程中,您将实现一个可以对其数据执行任意操作的自定义管道数据结构。我们将使用 Python 3。

如何在 Python 中实现自己的数据结构  第1张

管道数据结构

管道数据结构很有趣,因为它非常灵活。它由一系列任意函数组成,这些函数可以应用于对象集合并产生结果列表。我将利用 Python 的可扩展性,使用管道符 ( |) 来构造管道。

实例

在深入了解所有细节之前,让我们先看看一个非常简单的流水线:

x = range(5) | Pipeline() | double | Ω
print(x)
 
[0, 2, 4, 6, 8]

这里发生了什么?让我们一步一步地分解它。第一个元素range(5)创建一个整数列表 [0, 1, 2, 3, 4]。整数被送入由指定的空管道Pipeline()。然后一个double函数被添加到管道中,最后这个很酷的Ω函数终止了管道并使其自我评估。 

评估包括获取输入并应用管道中的所有函数(在本例中只是double函数)。最后,我们将结果存储在一个名为的变量中x并打印出来。

Python 类

Python 支持类并具有非常复杂的面向对象模型,包括多重继承、混入和动态重载。__init__()函数充当创建新实例的构造函数。Python 还支持一种高级的元编程模型,我们不会在本文中对其进行介绍。 

这是一个简单的类,它有一个__init__()构造函数,该构造函数接受一个可选参数x(默认为 5)并将其存储在一个self.x属性中。它还有一个foo()返回self.x属性乘以 3 的方法:

class A:
    def __init__(self, x=5):
        self.x = x
 
    def foo(self):
        return self.x * 3

以下是使用和不使用显式 x 参数实例化它的方法:

>>> a = A(2)
>>> print(a.foo())
6
 
a = A()
print(a.foo())
15

自定义运算符

使用 Python,您可以为您的类使用自定义运算符以获得更好的语法。有一些特殊的方法被称为“dunder”方法。“dunder”的意思是“双下划线”。这些方法,如__eq__、__gt__和__or__,允许您将 、 和 等运算符==用于>您|的类实例(对象)。让我们看看他们如何与A班级合作。

如果您尝试将 的两个不同实例A相互比较,结果将始终False与 的值无关x:

>>> print(A() == A())
False

这是因为 Python 默认比较对象的内存地址。假设我们要比较 x 的值。我们可以添加一个特殊的__eq__运算符,它接受两个参数,self和other,并比较它们的x属性:

def __eq__(self, other):
    return self.x == other.x

让我们验证一下:

>>> print(A() == A())
True
 
>>> print(A(4) == A(6))
False

将管道实现为 Python 类

现在我们已经介绍了 Python 中类和自定义运算符的基础知识,让我们用它来实现我们的管道。构造__init__()函数采用三个参数:函数、输入和终端。“函数”参数是一个或多个函数。这些函数是管道中对输入数据进行操作的阶段。 

“输入”参数是管道将操作的对象列表。输入的每一项都将由所有管道函数处理。“terminals”参数是一个函数列表,当遇到其中一个时,管道会自行评估并返回结果。默认情况下,终端只是打印函数(在 Python 3 中,print是一个函数)。 

请注意,在构造函数内部,一个 mysteriousΩ被添加到终端。接下来我会解释。 

管道构造器

这是类定义和__init__()构造函数:

class Pipeline:
    def __init__(self,
                 functions=(),
                 input=(),
                 terminals=(print,)):
        if hasattr(functions, '__call__'):
            self.functions = [functions]
        else:
            self.functions = list(functions)
        self.input = input
        self.terminals = [Ω] + list(terminals)

Python 3 完全支持标识符名称中的 Unicode。这意味着我们可以使用很酷的符号Ω,比如变量和函数名。在这里,我声明了一个名为 的标识函数Ω,它用作终端函数: 

Ω = lambda x: x

我也可以使用传统语法:

def Ω(x):
    return x

和运算__or__ 符__ror__

管道类的核心来了。为了使用|(管道符号),我们需要覆盖几个运算符。该|符号由 Python 用于按位或整数。在我们的例子中,我们想要覆盖它以实现函数链接以及在管道的开头提供输入。这是两个独立的操作。

__ror__只要第一个操作数不是管道实例,第二个操作数就会调用该运算符。它将第一个操作数视为输入并将其存储在self.input属性中,并返回管道实例(自身)。这允许稍后链接更多功能。

def __ror__(self, input):
    self.input = input
    return self

__ror__()下面是调用运算符 的示例: 'hello there' | Pipeline()。

当__or__第一个操作数是管道时调用运算符(即使第二个操作数也是管道)。它接受操作数作为可调用函数,并断言func操作数确实是可调用的。 

然后,它将函数附加到self.functions属性并检查函数是否是终端函数之一。如果是终端,则对整个管道进行评估并返回结果。如果它不是终端,则返回管道本身。

def __or__(self, func):
    assert(hasattr(func, '__call__'))
    self.functions.append(func)
    if func in self.terminals:
        return self.eval()
    return self

评估管道

当您向管道中添加越来越多的非终端函数时,什么也没有发生。实际评估被推迟到eval()方法被调用时。这可以通过向管道添加终端函数或eval()直接调用来实现。 

评估包括迭代管道中的所有函数(包括终端函数,如果有的话)并在前一个函数的输出上按顺序运行它们。管道中的第一个函数接收一个输入元素。

def eval(self):
    result = []
    for x in self.input:
        for f in self.functions:
            x = f(x)
        result.append(x)
    return result

有效地使用管道

使用管道的最佳方式之一是将其应用于多组输入。在以下示例中,定义了没有输入和终端功能的管道。它有两个函数:我们之前定义的臭名昭著的double 函数和标准的math.floor. 

然后,我们为它提供三种不同的输入。在内部循环中,我们在Ω调用它时添加终端函数以在打印结果之前收集结果:

p = Pipeline() | double | math.floor
 
for input in ((0.5, 1.2, 3.1),
              (11.5, 21.2, -6.7, 34.7),
              (5, 8, 10.9)):
    result = input | p | Ω
    print(result)
     
[1, 2, 6]
[23, 42, -14, 69]
[10, 16, 21]

您可以print直接使用终端功能,但每个项目都会打印在不同的行上:

keep_palindromes = lambda x: (p for p in x if p[::-1] == p)
keep_longer_than_3 = lambda x: (p for p in x if len(p) > 3)
 
p = Pipeline() | keep_palindromes | keep_longer_than_3 | list
(('aba', 'abba', 'abcdef'),) | p | print
 
['abba']

未来的改进

有一些改进可以使管道更有用:

  • 添加流,以便它可以处理无限的对象流(例如,从文件或网络事件中读取)。

  • 提供一种评估模式,其中整个输入作为单个对象提供,以避免提供一个项目的集合的繁琐解决方法。

  • 添加各种有用的管道功能。

结论

Python 是一种非常有表现力的语言,非常适合设计您自己的数据结构和自定义类型。当语义适用于这种表示法时,覆盖标准运算符的能力非常强大。例如,管道符号 ( |) 对于管道来说是很自然的。 

许多 Python 开发人员喜欢 Python 的内置数据结构,如元组、列表和字典。但是,设计和实现您自己的数据结构可以通过提升抽象级别和向用户隐藏内部细节,使您的系统更简单、更易于使用。试试看。

文章目录
  • 管道数据结构
  • 实例
  • Python 类
  • 自定义运算符
  • 将管道实现为 Python 类
    • 管道构造器
    • 和运算__or__ 符__ror__
    • 评估管道
  • 有效地使用管道
  • 未来的改进
  • 结论
  • 发表评论