今天在学习《数据结构(Python语言描述)》第四章 数组和链接结构 中的练习“提供一个把Grid类用作数据结构来实现三维array类的策略”。其中在实现这个三维array时,实现__getitem__以及__setitem__命令遇到了一些困惑的东西,其实问题的所在就是这个*args以及**kwargs两个参数,所以在此进行一个记录。
首先这两个命令的作用如下:
数组操作 | 方法名称 |
---|---|
a[index] = newItem | _setitem_(index, newItem) |
a[index] | _getitem_(index) |
而阅读这两个方法的源码:
def __getitem__(self, *args)........
def __setitem__(self, *args, **kwargs).......
错误复现(选看)
刚开始自己并没有看这个文档,也没关心,在实现Array的命令如下,就很简单:
class Array(object):
...
def __getitem__(self, index):
return self.items[index]
def __setitem__(self, key, value):
"""因为这个数组是基于list而创建的,故直接定义即可"""
"""Subscript operator for replacement at index"""
self.items[key] = value
于是在实现Grid命令时,就遵从了如上思路:
# 错误的代码
class Grid(object):
def __init__(self, rows, columns, fillvalue=None):
self.data = Array(rows)
for i in range(rows):
self.data[i] = Array(columns, fillvalue=fillvalue)
def __setitem__(self, row, col, value):
"""-> Supports two-dimensions index,
like[row, column]"""
self.data[row][col] = value
def __getitem__(self, row, col):
"""-> Supports two-dimensions index,
like[row, column]"""
return self.data[row][col]
if __name__ == "__main__":
grid = Grid(5, 5)
print(grid[3, 2])
grid[3, 2] = 10
print(grid)
于是就开始报错
TypeError: getitem() missing 1 required positional argument: ‘col’
以及
TypeError: setitem() missing 1 required positional argument: ‘value’
同时自己书写的三维array也会报错,具体代码忘了,但是写的很乱,依稀记着其提示为,需要一个整数而不是list这样子
正确理解
首先必须理解,这两个命令定义时只能写:
def __getitem__(self, index): ...
def __setitem__(self, index, value): ....
并且从上面可以知道,以setitem为例,其中index代表的是*args
,而value代表的是**kwargs
,在这个c#中*
代表指针的意思,但是python中并没有指针,那么我现在理解例如from xxx import *
其中的代表的就是所有的意思,那么在这里就可以理解为,index
代表你输入的a[a,b,c,d....]
中输入的所有参数,他把他们一起打包成一个元组,这样进行传递。
所以我上面的代码不对,并且报错的原因是:grid[3, 2]
他就把3和2打包成一个元素,传递给命令中的row,而col没有值,所以就报错缺少col,第二个也同理。
这个练习给我最大的思考就是,Array是基础的数据结构,Grid就是建立在Array基础上的数据结构,3D就是建立在Grid基础上的数据结构,也可以说二维网格就是数组的数组,三维数组就是数组的数组的数组,以此套娃。
所以在建立三维数组的__getitem__以及__setitem__
命令的时候,就需要按照上面的思路进行了。我写的正确代码俯下,截取部分:
def __setitem__(self, index, value):
"""-> Supports two-dimensions index,
like[row, column, channel]
"""
self.data[index[2]][index[0], index[1]] = value
其中第一个index[2]
就是先去索引channel,其中每一个channel记录的就是一个Grid,然后后面是一个数组,其实就是访问一个Grid中的元素,即调用他的__setitem__
命令。其实如果继续思考,Grid中__setitem__
其实最终还是调用了Array中的__setitem__
,也就是基础了。
其中在重写__setitem__
命令时,我只写了一个index
,但是在实例化使用的时候,我可以任意往里面写参数,最终这个index都会把这些参数吃进来,我在使用即可,就不会乱掉了。所以以后自己再重写*args以及**kwargs
命令的时候,就不需要写很多标识符,而只需要去关注他的数据结构即可。
附录:
"""
File:grid.py
A grid with size [rows, columns]
use getitem, getHeight, getWidth and str.
To instantiate, use
<variable> = Grid(<rows>, <columns>, <option for value>)
The fill value is None by default.
"""
from arrays import Array
class Grid(object):
def __init__(self, rows, columns, fillvalue=None):
self.data = Array(rows)
for i in range(rows):
self.data[i] = Array(columns, fillvalue=fillvalue)
def getHeight(self):
return len(self.data)
def getWidth(self):
return len(self.data[0])
def __setitem__(self, index, value):
"""-> Supports two-dimensions index,
like[row, column]"""
self.data[index[0]][index[1]] = value
def __getitem__(self, index):
"""-> Supports two-dimensions index,
like[row, column]"""
return self.data[index[0]][index[1]]
def __str__(self):
"""return the string format of the grid"""
result = ""
for row in range(self.getHeight()):
for col in range(self.getWidth()):
result += str(self.data[row][col]) + " "
result += '\n'
return result
def searchNegativeNum(self):
"""Search the first negative number in the grid,
if not, return the row and column number of the grid
"""
for row in range(self.getHeight()):
for col in range(self.getWidth()):
if self.data[row][col] is not None:
if self.data[row][col] < 0:
return [row, col]
return [self.getHeight(), self.getWidth()]
if __name__ == "__main__":
grid = Grid(5, 5)
grid[3, 2]
grid[3, 2] = 10
print(grid)
from arrays import Array
from grid import Grid
class Grid3D(object):
def __init__(self, rows, columns, channels, fillvalue=None):
self.data = Array(channels)
self.shape = [rows, columns, channels]
for channel in range(channels):
self.data[channel] = Grid(rows, columns, fillvalue=fillvalue)
def __getitem__(self, index):
"""-> Supports three-dimensions index,
like[row, column, channel]"""
return self.data[index[2]][index[0], index[1]]
def __setitem__(self, index, value):
"""-> Supports two-dimensions index,
like[row, column, channel]
"""
self.data[index[2]][index[0], index[1]] = value
def getChannel(self):
return self.shape[2]
def setPositionValue(self):
"""This will set the value with it position,
example:potion(2,3,3) will set value of '233'
"""
for channel in range(self.shape[2]):
for row in range(self.shape[0]):
for col in range(self.shape[1]):
self.data[channel][[row, col]] = str(row)+str(col)+str(channel)
if __name__ == '__main__':
a = Grid3D(5, 5, 3)
# a.setPositionValue()
print(a[3, 2, 2])
a[3, 2, 2] = 10
print(a[3, 2, 2])
print(a)