web-dev-qa-db-ja.com

numpyのファンシーインデックスはどのように実装されますか?

2Dリストとnumpy配列で少し実験をしていました。このことから、答えを知りたいと思う3つの質問を挙げました。

最初に、2D pythonリストを初期化しました。

_>>> my_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
_

次に、タプルを使用してリストのインデックスを作成してみました。

_>>> my_list[:,]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: list indices must be integers, not Tuple
_

インタプリタがTypeErrorではなくSyntaxErrorをスローするので、実際にこれを行うことは可能だと思いましたが、pythonはそれをネイティブでサポートしていません。

次に、リストをnumpy配列に変換して、同じことを試みました。

_>>> np.array(my_list)[:,]
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])
_

もちろん問題ありません。私の理解では、__xx__()メソッドの1つがnumpyパッケージでオーバーライドおよび実装されています。

Numpyのインデックス作成もリストをサポートしています:

_>>> np.array(my_list)[:,[0, 1]]
array([[1, 2],
       [4, 5],
       [7, 8]])
_

これにより、いくつかの質問が生じました。

  1. ファンシーインデックスを処理するために、どの___xx___メソッドがnumpyオーバーライド/定義されていますか?
  2. pythonリストがネイティブにファンシーインデックスをサポートしないのはなぜですか?

(ボーナス質問:なぜ私のタイミングはpython2でのスライスがpython3より遅いことを示しているのですか?)

22
cs95

次の3つの質問があります。

1.ファンシーインデックスを処理するために、どの__xx__メソッドがnumpyオーバーライド/定義されていますか?

インデックス付け演算子[]は、__getitem____setitem__、および__delitem__を使用してオーバーライドできます。いくつかの内省を提供する単純なサブクラスを書くのは楽しいかもしれません:

>>> class VerboseList(list):
...     def __getitem__(self, key):
...         print(key)
...         return super().__getitem__(key)
...

最初に空のものを作ってみましょう:

>>> l = VerboseList()

次に、いくつかの値を入力します。 __setitem__をオーバーライドしていないため、まだ興味深いことは何も起こりません。

>>> l[:] = range(10)

それではアイテムを取得しましょう。インデックス00になります:

>>> l[0]
0
0

タプルを使用しようとすると、エラーが発生しますが、最初にタプルが表示されます。

>>> 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

また、pythonがスライスを内部的にどのように表すかを知ることもできます:

>>> l[1:3]
slice(1, 3, None)
[1, 2]

このオブジェクトでできることはもっとたくさんあります-試してみてください!

2. pythonリストがネイティブにファンシーインデックスをサポートしないのはなぜですか?

これは答えるのが難しいです。それについて考える1つの方法は歴史的です:numpy開発者が最初にそれを考えたからです。

あなたは若者です。子供の頃...

1991年の最初の公開リリースでは、Pythonにはnumpyライブラリがなく、多次元リストを作成するには、リスト構造をネストする必要がありました。初期の開発者は、 -特に、Guido van Rossum( GvR )-最初は物事をシンプルに保つことが最善であると感じました。スライスのインデックス作成はすでにかなり強力でした。

しかし、それから間もなく、Python=を科学的コンピューティング言語として使用することに関心が高まりました。1995年から1997年にかけて、多くの開発者が初期の前身であるnumericというライブラリで共同作業を行いました。 of numpy。彼はnumericまたはnumpyの主要な貢献者ではありませんでしたが、G [R]はnumeric開発者と調整し、多次元配列のインデックス付けを行う方法でPythonのスライス構文を拡張しました後で、numericの代わりにnumarrayという名前が使用され、2006年に、両方の優れた機能を組み込んだnumpyが作成されました。

これらのライブラリは強力でしたが、重いc拡張などが必要でした。それらをベースに組み込むと、Python=ディストリビューションはかさばるようになります。GvRはスライス構文を少し拡張しましたが、通常のリストにファンシーインデックスを追加すると、APIが劇的に、そして多少冗長に変更されます。派手なインデックス作成は外部のライブラリで既に可能であったことを考えると、メリットはコストに見合うものではありませんでした。

この物語の一部は、すべての正直において投機的です。1 私は開発者を本当に知りません!しかし、それは私が下したであろう決定と同じです。実際には...

それは本当にそうあるべきです。

ファンシーインデックスは非常に強力ですが、それがバニラの一部ではないことをうれしく思いますPythonこれは、通常のリストを操作するときにそれほど深く考える必要がないことを意味します。あなたがそれを必要としないタスク、およびそれが課す認知負荷は重要です。

readersmaintainersに課される負荷について話していることを覚えておいてください。あなたは頭の中で5次元テンソル積を実行できる天才かもしれませんが、他の人はあなたのコードを読まなければなりません。 numpyのファンシーインデックスを維持することは、人々が正直にそれを必要としない限りそれを使用しないことを意味します。

3. python2でnumpyの豪華なインデックス作成がなぜ遅いのですか?このバージョンでは、numpyのネイティブBLASサポートがないためですか?

たぶん。それは明らかに環境に依存しています。私のマシンでも同じ違いは見られません。


1.投機的ではないナラティブの部分は、科学とエンジニアリングにおけるコンピューティングの特別号(2011 vol。13)で語られた 簡単な歴史 から引用されています。

24
senderle

my_list[:,]はインタプリタによって次のように翻訳されます

my_list.__getitem__((slice(None, None, None),))

*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]])

numpy/lib/index_tricks.pyファイルで、__getitem__でできる楽しいことの例をご覧ください。あなたはファイルを見ることができます

np.source(np.lib.index_tricks)

ネストされたリストはリストのリストです:

ネストされたリストでは、サブリストは包含リストから独立しています。コンテナーには、メモリ内の他の場所にあるオブジェクトへのポインターがあります。

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]]

ここで、my_listの2番目の項目を変更します。リストではなく文字列です。

リストに[:]を適用すると、浅いコピーが得られます。

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]]

ただし、xlistのリストの要素を変更すると、my_listの対応するサブリストも変更されます。

In [505]: xlist[0][1]=43
In [506]: my_list
Out[506]: [[1, 43, 3], 'astring', [7, 8, 9]]

私にとって、これは(numpy配列に実装されている)n次元のインデックスによる表示は、ネストされたリストでは意味がありません。ネストされたリストは、その内容が許す範囲でのみ多次元です。それらについては、構造的または構文的に多次元的なものは何もありません。

タイミング

リストで2つの[:]を使用しても、ディープコピーは作成されず、ネストの途中で機能しません。浅いコピー手順を繰り返すだけです。

In [507]: ylist=my_list[:][:]
In [508]: ylist[0][1]='boo'
In [509]: xlist
Out[509]: [[1, 'boo', 3], 43, [7, 8, 9]]

arr[:,]は、viewarrにします。 viewcopyの違いは、基本的なインデックスと高度なインデックスの違いを理解するための一部です。

したがって、alist[:][:]arr[:,]は異なりますが、リストや配列のコピーを作成する基本的な方法です。どちらも何も計算せず、要素全体を反復しません。したがって、タイミングの比較ではあまりわかりません。

5
hpaulj

ファンシーインデックスを処理するために、どの__xx__メソッドがnumpyオーバーライド/定義されていますか?

__getitem__は取得、__setitem__は割り当て。削除の場合は__delitem__になりますが、NumPy配列は削除をサポートしていません。

(ただし、すべてCで記述されているため、Cレベルで実装されたものはmp_subscriptおよびmp_ass_subscriptであり、__getitem__および__setitem__ラッパーはPyType_Readyによって提供されました。__delitem____setitem____delitem__の両方がCレベルでmp_ass_subscriptにマッピングされるため、削除はサポートされていません。

pythonリストがネイティブにファンシーインデックスをサポートしないのはなぜですか?

Pythonリストは基本的に1次元構造ですが、NumPy配列は任意次元です。多次元索引付けは、多次元データ構造に対してのみ意味があります。

[[1, 2], [3, 4]]のように、リストを要素として持つリストを作成できますが、リストはその要素の構造を認識していないか、気にしていません。リストがl[:, 2]インデックスをサポートするようにするには、リストが意図されていない方法でリストが多次元構造を認識する必要があります。それはまた、多くの複雑さ、多くのエラー処理、そして多くの余分な設計上の決定を追加します-コピーはどのくらい深くするべきですかl[:, :]?構造が不規則である、または一貫性のない入れ子の場合はどうなりますか?多次元索引付けはリスト以外の要素に再帰する必要がありますか? del l[1:3, 1:3]はどうしますか?

NumPyのインデックス作成の実装を見てきましたが、リストの実装全体よりも長くなっています。 これはその一部です。 NumPy配列があなたがそれを必要とする本当に説得力のあるすべてのユースケースを満たすときにリストにそれをする価値はありません。

なぜnumpyの派手なインデックス付けはpython2でとても遅いのですか?このバージョンでは、numpyのネイティブBLASサポートがないためですか?

NumPyのインデックス付けはBLAS操作ではないため、そうではありません。私は できない再現 そのような劇的なタイミングの違い、そして私が見る違いはマイナーのように見えますPython 3つの最適化、おそらく少し効率的タプルまたはスライスの割り当て。表示されているのは、おそらくNumPyのバージョンの違いによるものです。