简体   繁体   English

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

[英]Let a class behave like it's a list in Python

I have a class which is essentially a collection/list of things. 我有一个类,它本质上是一个集合/事物列表。 But I want to add some extra functions to this list. 但是我想在这个列表中添加一些额外的功能。 What I would like, is the following: 我想要的是以下内容:

  • I have an instance li = MyFancyList() . 我有一个实例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应该表现为列表: [e for e in li]li.expand(...)for e in li
  • Plus it should have some special functions like li.fancyPrint() , li.getAMetric() , li.getName() . 此外,它应该有一些特殊功能,如li.fancyPrint()li.getAMetric()li.getName()

I currently use the following approach: 我目前使用以下方法:

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

This is ok for usage as iterator like [e for e in li] , but I do not have the full list behavior like li.expand(...) . 这对于像[e for e in li]这样的迭代器使用是可以的,但是我没有像li.expand(...)这样的完整列表行为。

A first guess is to inherit list into MyFancyList . 第一个猜测是将list继承到MyFancyList But is that the recommended pythonic way to do? 但这是推荐的pythonic方式吗? If yes, what is to consider? 如果是,那要考虑什么? If no, what would be a better approach? 如果不是,那会是更好的方法吗?

If you want only part of the list behavior, use composition (ie your instances hold a reference to an actual list) and implement only the methods necessary for the behavior you desire. 如果您只想要列表行为的一部分,请使用合成(即您的实例持有对实际列表的引用)并仅实现所需行为所需的方法。 These methods should delegate the work to the actual list any instance of your class holds a reference to, for example: 这些方法应该将工作委托给实际列表,任何类的实例都包含对它的引用,例如:

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

Implementing __getitem__ alone will give you a surprising amount of features, for example iteration and slicing. 单独实现__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]

If you want the full behavior of a list, inherit from collections.UserList . 如果您想要列表的完整行为,请从collections.UserList继承。 UserList is a full Python implementation of the list datatype. UserList是list数据类型的完整Python实现。

So why not inherit from list directly? 那么为什么不直接从list继承呢?

One major problem with inheriting directly from list (or any other builtin written in C) is that the code of the builtins may or may not call special methods overridden in classes defined by the user. 直接从list (或用C编写的任何其他内置函数)继承的一个主要问题是内置函数的代码可能会也可能不会调用在用户定义的类中重写的特殊方法。 Here's a relevant excerpt from the pypy docs : 以下是pypy文档的相关摘录:

Officially, CPython has no rule at all for when exactly overridden method of subclasses of built-in types get implicitly called or not. 正式地说,CPython根本没有规则,因为内置类型的子类的完全重写方法是否被隐式调用。 As an approximation, these methods are never called by other built-in methods of the same object. 作为近似,这些方法永远不会被同一对象的其他内置方法调用。 For example, an overridden __getitem__ in a subclass of dict will not be called by eg the built-in get method. 例如,在dict的子类中重写的__getitem__将不会被内置的get方法调用。

Another quote, from Luciano Ramalho's Fluent Python , page 351: 另一个引用来自Luciano Ramalho的Fluent Python ,第351页:

Subclassing built-in types like dict or list or str directly is error- prone because the built-in methods mostly ignore user-defined overrides. 直接对dict或list或str等内置类型进行子类化是容易出错的,因为内置方法通常会忽略用户定义的覆盖。 Instead of subclassing the built-ins, derive your classes from UserDict , UserList and UserString from the collections module, which are designed to be easily extended. 不是对内置函数进行子类化,而是从collections模块中的UserDict,UserList和UserString派生类,这些类旨在轻松扩展。

... and more, page 370+: ......以及更多,第370+页:

Misbehaving built-ins: bug or feature? 行为不端的内置插件:错误或功能? The built-in dict , list and str types are essential building blocks of Python itself, so they must be fast — any performance issues in them would severely impact pretty much everything else. 内置的dict,list和str类型是Python本身的基本构建块,因此它们必须很快 - 它们中的任何性能问题都会严重影响其他所有内容。 That's why CPython adopted the shortcuts that cause their built-in methods to misbehave by not cooperating with methods overridden by subclasses. 这就是为什么CPython采用了一些快捷方式,这些快捷方式会导致内置方法因为没有与子类重写的方法合作而行为不端。

After playing around a bit, the issues with the list builtin seem to be less critical (I tried to break it in Python 3.4 for a while but did not find a really obvious unexpected behavior), but I still wanted to post a demonstration of what can happen in principle, so here's one with a dict and a UserDict : 在玩了一下之后,内置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 overridden __setitem__ method, while the __init__ method from our UserDict did not. 如您所见,来自dict__init__方法忽略了重写的__setitem__方法,而来自UserDict__init__方法则没有。

The simplest solution here is to inherit from list class: 这里最简单的解决方案是从list类继承:

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

You can then use MyFancyList type as a list, and use its specific methods. 然后,您可以使用MyFancyList类型作为列表,并使用其特定方法。

Inheritance introduces a strong coupling between your object and list . 继承在您的对象和list之间引入了强大的耦合。 The approach you implement is basically a proxy object. 您实现的方法基本上是一个代理对象。 The way to use heavily depends of the way you will use the object. 大量使用的方式取决于您使用对象的方式。 If it have to be a list, then inheritance is probably a good choice. 如果它必须一个列表,那么继承可能是一个不错的选择。


EDIT: as pointed out by @acdr, some methods returning list copy should be overriden in order to return a MyFancyList instead a list . 编辑:正如@a​​cdr所指出的,应该覆盖一些返回列表副本的方法,以便返回MyFancyList而不是list

A simple way to implement that: 一种简单的实现方法:

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

If you don't want to redefine every method of list , I suggest you the following approach: 如果您不想重新定义list每个方法,我建议您采用以下方法:

class MyList:
  def __init__(self, list_):
    self.li = list_
  def __getattr__(self, method):
    return getattr(self.li, method)

This would make methods like append , extend and so on, work out of the box. 这会使像appendextend等方法开箱即用。 Beware, however, that magic methods (eg __len__ , __getitem__ etc.) are not going to work in this case, so you should at least redeclare them like this: 但要注意,魔术方法(例如__len____getitem__等)在这种情况下不起作用,所以你至少应该重新声明它们:

class MyList:
  def __init__(self, list_):
    self.li = list_
  def __getattr__(self, method):
    return getattr(self.li, method)
  def __len__(self):
    return len(self.li)
  def __getitem__(self, item):
    return self.li[item]
  def fancyPrint(self):
    # do whatever you want...

Please note, that in this case if you want to override a method of list ( extend , for instance), you can just declare your own so that the call won't pass through the __getattr__ method. 请注意,在这种情况下,如果要覆盖list的方法(例如, extend ),您可以声明自己的方法,以便调用不会通过__getattr__方法。 For instance: 例如:

class MyList:
  def __init__(self, list_):
    self.li = list_
  def __getattr__(self, method):
    return getattr(self.li, method)
  def __len__(self):
    return len(self.li)
  def __getitem__(self, item):
    return self.li[item]
  def fancyPrint(self):
    # do whatever you want...
  def extend(self, list_):
    # your own version of extend

Based on the two example methods you included in your post ( fancyPrint , findAMetric ), it doesn't seem that you need to store any extra state in your lists. 根据您在帖子中包含的两个示例方法( fancyPrintfindAMetric ),您似乎不需要在列表中存储任何额外的状态 If this is the case, you're best off simple declaring these as free functions and ignoring subtyping altogether; 如果是这种情况,你最好简单地将这些声明为自由函数并完全忽略子类型; this completely avoids problems like list vs UserList , fragile edge cases like return types for __add__ , unexpected Liskov issues, &c. 这完全避免了list vs UserList ,像__add__返回类型,意外的Liskov问题等脆弱的边缘情况这样的问题。 Instead, you can write your functions, write your unit tests for their output, and rest assured that everything will work exactly as intended. 相反,您可以编写函数,为输出编写单元测试,并确保一切都能按预期完成。

As an added benefit, this means your functions will work with any iterable types (such as generator expressions) without any extra effort. 作为额外的好处,这意味着您的函数将适用于任何可迭代类型(例如生成器表达式),而无需任何额外的工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM