如何实现numpy的花哨索引?

cs9*_*s95 22 python arrays indexing numpy

我正在对2D列表和numpy数组进行一些实验.由此,我提出了3个问题,我很想知道答案.

首先,我初始化了一个2D python列表.

>>> my_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Run Code Online (Sandbox Code Playgroud)

然后我尝试使用元组索引列表.

>>> my_list[:,]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: list indices must be integers, not tuple
Run Code Online (Sandbox Code Playgroud)

由于解释器抛出一个TypeError而不是一个SyntaxError,我猜测它实际上可以这样做,但是python本身并不支持它.

然后我尝试将列表转换为numpy数组并执行相同的操作.

>>> np.array(my_list)[:,]
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])
Run Code Online (Sandbox Code Playgroud)

当然没问题.我的理解是,其中一个__xx__()方法已被覆盖并在numpy包中实现.

Numpy的索引也支持列表:

>>> np.array(my_list)[:,[0, 1]]
array([[1, 2],
       [4, 5],
       [7, 8]])
Run Code Online (Sandbox Code Playgroud)

这提出了几个问题:

  1. 哪个__xx__方法有numpy覆盖/定义来处理花哨的索引?
  2. 为什么python列表本身不支持花式索引?

(额外的问题:为什么我的时间显示python2中的切片比python3慢?)

sen*_*rle 23

你有三个问题:

1.哪个__xx__方法有numpy覆盖/定义来处理花哨的索引?

索引操作符[]是重写的使用__getitem__,__setitem____delitem__.编写一个提供一些内省的简单子类会很有趣:

>>> class VerboseList(list):
...     def __getitem__(self, key):
...         print(key)
...         return super().__getitem__(key)
...
Run Code Online (Sandbox Code Playgroud)

我们先做一个空的:

>>> l = VerboseList()
Run Code Online (Sandbox Code Playgroud)

现在填写一些值.请注意,我们还没有覆盖,__setitem__所以没有任何有趣的事情发生:

>>> l[:] = range(10)
Run Code Online (Sandbox Code Playgroud)

现在让我们来一个项目.索引0将是0:

>>> l[0]
0
0
Run Code Online (Sandbox Code Playgroud)

如果我们尝试使用元组,我们会收到错误,但我们先看到元组!

>>> l[0, 4]
(0, 4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in __getitem__
TypeError: list indices must be integers or slices, not tuple
Run Code Online (Sandbox Code Playgroud)

我们还可以了解python如何在内部表示切片:

>>> l[1:3]
slice(1, 3, None)
[1, 2]
Run Code Online (Sandbox Code Playgroud)

你可以用这个对象做更多有趣的事情 - 试一试!

2.为什么python列表本身不支持花式索引?

这很难回答.思考它的一种方式是历史:因为numpy开发人员首先考虑它.

你是年轻人.当我还是孩子的时候...

在1991年第一次公开发布时,Python没有numpy库,并且要创建一个多维列表,你必须嵌套列表结构.我认为早期的开发人员 - 特别是Guido van Rossum(GvR) - 认为保持简单是最好的,最初.切片索引已经相当强大.

然而,不久之后,人们对使用Python作为科学计算语言的兴趣越来越大.1995年至1997年间,许多开发人员合作开发了一个名为" numeric早期前身"的图书馆numpy.虽然他不是主要的贡献者,numeric或者numpyGvR与numeric开发人员协调,但是扩展了Python的切片语法,使得多维数组索引更容易.后来,又numeric出现了另一种叫做numarray; 并于2006 numpy年创建,融合了两者的最佳功能.

这些库功能强大,但它们需要大量的c扩展等等.将它们放入基础Python发行版会使它变得笨重.尽管GvR确实增强了切片语法,但是为普通列表添加花哨的索引会大大改变它们的API - 并且有点冗余.鉴于已经可以与外部图书馆进行花哨的索引,这样做的好处并不值得.

这种叙述的部分内容都是推测性的,说实话.1我真的不认识开发者!但这是我做出的同样决定.事实上...

它应该是这样的.

虽然花哨的索引非常强大,但我很高兴它甚至不是今天的vanilla Python的一部分,因为这意味着你在使用普通列表时不必非常努力.对于许多任务,你不需要它,它所带来的认知负荷是很重要的.

请记住,我在谈论强加给读者维护者的负担.你可能是一个天才的天才,可以在你的头脑中做5-d张量产品,但其他人必须阅读你的代码.保持花哨的索引numpy意味着人们不会使用它,除非他们诚实地需要它,这使得代码更易于阅读和维护.

3.为什么numpy的花式索引在python2上如此之慢?是因为我在这个版本中没有本地BLAS支持numpy吗?

有可能.这绝对是环境依赖的; 我在机器上看不出相同的区别.


1.叙述的部分不是推测性的,是从科学与工程计算特刊(2011年第13卷)中的简短历史中得出的.

  • 虽然其他答案同样出色,但我对这节小历史课很感兴趣。感谢您的详细,深思熟虑的答复。 (2认同)

hpa*_*ulj 5

my_list[:,] 由口译员翻译成

my_list.__getitem__((slice(None, None, None),))
Run Code Online (Sandbox Code Playgroud)

就像使用调用函数一样*args,但是需要将:符号转换为slice对象。没有,它将通过slice。随着,它通过一个元组。

该列表__getitem__不接受元组,如错误所示。数组__getitem__可以。我相信通过元组和创建切片对象的功能是为numpy(或其前身)添加了便利。元组符号从未添加到列表中__getitem__。(有一个operator.itemgetter类允许使用某种形式的高级索引,但在内部它只是一个Python代码迭代器。)

使用数组,您可以直接使用元组符号:

In [490]: np.arange(6).reshape((2,3))[:,[0,1]]
Out[490]: 
array([[0, 1],
       [3, 4]])
In [491]: np.arange(6).reshape((2,3))[(slice(None),[0,1])]
Out[491]: 
array([[0, 1],
       [3, 4]])
In [492]: np.arange(6).reshape((2,3)).__getitem__((slice(None),[0,1]))
Out[492]: 
array([[0, 1],
       [3, 4]])
Run Code Online (Sandbox Code Playgroud)

查看numpy/lib/index_tricks.py文件中的示例,您可以使用这些有趣的东西__getitem__。您可以使用

np.source(np.lib.index_tricks)
Run Code Online (Sandbox Code Playgroud)

嵌套列表是列表的列表:

在嵌套列表中,子列表独立于包含列表。容器仅具有指向内存中其他位置的对象的指针:

In [494]: my_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
In [495]: my_list
Out[495]: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
In [496]: len(my_list)
Out[496]: 3
In [497]: my_list[1]
Out[497]: [4, 5, 6]
In [498]: type(my_list[1])
Out[498]: list
In [499]: my_list[1]='astring'
In [500]: my_list
Out[500]: [[1, 2, 3], 'astring', [7, 8, 9]]
Run Code Online (Sandbox Code Playgroud)

在这里,我更改第二项my_list;它不再是列表,而是一个字符串。

如果我申请[:]一份清单,我只会得到一份浅表副本:

In [501]: xlist = my_list[:]
In [502]: xlist[1] = 43
In [503]: my_list           # didn't change my_list
Out[503]: [[1, 2, 3], 'astring', [7, 8, 9]]
In [504]: xlist
Out[504]: [[1, 2, 3], 43, [7, 8, 9]]
Run Code Online (Sandbox Code Playgroud)

但在xlist中更改列表的元素的确会在中更改相应的子列表my_list

In [505]: xlist[0][1]=43
In [506]: my_list
Out[506]: [[1, 43, 3], 'astring', [7, 8, 9]]
Run Code Online (Sandbox Code Playgroud)

对我来说,这显示了n维索引(对于numpy数组实现)对于嵌套列表没有意义。嵌套列表只有在其内容允许的范围内才是多维的。它们没有结构或语法上的多维性。

时机

[:]在列表上使用两个不会产生深层副本,也不会沿嵌套工作。它只是重复浅拷贝步骤:

In [507]: ylist=my_list[:][:]
In [508]: ylist[0][1]='boo'
In [509]: xlist
Out[509]: [[1, 'boo', 3], 43, [7, 8, 9]]
Run Code Online (Sandbox Code Playgroud)

arr[:,]只是让一个viewarrview和之间的区别copy是理解基本索引和高级索引之间区别的一部分。

所以alist[:][:]arr[:,]是不同的,但是是制作列表和数组副本的基本方法。既不计算任何内容,也不迭代元素。因此,时间比较不会告诉我们太多。