您不必真正成为list
使用这些功能的人。这就是鸭子打字的重点。任何定义__getitem__(self, i)
自动处理x[i]
、for i in x
、iter(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 ,或者只有当两者都是,或者如果两者都是?Universe
a
另外,__getitem__
这有点棘手,因为它们可以返回 alist
或单个对象,而您只想将前者包装在Universe
. 您可以通过检查 ; 的返回值isinstance(ret, list)
或检查索引来做到这一点isinstance(index, slice)
。哪个合适取决于您是否可以将list
s 作为 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 上有许多食谱可以显示更完整的细节——而且甚至是标准库源代码中的一些包装器。