存值,两个列表通过索引的一一对应,从而达到模拟字典的目的。
首先,我们看看__len__方法,按照协议,这个方法应该返回容器的长度,因为这个类在设计的时候要求两个列表必须等长,所以理论上返回哪个列表的长度都是一样的,这里我选择返回 key 的长度。
然后是__getitem__方法。这个方法会在a['scolia']时,调用a.__getitem__('scolia')。也就是说这个方法定义了元素的获取,我这里的思路是先找到 key 列表中建的索引,然后用索引去 value 列表中找对应的元素,然后将其返回。然后为了进一步伪装成字典,我捕获了可能产生的 ValueError (这是 item 不在 key 列表中时触发的异常),并将其伪装成字典找不到键时的 KeyError。
理论上只要实现了上面两个方法,就可以得到一个不可变的容器了。但是我觉得并不满意所以继续拓展。
__setitem__(self, key, value)方法定义了 a['scolia'] = 'good' 这种操作时的行为,此时将会调用a.__setitem__('scolia', 'good') 因为是绑定方法,所以self是自动传递的,我们不用理。这里我也模拟了字典中对同一个键赋值时会造成覆盖的特性。这个方法不用返回任何值,所以return语句也省略了。
__delitem__(self, key)方法定义了del a['scolia'] 这类操作时候的行为,里面的‘scolia’就作为参数传进去。这里也进行了异常的转换。
只有实现里以上四个方法,就可以当做可变容器来使用了。
接下来的 __str__ 是对应于 str() 函数,在类的表示中会继续讨论,这里是为了 print 语句好看才加进去的,因为print语句默认就是调用str()函数。
__iter__和next方法在开头的时候讨论过了,这里是为了能让其进行迭代操作而加入的。
__reversed__(self)方法返回一个倒序后的副本,这里体现了有序性,当然是否需要还是要看个人。
__contains__实现了成员判断,这里我们更关心value列表中的数据,所以判断的是value列表。该方法要求返回布尔值。
下面是相应的测试:
a = Foo('scolia', 'good')
a[123] = 321
a[456] = 654
a[789] = 987
print a
del a[789]
print a
for x, y in a:
print x, y
print reversed(a)
print 123 in a
print 321 in a
class Boo(dict):
def __new__(cls, *args, **kwargs):
return super(Boo, cls).__new__(cls)
def __missing__(self, key):
return 'The key(%s) can not be find.'% key
测试:
b = Boo()
b['scolia'] = 'good'
print b['scolia']
print b['123']
当然你也可以在找不到 key 的时候触发异常,具体实现看个人需求。
只用__getitem__(self, item)实现支持for循环:
class Foo(object):
def __init__(self, x):
self.x = x
self.__index = -1
def __getitem__(self, item):
self.__index += 1
return self.x[self.__index]
测试:
a = Foo([1, 2, 3])
for x in a:
print x
工作良好。
切片操作的实现:
有好奇的同学可能还会发现上面并没有出现序列的典型操作:切片的实现。
其实切片也是使用__getitem__(self, item)魔法方法的,先让我们看看当我们使用切片的时候,item参数会获得什么:
class Foo(object):
def __init__(self, x):
self.x = x
def __getitem__(self, item):
return item
a = Foo(123)
print a[1:2]
获得了一个类似函数的对象,其类型为:
该类型由 slice 函数创建,感兴趣的同学可以使用 help 函数进行深入研究。
该函数的创建方法为: slice(stop)/slice(start, stop[, step]) 两种,一旦创建后,我们可以使用 start、stop、step属性来获取相应的值。
如果要让上面的例子支持切片,只需要修改__getitem__(self, item)处的代码:
def __getitem__(self, item):
if isinstance(item, slice):
return self.value[item.start:item.stop:item.step]
else:
try:
__index = self.key.index(item)
return self.value[__index]
except ValueError:
raise KeyError('can not find the key')
输出:
a = Foo('scolia', 'good')
a[123] = 321
a[456] = 654
a[789] = 987
print a[:]
print a[2:]
print a[:3]
print a[1:5]
print a[1:10:2]
print a[-4:-2]
运行良好,切片功能支持完毕。
欢迎大家交流。
参考资料:戳这里