在Python中可以轻松创建一个类似列表的对象吗,它的项使用类似描述符的功能?
我正在尝试写一个接口,它可以在某种程度上简化另一个接口。
底层的接口在要求上有点不一致:有时候需要 ID,有时候需要名字。我想把这些细节隐藏起来。
我想创建一个类似列表的对象,允许你往里面添加名字,但内部实际上存储的是与这些名字相关的 ID。
最好是能用一些类似于类属性描述符的东西,只不过它们是作用于列表项的。也就是说,对于添加到列表中的每个项目,都会调用一个函数(像 __get__
),把它转换成我想要存储的 ID;而在从列表中取出项目时,会调用另一个函数(像 __set__
),返回一些提供便利方法的对象,而不是实际的 ID。
这样我就可以做类似这样的事情:
def get_thing_id_from_name(name):
# assume that this is more complicated
return other_api.get_id_from_name_or_whatever(name)
class Thing(object)
def __init__(self, thing_id):
self.id = thing_id
self.name = other_api.get_name_somehow(id)
def __eq__(self, other):
if isinstance(other, basestring):
return self.name == other
if isinstance(other, Thing):
return self.thing_id == other.thing_id
return NotImplemented
tl = ThingList()
tl.append('thing_one')
tl.append('thing_two')
tl[1] = 'thing_three'
print tl[0].id
print tl[0] == 'thing_one'
print tl[1] == Thing(3)
文档建议为一个像可变序列的对象定义 17 个方法(不包括构造函数)。我觉得继承 list
对我没有帮助。感觉我应该可以只定义一个获取器和一个设置器就能实现这个功能。
UserList
显然已经被弃用了(虽然在 Python 3 中还在?我现在用的是 2.7)。
有没有办法实现这个,或者类似的功能,而不需要重新定义这么多功能呢?
1 个回答
你不需要重写所有的列表方法,像__setitem__、__init__和append这些就够了,可能你还想要insert和其他一些方法。你可以写__setitem__和__getitem__,让它们调用一个特殊的“Thing”类里的__set__和__get__方法,就像描述符那样。
这里有一个简单的例子,可能和你想要的差不多:
class Thing(object):
def __init__(self, thing):
self.value = thing
self.name = str(thing)
id = property(lambda s: id(s))
#...
def __repr__(self):
return "I am a %s" %self.name
class ThingList(list):
def __init__(self, items):
for item in items:
self.append(item)
def append(self, value):
list.append(self, Thing(value))
def __setitem__(self, index, value):
list.__setitem__(self, index, Thing(value))
例子:
>>> a = ThingList(range(3))
>>> a.append("three")
>>> a
[I am a 0, I am a 1, I am a 2, I am a three]
>>> a[0].id
35242896
>>>
-- 编辑 --
原作者评论说:“我真的希望能有一种方法,能够拥有列表的所有功能——添加、扩展、切片等等,只需要重新定义获取和设置项的行为。”
那么就这样吧——确实需要以这种方式重写所有相关的方法。但是如果我们想避免的是一堆重复的代码,很多函数几乎做同样的事情,那么这些新的重写方法可以动态生成——我们只需要一个装饰器,把普通对象转换成Things
,用于所有设置值的操作:
class Thing(object):
# Prevents duplicating the wrapping of objects:
def __new__(cls, thing):
if isinstance(thing, cls):
return thing
return object.__new__(cls, thing)
def __init__(self, thing):
self.value = thing
self.name = str(thing)
id = property(lambda s: id(s))
#...
def __repr__(self):
return "I am a %s" %self.name
def converter(func, cardinality=1):
def new_func(*args):
# Pick the last item in the argument list, which
# for all item setter methods on a list is the one
# which actually contains the values
if cardinality == 1:
args = args[:-1] + (Thing(args[-1] ),)
else:
args = args[:-1] + ([Thing(item) for item in args[-1]],)
return func(*args)
new_func.func_name = func.__name__
return new_func
my_list_dict = {}
for single_setter in ("__setitem__", "append", "insert"):
my_list_dict[single_setter] = converter(getattr(list, single_setter), cardinality=1)
for many_setter in ("__setslice__", "__add__", "__iadd__", "__init__", "extend"):
my_list_dict[many_setter] = converter(getattr(list, many_setter), cardinality="many")
MyList = type("MyList", (list,), my_list_dict)
它的工作原理如下:
>>> a = MyList()
>>> a
[]
>>> a.append(5)
>>> a
[I am a 5]
>>> a + [2,3,4]
[I am a 5, I am a 2, I am a 3, I am a 4]
>>> a.extend(range(4))
>>> a
[I am a 5, I am a 0, I am a 1, I am a 2, I am a 3]
>>> a[1:2] = range(10,12)
>>> a
[I am a 5, I am a 10, I am a 11, I am a 1, I am a 2, I am a 3]
>>>