一个小例子将有助于澄清我的问题:
我定义了两个类: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])
我希望我的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类型。
答案 0 :(得分:6)
您无需真正成为list
即可使用这些功能。这就是鸭子打字的重点。任何定义__getitem__(self, i)
的内容都会自动处理x[i]
,for i in x
,iter(x)
,enumerate(x)
以及其他各种内容。同样定义__len__(self)
和len(x)
,list(x)
等也可以。或者,您可以定义__iter__
而不是__getitem__
。或两者。这取决于你想要的list
- 你的确切方式。
Python special methods上的文档解释了每个文档的用途,并很好地组织它们。
例如:
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
这样的不可变类型更为重要。)
如果您的对象拥有列表而不是 ,那么它的设计是固有的,请使用委托。
最简单的委派方式是明确的。定义与假定为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
的许多方法都会返回新的list
。如果您希望它们返回新的Universe
,则需要包装这些方法。但请记住,其中一些方法是二元运算符 - 例如,a + b
只有在Universe
为1时才返回a
,或者只有两者都是,或者两者都是?
此外,__getitem__
有点棘手,因为它们可以返回list
或单个对象,而您只想将前者包装在Universe
中。您可以通过检查isinstance(ret, list)
的返回值或检查isinstance(index, slice)
的索引来执行此操作;哪一个适当取决于您是否可以将list
s作为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)
明确授权:
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__
处理其他所有方法。
如果你发现自己做了很多这样的包装,你可以编写一个生成包装类的函数,或者一个类装饰器,它可以让你编写骨架包装器和填充细节等等。因为细节取决于你的使用case(我上面提到过的所有问题都可以采用这种方式),没有一个适合所有人的库,只是神奇地做你想要的,但是ActiveState上的一些配方显示了更完整的细节 - 标准库源中甚至还有一些包装器。
答案 1 :(得分:4)
这是一种合理的方法,尽管您不需要继承list
和object
。仅list
就足够了。此外,如果您的课程是一个列表,则您不需要存储self.securities
;它将被存储为列表的内容。
但是,根据您要使用的类,您可能会发现更容易定义一个在内部存储列表的类(当您存储self.securities
时),然后在类上定义方法(有时)传递给此存储列表的方法,而不是继承自list
。 Python内置类型没有定义严格的接口,哪些方法依赖于哪些方法(例如,append
是否依赖于insert
),所以如果你有可能遇到混乱的行为尝试对列表类的内容进行任何重要的操作。
编辑:正如您所发现的,任何返回新列表的操作都属于此类别。如果您在不重写其方法的情况下继承list
,那么您可以调用对象上的方法(显式或隐式),将调用基础list
方法。这些方法被硬编码以返回普通的Python列表,并且不检查对象的实际类是什么,因此它们将返回纯Python列表。