0%

优雅的 Python 之 Ellipsis

Python 是一门非常具有包容性的语气,体现在一个优秀的工程师可以非常容易优雅高效地完成一件事情,而一个拙略的工程师通过一样的代码同样可以做到几乎一样的功能。今天,介绍一下 Python 的 Ellipsis~~~

想象这样一个问题:

如何优雅地生成一个等差数组?比如输入一个序列的第一、第二项以及最后一项,然后返回这个等差数组。

这里指的优雅并不是实现代码上,而是调用方式优雅。那么,在具体实现之前,我们先看一眼调用的方式吧:

1
2
3
4
5
6
7
In [1]: gen = SeqGenerator()

In [2]: gen[1, 2, ..., 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]

In [3]: gens[20, 16, ..., 0]
[20, 16, 12, 8, 4, 0]

有意思吧,这样直观的方式调用,简单明了。下面简单聊聊实现原理吧。

其实,在 object 对象中,有一个 __getitem__ 方法。你可以做如下测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
In [1]: class Test(object):
....: def __getitem__(self, item):
....: print item

In [2]: t = Test()

In [2]: t[1]
1

In [3]: t['what the fuck']
what the fuck

In [4]: t[:]
slice(None, None, None)

In [5]: t[1: 10]
slice(1, 10, None)

In [6]: t[1:3:5]
slice(1, 3, 5)

In [7]: t[1, 2]
(1, 2)

In [8]: t[1, 2, ..., 5]
(1, 2, Ellipsis, 5)

可以看到,第八行的调用方式与上面产生等差数列的方式基本是一样的了,但是返回的内容( Test 中事实上并没有返回,只是直接 print 了)不同,多了一个 Ellipsis

那么,对应上面的 SeqGenerator 的实现就一目了然了:

1
2
3
4
5
class SeqGenerator(object):
def __getitem__(self, item):
if not (isinstance(item, tuple) and len(item) == 4 and item[2] is Ellipsis):
raise RuntimeError('what the fuck')
return range(item[0], (item[-1]+1, item[-1]-1)[item[0]>item[1]], item[1]-item[0])

然后就可以愉快地按照上面的示例一样调用啦。当然,上面的实现比较简单,没有完整地考虑到各种情况,如果你愿意,可以自行解决之~~~