一个小例子将有助于澄清我的问题:
我定义了两个类:Security和Universe,我想表现为Secutity对象列表.
这是我的示例代码:
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])
Run Code Online (Sandbox Code Playgroud)
我希望我的Universe类能够使用通常的列表功能,例如enumerate(),len(),__ getitem __()......:
enumerate(u)
len(u)
u[0]
Run Code Online (Sandbox Code Playgroud)
所以我把我的班级定义为:
class Universe(list, object):
def __init__(self, securities):
super(Universe, self).__init__(iter(securities))
self.securities = securities
Run Code Online (Sandbox Code Playgroud)
它似乎工作,但它是适当的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'>
Run Code Online (Sandbox Code Playgroud)
我希望我的变量sub_u保持为Universe类型.
您不必实际list使用这些功能.这就是鸭子打字的重点.凡是定义__getitem__(self, i)自动处理x[i],for i in x,iter(x),enumerate(x),和其他各种东西.也定义__len__(self)和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
Run Code Online (Sandbox Code Playgroud)
没有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`!
Run Code Online (Sandbox Code Playgroud)
您可能还想覆盖__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.
Run Code Online (Sandbox Code Playgroud)
您也可以通过__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)
Run Code Online (Sandbox Code Playgroud)
请注意,许多list方法将返回一个新方法list.如果您希望它们返回一个新的Universe,则需要包装这些方法.但请记住,其中一些方法是二元运算符 - 例如,应该只a + b返回一个Universeif a,或者只有两者都是,或者两者都是?
此外,__getitem__有点棘手,因为他们可以返回一个list或一个对象,你只想将前者包装成一个Universe.你可以通过检查返回值isinstance(ret, list),或通过检查索引来做到这一点isinstance(index, slice); 哪一个是合适的取决于你是否可以将lists作为a的元素Universe,以及它们是否应该被视为一个list或一个Universe被提取时.另外,如果您正在使用继承,在Python 2中,您还需要包装已弃用的__getslice__朋友,因为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)
Run Code Online (Sandbox Code Playgroud)
明确授权:
class Universe(object):
# same _wrap_if_needed
def __getitem__(self, index):
ret = self.securities.__getitem__(index)
return self._wrap_if_needed(ret)
Run Code Online (Sandbox Code Playgroud)
动态授权:
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)
Run Code Online (Sandbox Code Playgroud)
正如我所说,在这种情况下,这可能是矫枉过正,特别是对于__getattr__版本.如果你只想覆盖一个方法,比如__getitem__,并委托其他所有方法,你总是可以__getitem__明确定义,并让__getattr__其他一切处理.
如果你发现自己做了很多这样的包装,你可以编写一个生成包装类的函数,或者一个类装饰器,它可以让你编写骨架包装器和填充细节等等.因为细节取决于你的用例(所有我上面提到过的那些问题可以是这样或那样的),没有一个适合所有人的库,只是神奇地做你想要的,但是ActiveState上的一些配方显示了更完整的细节 - 那里甚至是标准库源中的几个包装器.
这是一种合理的方法,尽管您不需要同时继承list和object。 list一个人就够了。另外,如果你的类是一个列表,你不需要存储self.securities; 它将被存储为列表的内容。
但是,根据您想将类用于什么目的,您可能会发现定义一个在内部存储列表的类(就像您存储的那样self.securities)更容易,然后在您的类上定义(有时)传递给方法的方法此存储列表的,而不是从list. Python的内建类型不来定义一个严格的接口,它的方法取决于哪个其他的(例如,其是否append依赖于insert),所以你可以,如果你尝试做你的列表中的内容任何平凡的操作碰上混淆行为-班级。
编辑:正如您所发现的,任何返回新列表的操作都属于这一类。如果你子类化list而不覆盖它的方法,那么你在你的对象上调用方法(显式或隐式),底层list方法将被调用。这些方法被硬编码为返回一个普通的 Python 列表,并且不检查对象的实际类是什么,因此它们将返回一个普通的 Python 列表。
| 归档时间: |
|
| 查看次数: |
2217 次 |
| 最近记录: |