4

私は Python で線形代数ライブラリを実装しています (おそらく何かが存在することは知っていますが、Python と試験に必要な数学について学ぶために実行しています)。次のような行列:

(私の行列クラスはタプルのサブクラスです。)

  • M = Matrix([list of rows of elements])
  • M[1, 2](1, 2) の要素を取得します
  • M[3]行 3 を取得します

これらは非常に簡単に実行できますが、次のようにスライスも実装したいと考えています。

  • M[:,:]行列全体を返します
  • M[1:6:2]行 1、3、および 5 を返します
  • M[1:6:2, 0:2]最初の 2 列と交差する行 1、3、および 5 で構成される行列を返します。

私はこれを行いましたが、私の答えは非常にPythonicではないようです:

def __getitem__ (self, idx):
    if isinstance(idx, numbers.Integral):
        # Code to return the row at idx
    elif (isinstance(idx, tuple) and len(idx) == 2 and
            all(isinstance(i, numbers.Integral) for i in idx)):
        # Code to return element at idx
    elif (isinstance(idx, tuple) and len(idx) == 2 and
            all(isinstance(i, slice) for i in idx)):
        # Code to parse slices

これに関するもう 1 つの問題は、両方のインデックスが数値またはスライスでなければならないことです。混在させることはできません。このようにするには、さらに 2 つの elif ブロックが必要になります。すでにコードは本当に醜いです。

答えにはダックタイピングが含まれると思いますが、それを実装する方法が完全にはわかりません。私はtry:except:ブロックを見てきましたが、それらを連鎖させる方法がわかりません。また、あまり入れ子にしたくありません。

それでは、お読みいただきありがとうございます。このような機能を実装する最良の方法は何ですか?

4

1 に答える 1

4

このようなことをしなければならないことはほとんどありませんが、少なくとも重複を取り除くことはできます。

[1,]まず、 のように「行 1」を意味すると考えるのがおそらく合理的[1]です。(numpyこれを行います。) つまり、tuple-vs.-int は必要ありません。int を 1 要素のタプルとして扱うだけです。言い換えると:

def __getitem__(self, idx):
    if isinstance(idx, numbers.Integral):
        idx = (idx, slice(None, None, None))
    # now the rest of your code only needs to handle tuples

次に、サンプル コードでは 2 つのスライスの場合しか処理できませんが、実際のコードでは 2 つのスライス、またはスライスと int、または int とスライス、または 2 つの int、またはスライス、または int を処理する必要があります。スライス処理コードを除外できれば、何度も複製する必要はありません。

int-vs.-slice を処理するための 1 つの秘訣は[n]、本質的に を実行するラッパーとして扱う[n:n+1][0]ことです。これにより、すべてをさらに削減できます。(これよりも少しトリッキーです。-1なぜなら、一般に負の数を特殊なケースにする必要があるか、明らかにn[-1] != n[-1:0][0].つまり、列を扱っている間は、ただの行ではなく、常に行のリストを取得していることになります。

一方で、いくつかのコードを__getitem____setitem__… の間で共有したいと思うかもしれません。したがって、トレードオフがあります。

いずれにせよ、これは私が考えることができるすべての単純化と前処理/後処理を行う例です (おそらくあなたが望む以上のものです)。これにより、最終的には常にスライスのペアを検索することになります:

class Matrix(object):
    def __init__(self):
        self.m = [[row + col/10. for col in range(4)] for row in range(4)]
    def __getitem__(self, idx):
        if isinstance(idx, (numbers.Integral, slice)):
            idx = (idx, slice(None, None, None))
        elif len(idx) == 1:
            idx = (idx[0], slice(None, None, None))
        rowidx, colidx = idx
        rowslice, colslice = True, True
        if isinstance(rowidx, numbers.Integral):
            rowidx, rowslice = slice(rowidx, rowidx+1), False
        if isinstance(colidx, numbers.Integral):
            colidx, colslice = slice(colidx, colidx+1), False
        ret = self.m[rowidx][colidx]
        if not colslice:
            ret = [row[0] for row in ret]
        if not rowslice:
            ret = ret[0]
        return ret

または、他の軸に沿ってリファクタリングした方が良いかもしれません: 行を取得してから、その中の列を取得します:

def _getrow(self, idx):
    return self.m[idx]

def __getitem__(self, idx):
    if isinstance(idx, (numbers.Integral, slice)):
        return self._getrow(idx)
    rowidx, colidx = idx
    if isinstance(rowidx, numbers.Integral):
        return self._getrow(rowidx)[colidx]
    else:
        return [row[colidx] for row in self._getrow(rowidx)]

これはかなり単純に見えますが、ここでは 2 番目のインデックスを normal に転送することでごまかしてlistlistますlist。しかし、延期する何らかの種類のインデックス可能な行オブジェクトがある場合 (そして、それらのオブジェクトを不必要に作成するために許容できない時間/スペースを無駄にしない場合)、同じチートを使用できます。


index パラメーターで型を切り替える必要があることに反対している場合は、はい、それは一般的に非 Pythonic のように見えますが、残念ながら、それは一般的にどのように機能するか__getitem__です。通常の EAFTP ロジックを使用したい場合は使用できますが、複数の場所で 2 つの異なる API (たとえば、タプル用とスライス用)tryを試す必要がある場合は、読みやすくなるとは思いません。次のように、上部で「ダックタイプの切り替え」を行うことになります。[0].start

try:
    idx[0]
except AttributeError:
    idx = (idx, slice(None, None, None))

…など、これは通常の型切り替えの 2 倍のコードであり、通常のメリットはありません。

于 2013-03-27T20:53:53.063 に答える