在Python中创建默认列表
我正在尝试创建一个类似于非常有用的 collections.defaultdict
的列表。下面的设计效果很好:
class defaultlist(list):
def __init__(self, fx):
self._fx = fx
def __setitem__(self, index, value):
while len(self) <= index:
self.append(self._fx())
list.__setitem__(self, index, value)
这是使用它的方法:
>>> dl = defaultlist(lambda:'A')
>>> dl[2]='B'
>>> dl[4]='C'
>>> dl
['A', 'A', 'B', 'A', 'C']
我应该在 defaultlist 中添加什么,以支持以下行为呢?
>>> dl = defaultlist(dict)
>>> dl[2]['a'] = 1
>>> dl
[{}, {}, {'a':1}]
4 个回答
可以创建一个叫做 defaultlist
的东西,它是从 MutableSequence
这个抽象基类继承而来的,并且是围绕 defaultdict
进行封装的。我在我的 coinflip 包中实现了这个功能,并在 coinflip.collections
子模块中提供了它。
要实现这个功能,需要像下面这样重写抽象基类:
class defaultlist(MutableSequence):
def __getitem__(self, i):
...
def __setitem__(self, i, value):
...
def __delitem__(self, i):
...
def __len__(self):
...
def insert(self):
...
我会通过模仿 defaultdict(default_factory=None)
方法来初始化这个 defaultlist
,并把 default_factory
传递给一个内部的私有 defaultdict
。
就像 c0fec0de 的解决方案一样,我建议默认情况下用 None
填充索引(也就是说,传入一个“none factory”方法),否则在访问未使用的索引时会出现 KeyError
错误。
访问方法(获取、设置和删除项目)会把索引当作私有 defaultdict
中的键,并在添加或删除项目时相应地更新这个字典。
内置的 list
还有一些不常用的功能,如果想要模仿这些功能,需要特别注意。我在这里 写了更详细的内容。
有一个可以使用的Python包:
$ pip install defaultlist
新增的索引默认会填充为None。
>>> from defaultlist import defaultlist
>>> l = defaultlist()
>>> l
[]
>>> l[2] = "C"
>>> l
[None, None, 'C']
>>> l[4]
>>> l
[None, None, 'C', None, None]
切片和负索引也可以使用。
>>> l[1:4]
[None, 'C', None]
>>> l[-3]
'C'
可以通过lambda创建简单的工厂函数。
>>> l = defaultlist(lambda: 'empty')
>>> l[2] = "C"
>>> l[4]
'empty'
>>> l
['empty', 'empty', 'C', 'empty', 'empty']
也可以实现更复杂的工厂函数:
>>> def inc():
... inc.counter += 1
... return inc.counter
>>> inc.counter = -1
>>> l = defaultlist(inc)
>>> l[2] = "C"
>>> l
[0, 1, 'C']
>>> l[4]
4
>>> l
[0, 1, 'C', 3, 4]
想了解更多细节,请查看文档。
在你给的例子中,你首先尝试从列表中获取一个不存在的值,比如你写的 dl[2]['a']
。在这里,Python 首先会去获取列表中的第三个元素(因为索引是从0开始的,所以索引2代表第三个元素),然后再去这个对象中找名为 'a' 的元素。因此,你需要在 __getitem__
方法中实现自动扩展的功能,像这样:
class defaultlist(list):
def __init__(self, fx):
self._fx = fx
def _fill(self, index):
while len(self) <= index:
self.append(self._fx())
def __setitem__(self, index, value):
self._fill(index)
list.__setitem__(self, index, value)
def __getitem__(self, index):
self._fill(index)
return list.__getitem__(self, index)