在Python中创建默认列表

18

我正在尝试创建一个等效于非常有用的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']

我应该添加什么到默认列表中才能支持以下行为?

>>> dl = defaultlist(dict)
>>> dl[2]['a'] = 1
>>> dl
[{}, {}, {'a':1}]

这个问题是对这个问题的跟进。 - Jonathan Livni
我猜你只是在寻找重载用于项访问的方法(而不是项赋值)? - user395760
3
dl[2] = {'a', 1} 更有意义,不是吗? - Brigand
4个回答

24

在你提供的例子中,你首先尝试检索列表中不存在的值,就像这样 dl[2]['a'],Python 首先获取列表中的第三个(索引为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)

我也希望像这样简单的东西就足够了,但请记住索引也可以是切片。例如 lst = defaultlist(lambda:None); lst[:] 将会抛出 TypeError: '<=' not supported between instances of 'int' and 'slice' - xaedes
为了支持切片,我在代码中使用了类似于以下的语句:idx = (idx if type(idx) is int else ((idx.stop if idx.stop is not None else len(lst))-(idx.step if idx.step is not None else 1)) if type(idx) is slice else len(lst)-1) - xaedes

15

有一个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]

查看文档以获取更多详细信息。


ModuleNotFoundError: 找不到名为'defaultlist'的模块这很奇怪,因为pip freeze显示已安装1.0版本。 - rjurney

1

可以实现一个defaultlist,它继承自MutableSequence抽象基类,并包装defaultdict。我在coinflip包中这样做,并在coinflip.collections子模块中公开它。

需要这样覆盖ABC:

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)方法并将default_factory传递给内部私有的defaultdict来初始化这个defaultlist
与c0fec0de的解决方案一样,我建议默认使用None填充索引(即传入一个“none factory”方法),否则将使用未访问的索引导致KeyError
访问器方法(获取、设置和删除项)将使索引成为私有defaultdict中的键,并在添加和/或删除项时相应地更新该字典。
如果想要模拟内置list的较少使用的方面,则需要考虑一些特殊的问题。我在这里更详细地写了关于这个主题的内容。

-3

创建一个默认列表的一种非常简单的方法是使用列表推导式和范围。

defaultvalue = "catfood"
x = [defaultvalue for i in range(0,4)]

这将导致

['catfood', 'catfood', 'catfood', 'catfood']

这可以是一个子类,但不一定需要。 它可以只是一个工厂函数,就像这样:
def defaultlist(defaultvalue, elementcount):
    return [defaultvalue for i in range(0, elementcount)]

并且会像这样工作

defaultlist(None, 7)

返回这个

[None, None, None, None, None, None, None]

我真的不认为这是原帖作者想要的。 - user9413641

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接