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

Mic*_*ael 44 python list python-3.x

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

  • 我有一个例子li = MyFancyList().变量li应该表现为它是每当我用它作为一个列表的列表:[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
Run Code Online (Sandbox Code Playgroud)

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

第一种猜测是继承listMyFancyList.但这是推荐的pythonic方式吗?如果是,那要考虑什么?如果不是,那会是更好的方法吗?

tim*_*geb 65

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

def __getitem__(self, item):
    return self.li[item] # delegate to li.__getitem__
Run Code Online (Sandbox Code Playgroud)

__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]
Run Code Online (Sandbox Code Playgroud)

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

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

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

正式地说,CPython根本没有规则,因为内置类型的子类的完全重写方法是否被隐式调用.作为近似,这些方法永远不会被同一对象的其他内置方法调用.例如,__getitem__在dict的子类中重写不会被例如内置get方法调用.

另一个引用来自Luciano Ramalho的Fluent Python,第351页:

直接对dict或list或str等内置类型进行子类化是容易出错的,因为内置方法通常会忽略用户定义的覆盖.不是对内置函数进行子类化,而是从集合模块中的UserDict,UserList和UserString派生类,这些类旨在轻松扩展.

......以及更多,第370+页:

行为不端的内置插件:错误或功能?内置的dict,list和str类型是Python本身的基本构建块,因此它们必须快速 - 其中的任何性能问题都会严重影响其他所有内容.这就是为什么CPython采用了一些快捷方式,这些快捷方式会导致内置方法因为没有与子类重写的方法合作而行为不端.

玩了一下后,list内置的问题似乎不那么重要了(我试图在Python 3.4中暂时破解它但没有找到一个非常明显的意外行为),但我还是想发布一个什么可以演示的演示原则上发生,所以这里有一个a dict和a UserDict:

>>> 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]}
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,该__init__方法dict忽略了重写__setitem__方法,而__init__我们的方法UserDict没有.


alu*_*iak 5

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

class MyFancyList(list):
    def fancyFunc(self):
        # do something fancy
Run Code Online (Sandbox Code Playgroud)

然后,您可以使用MyFancyListtype作为列表,并使用其特定方法.

继承引入了对象和对象之间的强耦合list.您实现的方法基本上是一个代理对象.大量使用的方式取决于您使用对象的方式.如果它必须一个列表,那么继承可能是一个不错的选择.


编辑:正如@a​​cdr所指出的,应该重写一些返回列表副本的方法,以便返回a MyFancyList而不是list.

一种简单的实现方法:

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

  • 请记住,您可能想要覆盖一些返回列表副本的方法.例如,在这种情况下,`lst = MyFancyList()`,`lst + [1,2,3]`将返回一个普通列表,而不是一个花哨的列表.你需要覆盖`__add__`方法才能生效. (6认同)
  • 正如另一个答案所指出的,直接继承自`list`有其缺陷.所以我想,毕竟,这不是一个很好的答案,为什么我发出了一个downvote. (3认同)