8

したがって、これは、私が想定していることについての 2 つの質問です。これは、私の部分と同じ基本的な根本的な混乱です。大丈夫だと思います。

ここにいくつかのコード:

import numpy as np

class new_array(np.ndarray):

    def __new__(cls, array, foo):
        obj = array.view(cls)
        obj.foo = foo
        return obj

    def __array_finalize__(self, obj):
        print "__array_finalize"
        if obj is None: return
        self.foo = getattr(obj, 'foo', None)

    def __getitem__(self, key):
        print "__getitem__"
        print "key is %s"%repr(key)
        print "self.foo is %d, self.view(np.ndarray) is %s"%(
            self.foo,
            repr(self.view(np.ndarray))
            )
        self.foo += 1
        return super(new_array, self).__getitem__(key)

print "Block 1"
print "Object construction calls"
base_array = np.arange(20).reshape(4,5)
print "base_array is %s"%repr(base_array)
p = new_array(base_array, 0)
print "\n\n"

print "Block 2"
print "Call sequence for p[-1:] is:"
p[-1:]
print "p[-1].foo is %d\n\n"%p.foo

print "Block 3"
print "Call sequence for s = p[-1:] is:"
s = p[-1:]
print "p[-1].foo is now %d"%p.foo
print "s.foo is now %d"%s.foo
print "s.foo + p.foo = %d\n\n"%(s.foo + p.foo)

print "Block 4"
print "Doing q = s + s"
q = s + s
print "q.foo = %d\n\n"%q.foo

print "Block 5"
print "Printing s"
print repr(s)
print "p.foo is now %d"%p.foo
print "s.foo is now %d\n\n"%s.foo

print "Block 6"
print "Printing q"
print repr(q)
print "p.foo is now %d"%p.foo
print "s.foo is now %d"%s.foo
print "q.foo is now %d\n\n"%q.foo

print "Block 7"
print "Call sequence for p[-1]"
a = p[-1]
print "p[-1].foo is %d\n\n"%a.foo

print "Block 8"
print "Call sequence for p[slice(-1, None, None)] is:"
a = p[slice(-1, None, None)]
print "p[slice(None, -1, None)].foo is %d"%a.foo
print "p.foo is %d"%p.foo
print "s.foo + p.foo = %d\n\n"%(s.foo + p.foo)

このコードの出力は

Block 1
Object construction calls
base_array is array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19]])
__array_finalize



Block 2
Call sequence for p[-1:] is:
__array_finalize
p[-1].foo is 0


Block 3
Call sequence for s = p[-1:] is:
__array_finalize
p[-1].foo is now 0
s.foo is now 0
s.foo + p.foo = 0


Block 4
Doing q = s + s
__array_finalize
q.foo = 0


Block 5
Printing s
__getitem__
key is -1
self.foo is 0, self.view(np.ndarray) is array([[15, 16, 17, 18, 19]])
__array_finalize
__getitem__
key is -5
self.foo is 1, self.view(np.ndarray) is array([15, 16, 17, 18, 19])
__getitem__
key is -4
self.foo is 2, self.view(np.ndarray) is array([15, 16, 17, 18, 19])
__getitem__
key is -3
self.foo is 3, self.view(np.ndarray) is array([15, 16, 17, 18, 19])
__getitem__
key is -2
self.foo is 4, self.view(np.ndarray) is array([15, 16, 17, 18, 19])
__getitem__
key is -1
self.foo is 5, self.view(np.ndarray) is array([15, 16, 17, 18, 19])
new_array([[15, 16, 17, 18, 19]])
p.foo is now 0
s.foo is now 1


Block 6
Printing q
__getitem__
key is -1
self.foo is 0, self.view(np.ndarray) is array([[30, 32, 34, 36, 38]])
__array_finalize
__getitem__
key is -5
self.foo is 1, self.view(np.ndarray) is array([30, 32, 34, 36, 38])
__getitem__
key is -4
self.foo is 2, self.view(np.ndarray) is array([30, 32, 34, 36, 38])
__getitem__
key is -3
self.foo is 3, self.view(np.ndarray) is array([30, 32, 34, 36, 38])
__getitem__
key is -2
self.foo is 4, self.view(np.ndarray) is array([30, 32, 34, 36, 38])
__getitem__
key is -1
self.foo is 5, self.view(np.ndarray) is array([30, 32, 34, 36, 38])
new_array([[30, 32, 34, 36, 38]])
p.foo is now 0
s.foo is now 1
q.foo is now 1


Block 7
Call sequence for p[-1]
__getitem__
key is -1
self.foo is 0, self.view(np.ndarray) is array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19]])
__array_finalize
p[-1].foo is 1


Block 8
Call sequence for p[slice(-1, None, None)] is:
__getitem__
key is slice(-1, None, None)
self.foo is 1, self.view(np.ndarray) is array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19]])
__array_finalize
p[slice(None, -1, None)].foo is 2
p.foo is 2
s.foo + p.foo = 3

次の 2 点に注意してください。

  1. への呼び出しは、への呼び出しにはなりp[-1:]ませんnew_array.__getitem__。これは、 が 、 などp[-1:]で置き換えられた場合に当てはまりますがp[0:]、やp[0:-1]などのステートメントは を呼び出します。orのようなステートメントにも当てはまりますが、 のようなステートメントには当てはまりません。これは、上記の「ブロック」を見るとわかります。p[-1]p[slice(-1, None, None)]new_array.__getitem__p[-1:] + p[-1:]s = p[-1]print s

  2. 変数fooは の呼び出し中に正しく更新されますがnew_array.__getitem__(ブロック 5 および 6 を参照)、 の評価new_array.__getitem__が完了すると正しくありません (ブロック 5 および 6 を再度参照)。return super(new_array, self).__getitem__(key)また、行を次のように置き換えても機能しないことも付け加えておく必要がありreturn new_array(np.array(self.view(np.ndarray)[key]), self.foo)ます。次のブロックは、出力の唯一の違いです。

    Block 5
    Printing s
    __getitem__
    key is -1
    self.foo is 0, self.view(np.ndarray) is array([[15, 16, 17, 18, 19]])
    __array_finalize__
    __getitem__
    key is -5
    self.foo is 1, self.view(np.ndarray) is array([15, 16, 17, 18, 19])
    __array_finalize__
    __array_finalize__
    __array_finalize__
    __getitem__
    key is -4
    self.foo is 2, self.view(np.ndarray) is array([15, 16, 17, 18, 19])
    __array_finalize__
    __array_finalize__
    __array_finalize__
    __getitem__
    key is -3
    self.foo is 3, self.view(np.ndarray) is array([15, 16, 17, 18, 19])
    __array_finalize__
    __array_finalize__
    __array_finalize__
    __getitem__
    key is -2
    self.foo is 4, self.view(np.ndarray) is array([15, 16, 17, 18, 19])
    __array_finalize__
    __array_finalize__
    __array_finalize__
    __getitem__
    key is -1
    self.foo is 5, self.view(np.ndarray) is array([15, 16, 17, 18, 19])
    __array_finalize__
    __array_finalize__
    __array_finalize__
    new_array([[15, 16, 17, 18, 19]])
    p.foo is now 0
    s.foo is now 1
    
    
    Block 6
    Printing q
    __getitem__
    key is -1
    self.foo is 0, self.view(np.ndarray) is array([[30, 32, 34, 36, 38]])
    __array_finalize__
    __getitem__
    key is -5
    self.foo is 1, self.view(np.ndarray) is array([30, 32, 34, 36, 38])
    __array_finalize__
    __array_finalize__
    __array_finalize__
    __getitem__
    key is -4
    self.foo is 2, self.view(np.ndarray) is array([30, 32, 34, 36, 38])
    __array_finalize__
    __array_finalize__
    __array_finalize__
    __getitem__
    key is -3
    self.foo is 3, self.view(np.ndarray) is array([30, 32, 34, 36, 38])
    __array_finalize__
    __array_finalize__
    __array_finalize__
    __getitem__
    key is -2
    self.foo is 4, self.view(np.ndarray) is array([30, 32, 34, 36, 38])
    __array_finalize__
    __array_finalize__
    __array_finalize__
    __getitem__
    key is -1
    self.foo is 5, self.view(np.ndarray) is array([30, 32, 34, 36, 38])
    __array_finalize__
    __array_finalize__
    __array_finalize__
    new_array([[30, 32, 34, 36, 38]])
    p.foo is now 0
    s.foo is now 1
    q.foo is now 1
    

    への過剰な呼び出しが含まれるようnew_array.__array_finalize__になりましたが、変数の「問題」に変化はありませんfoo

  3. オブジェクト withを likep[-1:]で呼び出すと、このステートメントが返されると予想していました。の呼び出し中に が正しく更新されていたとしても、明らかにそうではありません。なぜなら、 のようなステートメントは(遅延評価が考慮されると)の呼び出しが多数になるからです。さらに、との呼び出しは異なる値になります(カウントが正しく機能していた場合)。前者の場合は追加され、後者の場合は追加されます。new_arrayp.foo = 0p.foo == 1Truefoo__getitem__p[-1:]__getitem__p[-1:]p[slice(-1, None, None)]foofoo5foo1

質問

numpy 配列のスライスの遅延評価は、コードの評価中に問題を引き起こすことはありませんが、pdb を使用してコードをデバッグするのは非常に苦痛でした。基本的に、ステートメントは実行時と pdb で異なる評価をしているように見えます。これは良くないと思います。それが私がこの行動に出くわした方法です。

私のコードは、入力を使用して、__getitem__返されるオブジェクトのタイプを評価します。同じ型の新しいインスタンスを返す場合もあれば、他の型の新しいインスタンスを返す場合もあれば、numpy 配列、スカラー、または浮動小数点数を返す場合もあります (基礎となる numpy 配列が正しいと考えるものに応じて異なります)。 )。渡されたキーを使用して、__getitem__返す正しいオブジェクトを決定します。しかし、ユーザーが のようなスライスを渡した場合p[-1:]、メソッドは個々のインデックスを取得するだけなので、ユーザーが書いたように、これを行うことはできませんp[4]では、numpy サブクラスのkeyin__getitem__が反映されない場合、ユーザーが で指定されたスライス、p[-1:]または で指定された単なるエントリを要求している場合、どうすればよいp[4]でしょうか?

副次的なポイントとして、numpy のインデックス作成slice(start, stop, step)のドキュメントでは、たとえば、スライス オブジェクトが のようなステートメントと同じように扱われることを暗示していますstart:stop:step。これは、非常に基本的なものが欠けていると私に思わせます。これを示唆する文は非常に早い段階で発生します。

基本的なスライスは、obj がスライス オブジェクト (かっこ内の start:stop:step 表記によって構築される)、整数、またはスライス オブジェクトと整数のタプルである場合に発生します。

self.foo += 1この同じ基本的な間違いが、行がユーザーがスライスを要求した回数、またはインスタンスの要素をnew_array(要素の数ではなく)カウントする必要があると考える理由でもあると思わずにはいられません。 " スライス)。これら 2 つの問題は実際に関連しているのでしょうか。

4

2 に答える 2

8

あなたは確かに厄介なバグに噛まれてきました。私だけではないことを知ってほっとしました!幸いなことに、それは簡単に解決できます。次のようなものをクラスに追加するだけです。これは実際には、数か月前に書いたコードからのコピーペーストです。docstringは何が起こっているかを示していますが、Pythonのドキュメントも読むことをお勧めします。

def __getslice__(self, start, stop) :
    """This solves a subtle bug, where __getitem__ is not called, and all
    the dimensional checking not done, when a slice of only the first
    dimension is taken, e.g. a[1:3]. From the Python docs:
       Deprecated since version 2.0: Support slice objects as parameters
       to the __getitem__() method. (However, built-in types in CPython
       currently still implement __getslice__(). Therefore, you have to
       override it in derived classes when implementing slicing.)
    """
    return self.__getitem__(slice(start, stop))
于 2013-01-28T03:49:59.967 に答える
0

メソッドを使用してisinstance、スライス タイプを確認します。

from __future__ import print_function

class SliceExample(object):
    def __getitem__(self, key):
        if isinstance(key, slice):
            return key.start, key.stop
        return key

sl = SliceExample()

print(repr(sl[1]))
print(repr(sl[1:2]))
于 2013-01-28T01:12:17.830 に答える