让类的行为类似于Python中的列表

2024-04-18 18:47:34 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个类,本质上是一个事物的集合/列表。但我想在这个列表中添加一些额外的函数。我想要的是:

  • 我有一个实例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

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

第一种猜测是将list继承到MyFancyList。但这是推荐的Python疗法吗?如果是,应该考虑什么?如果没有,还有什么更好的方法?


Tags: 实例方法函数inself列表fordef
3条回答

如果您不想重新定义list的每个方法,我建议您使用以下方法:

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

这将使诸如appendextend等方法可以开箱即用。但是,请注意,在这种情况下,神奇的方法(例如__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...

请注意,在这种情况下,如果要重写listextend,例如)的方法,可以声明自己的方法,这样调用就不会通过__getattr__方法。例如:

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

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

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

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

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


EDIT:正如@acdr所指出的,为了返回MyFancyList而不是list,一些返回列表副本的方法应该被重写。

实现这一点的简单方法是:

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

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

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]

如果希望列表的完整行为,请从^{}继承。UserList是列表数据类型的完整Python实现。

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

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

Officially, CPython has no rule at all for when exactly overridden method of subclasses of built-in types get implicitly called or not. 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 e.g. the built-in get method.

另一段引自卢西亚诺·拉马略的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. 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.

。。。更多内容,第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. That’s why CPython adopted the shortcuts that cause their built-in methods to misbehave by not cooperating with methods overridden by subclasses.

在玩了一会儿之后,list内置的问题似乎就不那么重要了(我试图在Python 3.4中打破它一段时间,但是没有发现一个非常明显的意外行为),但是我仍然想发布一个原则上可以发生什么的演示,下面是一个带有adict和aUserDict的:

>>> 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]}

如您所见,来自dict__init__方法忽略了重写的__setitem__方法,而来自UserDict__init__方法则没有。

相关问题 更多 >