文章目录
前言
数组在Python编程中非常广泛,其中涉及到子维数据提取、重组、操作、广播、…,让初学者眼花缭乱,可以说是学习python的一个坎。本人想把数组涉及到的相关常用点,简单通俗地一 一展开,希望能对大家有所帮助。
相信大家刚开始学python数组时,对[]里可以放list()、tuple()、ndarray()、int会感觉有点眼花缭乱,一时搞不清它们在[]里有何不同,今天就用数组的__getitem__(self, key)函数简单清晰讲解。
一、任何类都可以通过__getitem__(self, key)实现类似列表、数组的[]功能
如果你的自定义类需要像string、list、tuple、string用[key]获取数据,可以在自定义类里添加 __getitem__(self, key)或__getitem__(self, *key) 函数即可,如:
class MyClass:
'''下面item为1个位置参数,也就是只能接收1个参数'''
def __getitem__(self, key):
print('param is ', key)
return item
obj = MyClass()
'''因为直接调用__getitem__(self, item),而且item是1个位置参数,所以item直接与(1, 2)对应'''
obj.__getitem__((1, 2))
param is (1, 2)
'''因为下面用了[],又因为[]里的参数只有1个,所以python解释器不会对(1,2)再进行打包,而是把(1,2)作为
__getitem__(self, item)的item参数并调用__getitem__'''
obj[(1, 2)]
param is (1, 2)
'''因为下面用了[],而且[]的参数大于1个,所以python解释器会先对1,2用tuple()进行打包,然后把打包后
的这1个参数(1,2)作为__getitem__(self, item)的item参数并调用__getitem__'''
obj[1, 2]
param is (1, 2)
'''因为直接调用__getitem__(self, item),而且item是1个位置参数,但下面1, 2是2个参数,所以报参数给多了'''
obj.__getitem__(1, 2)
obj.__getitem__(1, 2)
^^^^^^^^^^^^^^^^^^^^^
TypeError: MyClass.__getitem__() takes 2 positional arguments but 3 were given
从上面可以看到,当类实例后面跟[]时,最终会调用是类里的__getitem__()函数。这个流程是:在执行时,当python解释器发现类的实例后跟[]并且[]后没=时,python解释器就知道是要从类实例获取数据,这时python解释器会看[]里的参数有几个,当为1个时参数不变,为多个时,python解释器会先把[]里的参数用tupple()打包成1个参数。最后用这1个参数作为key,来调用类的__getitem__(self, key)。
因为上面的__getitem__(self, item)的item是1个位置参数,所以当直接调用__getitem__()函数时,如果参数除了self外,大于1个时,就会报错。如果想让__getitem__()能接收不定长度参数,只需要把item变成*item,如:
class MyClass:
'''将item变成*item,即从位置参数变成不定长度参数,这时下面的__getitem__能接收多个参数'''
def __getitem__(self, *item):
print('param is ', item)
return item
obj = MyClass()
'''因为*item为不定长度参数,所以(1, 2)被打包成((1, 2),)'''
obj.__getitem__((1, 2))
param is ((1, 2),)
'''因为下面用了[],而且[]的参数只有1个,所以python解释器不会对(1,2)再进行打包,而是把(1,2)作为
*item参数调用__getitem__(self, *item)。但由于*item是不定长度参数,所以(1,2)会被打包成((1, 2),)'''
obj[(1, 2)]
param is ((1, 2),)
'''因为下面用了[],而且[]的参数大于1个,所以python解释器会先把[]里1,2打包成(1,2),再以(1,2)作为
*item参数调用__getitem__(self, *item)。但由于*item是不定长度参数,所以(1,2)会再次被打包成((1, 2),)'''
obj[1, 2]
param is ((1, 2),)
'''因为直接调用__getitem__(self, item),而且*item是不定长度参数,所以1, 2会被打包成(1,2)'''
obj.__getitem__(1, 2)
param is (1, 2)
二、python数组[]也是通过np.ndarray.getitem__(self, key)__函数获取数组元素
python数组和其它任何python类的[]都是通过类自定义的.getitem()函数来获取[]里的参数的。下面进行验证:
a = np.arange(2*3*4).reshape(2,3,4)
print(a)
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]
'''python解释器会把[]里的大于1个参数的'0,1,2'打包成元组(0,1,2),然后__getitem__(key)会把传进来的元组(0,1,2)解包成0,1,2'''
print(a[0,1,2])
6
'''python解释器会把[]里的1个参数的(0,1,2)直接传给__getitem__(key),然后__getitem__(key)会把传进来的元组(0,1,2)解包成0,1,2'''
print(a[(0,1,2)])
6
'''indexes为元组(0,1,2),所以a[indexes]和a[(0,1,2)]一样'''
indexes = 0, 1, 2
print(a[indexes])
6
'''a.__getitem__((0,1,2)与a[0,1,2]结果一致'''
print( a.__getitem__((0,1,2))
6
'''下面__getitem__()直接被作为普通函数调用,所以python解释器没机会再对__getitem__((0,1,2))里的参数打包'''
a.__getitem__(0,1,2)
a.__getitem__(0,1,2)
'''下面错误提示expected 1 argument, got 3,说明数组的__getitem__()函数的参数只能有1个,
即为__getitem__(self, key),而不是__getitem__(self, *key)'''
TypeError: expected 1 argument, got 3
上面a[0,1,2]与a.__getitem__((0,1,2))打印的元素值一样,说明python数组的[]的确也是通过__getitem__(self, key)来获取数组元素。
再从a.__getitem__((0,1,2))报错,可知数组的__getitem__(self, key)函数的key参数为1个位置参数,而不是不定长度参数,即为__getitem__(self, key),而不是__getitem__(self, *key)。
三、从数组__getitem__(self, key)__函数的key为位置参数来解释a[[0,1]]、a[(0,1)]、a[0,1]的异同
我们先用实例,看a[[1,0]]、a[(1,0)]、a[1,0]的输出有什么异同,如下:
a = np.arange(2*3).reshape(2,3)
print(a)
[[0 1 2]
[3 4 5]]
'''从print(a[[1,0]])的打印值明显能看出a[[1,0]]中的1和0都是第0索引位置'''
print(a[[1,0]])
[[3 4 5]
[0 1 2]]
'''从print(a[(1,0)])的打印值明显能看出a[(1,0)]中的1是第0索引位置,0是第1索引位置'''
print(a[(1,0)])
3
'''从print(a[1,0])直接能知道1是第0索引位置,0是第1索引位置'''
print(a[1,0])
3
会发现上面的print(a[(1,0)])和print(a[1,0])打印的值一样,我们可以从[]调用__getitem__(self, key)进行分析:
'''下面[]里有2个参数,所以python解释器会把1,0打包成(1,0),再传给__getitem__(self, key)的位置参数key,所以key为(1,0)'''
print(a[1,0])
'''下面[]里只有1个参数,所以python解释器会直接把(1,0)传给__getitem__(self, key)的位置参数key,所以key为(1,0)'''
print(a[(1,0)])
有了上面分析,可知a[1,0]中的1,0与a[(1,0)]中的(1,0)完全等价,再从a[1,0]中的1,0为2个参数,占了2个子索引位置,所以(1,0)要与1,0等价,那肯定是在数组的__getitem__(self, key)里对元组(1,0)进行了解包,得到1和0,其中1在索引0位置上,0在索引位置上。
看了print(a[(1,0)])和print(a[1,0])的异同后,我们再看print(a[[1,0]]),从打印值可看出a[[1,0]]中的1和0都是第0索引位置的索引。说明当列表list作为参数key,传进__getitem__(self, key)的key时,在__getitem__()函数里并未对列表list进行解包,所以列表list只占据了第0索引位置,从而让list里的所有值都成了第0索引位置上的索引。
如果对上面描述还不清楚,可以用下面的代码理解:
class MydemoClass:
def __init__(self, arr : np.ndarray):
self.arr = arr
'''为了print(MydemoClass的实力)打印出数组self.arr的值'''
def __str__(self):
st = self.arr.__str__()
return st
'''[]会调用__getitem__函数,下面key为1个位置参数,也就是只能接收1个参数'''
def __getitem__(self, key):
if isinstance(key, tuple):
'''key参数类型为tuple时,用*key对key进行解包,这时解包出来的多个参数,会按顺序依次占据第0、第1索引位置'''
v = self.arr[*key]
return v
elif isinstance(key, list):
'''key参数类型为list时,key只能占据第0索引位置,剩下的其它所有索引位置用...代替,实际上就是都用:代替'''
v = self.arr[key, ...]
return v
return self.arr[key]
a = np.arange(2*3).reshape(2,3)
print(a)
[[0 1 2]
[3 4 5]]
mydemo = MydemoClass(a)
'''和print(a)打印值相同'''
print(mydemo)
[[0 1 2]
[3 4 5]]
'''和print(a[[1,0]])打印值相同'''
print(mydemo[[1,0]])
[[3 4 5]
[0 1 2]]
'''和print(a[(1,0)])打印值相同'''
print(mydemo[(1,0)])
3
'''和print(a[1,0])打印值相同'''
print(mydemo[1,0])
3
到这可总结:
- []会调用类的__getitem__(self,key)函数。
- []里的参数多于1个时,python解释器会将多个参数打包成1个参数,总之传进数组的__getitem__(sefl,key)的key参数只能有1个。
- 当__getitem__(self,key)的key参数为元组tupple时,在__getitem__()里就会对tuple进行解包,解包出来的各元素按顺序对应各子索引位置。
- 当__getitem__(self,key)的key参数为列表list时,在__getitem__()里不会对list进行解包,直接把list作为数组第0索引位置上的索引,因此list里的所有索引值都为第0索引位置上的索引。
后续
在前一篇 从python数组下标由索引广播实现去理解数组中我们已知道数组所有功能都可以通过数组下标完成,今天看了数组下标[]放list()、tuple()、ndarray()、int各种不同类型参数时的功能。
339

被折叠的 条评论
为什么被折叠?



