让一个类表现得像 Python 中的列表

新手上路,请多包涵

我有一个类,它本质上是一个事物的集合/列表。但我想在此列表中添加一些额外的功能。我想要的是以下内容:

  • 我有一个实例 li = MyFancyList() 。 Variable li should behave as it was a list whenever I use it as a list: [e for e in li] , li.expand(...) , for e in li .
  • 另外它应该有一些特殊的功能,比如 li.fancyPrint()li.getAMetric()li.getName()

我目前使用以下方法:

 class MyFancyList:
  def __iter__(self):
    return self.li
  def fancyFunc(self):
    # do something fancy

这可以用作像 [e for e in li] 这样的迭代器,但我没有像 li.expand(...) 这样的完整列表行为。

第一个猜测是将 list 继承到 MyFancyList 。但这是推荐的 pythonic 方式吗?如果是,需要考虑什么?如果不是,什么是更好的方法?

原文由 Michael 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 550
2 个回答

如果您只想要列表行为的一部分,请使用组合(即您的实例持有对实际列表的引用)并仅实现您想要的行为所必需的方法。这些方法应该将工作委托给您的类的任何实例引用的实际列表,例如:

 def __getitem__(self, item):
    return self.li[item] # delegate to li.__getitem__

单独实施 __getitem__ 将为您提供数量惊人的功能,例如迭代和切片。

 >>> class WrappedList:
...     def __init__(self, lst):
...         self._lst = lst
...     def __getitem__(self, item):
...         return self._lst[item]
...
>>> w = WrappedList([1, 2, 3])
>>> for x in w:
...     x
...
1
2
3
>>> w[1:]
[2, 3]

如果您想要列表的 完整 行为,请继承自 collections.UserListUserList 是列表数据类型的完整 Python 实现。

那么为什么不直接继承 list 呢?

直接从 list (或任何其他用 C 编写的内置函数)继承的一个主要问题是,内置函数的代码可能调用也可能不调用用户定义的类中覆盖的特殊方法。这是 pypy 文档 的相关摘录:

正式地,CPython 对于何时隐式调用或不调用内置类型的子类的完全重写方法没有任何规则。作为近似,这些方法永远不会被同一对象的其他内置方法调用。例如,dict 子类中的重写 __getitem__ 不会被内置的 get 方法调用。

另一句话,来自 Luciano Ramalho 的 Fluent Python ,第 351 页:

直接对内置类型(如 dict 或 list 或 str)进行子类化很容易出错,因为内置方法大多忽略用户定义的覆盖。不要对内置函数进行子类化,而是从 collections 模块的 UserDict 、 UserList 和 UserString 派生类,这些类旨在轻松扩展。

…等等,第 370 页以上:

行为不端的内置插件:错误还是功能?内置的 dict 、 list 和 str 类型是 Python 本身的基本构建块,因此它们必须很快——它们中的任何性能问题都会严重影响几乎所有其他东西。这就是为什么 CPython 采用了一些快捷方式,这些快捷方式通过不与子类覆盖的方法合作来导致其内置方法行为不端。

玩了一会儿之后,内置的 list 的问题似乎不那么严重(我试图在 Python 3.4 中打破它一段时间但没有发现真正明显的意外行为),但我仍然想要发布原则上可能发生的事情的演示,所以这里有一个 dictUserDict

 >>> class MyDict(dict):
...     def __setitem__(self, key, value):
...         super().__setitem__(key, [value])
...
>>> d = MyDict(a=1)
>>> d
{'a': 1}

>>> class MyUserDict(UserDict):
...     def __setitem__(self, key, value):
...         super().__setitem__(key, [value])
...
>>> m = MyUserDict(a=1)
>>> m
{'a': [1]}

As you can see, the __init__ method from dict ignored the __setitem__ method, while the __init__ method from our UserDict 没有。

原文由 timgeb 发布,翻译遵循 CC BY-SA 3.0 许可协议

这里最简单的解决方案是继承自 list 类:

 class MyFancyList(list):
    def fancyFunc(self):
        # do something fancy

然后,您可以使用 MyFancyList 类型作为列表,并使用其特定方法。

继承在您的对象和 list 之间引入了强耦合。您实现的方法基本上是一个代理对象。大量使用的方式取决于您将使用该对象的方式。如果一定 要是 列表,那么继承大概是个不错的选择。


编辑:正如@acdr 所指出的,应该覆盖一些返回列表副本的方法,以便返回 MyFancyList 而不是 list

一个简单的实现方法:

 class MyFancyList(list):
    def fancyFunc(self):
        # do something fancy
    def __add__(self, *args, **kwargs):
        return MyFancyList(super().__add__(*args, **kwargs))

原文由 aluriak 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题