3

一个小例子将有助于澄清我的问题:

我定义了两个类:Security 和 Universe,我希望将它们作为一个安全对象列表。

这是我的示例代码:

class Security(object):
    def __init__(self, name):
        self.name = name

class Universe(object):
    def __init__(self, securities):
        self.securities = securities

s1 = Security('name1')
s2 = Security('name2')
u = Universe([s1, s2])

我希望我的 Universe 类能够使用常用的列表功能,例如 enumerate()、len()、__getitem__()...:

enumerate(u)
len(u)
u[0]

所以我将我的班级定义为:

class Universe(list, object):
    def __init__(self, securities):
        super(Universe, self).__init__(iter(securities))
        self.securities = securities

它似乎有效,但它是合适的pythonic方式吗?

[编辑]

当我对列表进行子集化时,上述解决方案无法正常工作:

>>> s1 = Security('name1')
>>> s2 = Security('name2')
>>> s3 = Security('name3')
>>> u = Universe([s1, s2, s3])
>>> sub_u = u[0:2]
>>> type(u)
<class '__main__.Universe'>
>>> type(sub_u)
<type 'list'>

我希望我的变量 sub_u 保持为 Universe 类型。

4

2 回答 2

6

您不必真正成为list使用这些功能的人。这就是鸭子打字的重点。任何定义__getitem__(self, i)自动处理x[i]for i in xiter(x)enumerate(x)和各种其他事物的东西。也定义__len__(self)and len(x),list(x)等也有效。或者您可以定义__iter__而不是__getitem__. 或两者。这取决于list你想成为什么样的人。

Python特殊方法的文档解释了每个方法的用途,并且很好地组织了它们。

例如:

class FakeList(object):
    def __getitem__(self, i):
        return -i
fl = FakeList()
print(fl[20])
for i, e in enumerate(fl):
    print(i)
    if e < -2: break

看不到list

如果您确实有一个real列表并希望将其数据表示为您自己的数据,则有两种方法可以做到这一点:委托和继承。两者都有效,并且都适用于不同的情况。

如果你的对象真的一个list额外的东西,使用继承。如果您发现自己踩到了基类的行为,您可能无论如何都想切换到委托,但至少从继承开始。这很简单:

class Universe(list): # don't add object also, just list
    def __init__(self, securities):
        super(Universe, self).__init__(iter(securities))
        # don't also store `securities`--you already have `self`!

您可能还想覆盖__new__,它允许您iter(securities)进入list创建时间而不是初始化时间,但这通常对list. (对于像 . 这样的不可变类型更重要str。)

如果您的对象拥有一个列表而不是一个列表这一事实在其设计中是固有的,请使用委托。

最简单的委托方式是显式的。定义与您定义的完全相同的方法来伪造 a list,并将它们全部转发给list您拥有的:

class Universe(object):
    def __init__(self, securities):
        self.securities = list(securities)
    def __getitem__(self, index):
        return self.securities[index] # or .__getitem__[index] if you prefer
    # ... etc.

您还可以通过以下方式进行委托__getattr__

class Universe(object):
    def __init__(self, securities):
        self.securities = list(securities)
    # no __getitem__, __len__, etc.
    def __getattr__(self, name):
        if name in ('__getitem__', '__len__',
                    # and so on
                   ):
            return getattr(self.securities, name)
        raise AttributeError("'{}' object has no attribute '{}'"
                             .format(self.__class__.__name__), name)

请注意,许多list's 方法将返回一个新的list. 如果您希望它们返回一个新的Universe,则需要包装这些方法。但请记住,其中一些方法是二元运算符——例如,只有当是一个时才应该a + b返回 a ,或者只有当两者都是,或者如果两者都是?Universea

另外,__getitem__这有点棘手,因为它们可以返回 alist或单个对象,而您只想将前者包装在Universe. 您可以通过检查 ; 的返回值isinstance(ret, list)或检查索引来做到这一点isinstance(index, slice)。哪个合适取决于您是否可以将lists 作为 a 的元素,以及提取时Universe是否应将它们视为 alist或 a 。Universe另外,如果您使用继承,在 Python 2 中,您还需要包装 deprecated__getslice__和 friends,因为list确实支持它们(尽管__getslice__总是返回一个子列表,而不是一个元素,所以这很容易)。

一旦你决定了这些事情,实现就很容易了,虽然有点乏味。以下是所有三个版本的示例,使用__getitem__因为它很棘手,以及您在评论中询问的那个。我将展示一种使用通用帮助器进行包装的方法,即使在这种情况下,您可能只需要一种方法,所以它可能有点矫枉过正。

遗产:

class Universe(list): # don't add object also, just list
    @classmethod
    def _wrap_if_needed(cls, value):
        if isinstance(value, list):
            return cls(value)
        else:
            return value
    def __getitem__(self, index):
        ret = super(Universe, self).__getitem__(index)
        return _wrap_if_needed(ret)

显式委托:

class Universe(object):
    # same _wrap_if_needed
    def __getitem__(self, index):
        ret = self.securities.__getitem__(index)
        return self._wrap_if_needed(ret)

动态委托:

class Universe(object):
    # same _wrap_if_needed
    @classmethod
    def _wrap_func(cls, func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            return cls._wrap_if_needed(func(*args, **kwargs))
    def __getattr__(self, name):
        if name in ('__getitem__'):
            return self._wrap_func(getattr(self.securities, name))
        elif name in ('__len__',
                      # and so on
                      ):
            return getattr(self.securities, name)
        raise AttributeError("'{}' object has no attribute '{}'"
                             .format(self.__class__.__name__), name)        

正如我所说,在这种情况下这可能有点矫枉过正,尤其是对于这个__getattr__版本。如果您只想覆盖一个方法,例如__getitem__,并委托其他所有内容,您始终可以__getitem__显式定义,并让__getattr__处理其他所有内容。

如果你发现自己经常做这种包装,你可以编写一个生成包装类的函数,或者一个让你编写骨架包装并填充细节的类装饰器,等等。因为细节取决于你的用例(所有我上面提到的那些问题可能会以某种方式或其他方式发生),没有一种万能的库可以神奇地做你想要的,但是 ActiveState 上有许多食谱可以显示更完整的细节——而且甚至是标准库源代码中的一些包装器。

于 2013-01-10T00:03:20.350 回答
5

这是一种合理的方法,尽管您不需要同时继承listand objectlist一个人就够了。另外,如果你的类是一个列表,你不需要存储self.securities; 它将被存储为列表的内容。

但是,根据您要使用类的目的,您可能会发现定义一个在内部存储列表的类(就像您正在存储的那样self.securities)更容易,然后在您的类上定义(有时)传递给方法的方法这个存储的列表,而不是继承自list. Python 内置类型没有根据哪些方法依赖于哪些其他方法(例如,是否append依赖于insert)定义严格的接口,因此如果您尝试对列表的内容进行任何非平凡的操作,您可能会遇到令人困惑的行为-班级。

编辑:正如您所发现的,任何返回新列表的操作都属于此类别。如果您子类list化而不覆盖其方法,那么您调用对象上的方法(显式或隐式),list将调用底层方法。这些方法被硬编码为返回一个纯 Python 列表,并且不检查对象的实际类是什么,因此它们将返回一个纯 Python 列表。

于 2013-01-09T23:09:41.383 回答