Ben*_*ale 7 python numpy magic-methods
所以这是关于我所假设的两个问题,就是我的基本混淆.我希望没关系.
这里有一些代码:
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)
Run Code Online (Sandbox Code Playgroud)
这段代码的输出是
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
Run Code Online (Sandbox Code Playgroud)
请注意两件事:
呼叫p[-1:]不会导致呼叫new_array.__getitem__.如果p[-1:]被诸如p[0:],p[0:-1]等之类的东西所取代,那就是这样......但是语句喜欢p[-1]并且p[slice(-1, None, None)]确实会导致调用new_array.__getitem__.这也适用于像陈述p[-1:] + p[-1:]或者s = p[-1]不过是不是真的像语句print s.您可以通过查看上面给出的"块"来看到这一点.
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
Run Code Online (Sandbox Code Playgroud)
现在包含过多的调用new_array.__array_finalize__,但变量的"问题"没有变化foo.
我的期望是p[-1:]对一个new_array对象的调用p.foo = 0会导致这个语句p.foo == 1返回True.显然情况并非如此,即使foo在调用期间正确更新__getitem__,因为类似的语句会p[-1:]导致大量调用__getitem__(一旦延迟评估被考虑在内).此外,该电话p[-1:]和p[slice(-1, None, None)]将导致不同的值foo(如果计数的事情是正常工作).在前一种情况下foo会5增加它,而在后一种情况下foo会1增加它.
虽然对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:括号内的步骤表示法构造),整数或切片对象和整数的元组时,会发生基本切片.
我不禁感到这同样的基本错误也是我认为该self.foo += 1行应该计算用户请求切片的次数,或实例的元素new_array(而不是元素的数量)的原因. " 一片).这两个问题实际上是否相关,如果是这样的话?
你确实有点讨厌一个臭虫.知道我不是唯一一个人,这是一种解脱!幸运的是,它很容易解决.只需在课堂上添加以下内容即可.这实际上是我几个月前写的一些代码的复制粘贴,文档字符串告诉了发生了什么,但你可能也想阅读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))
Run Code Online (Sandbox Code Playgroud)