如何根据属性或其他条件缩小元素列表范围?
我想根据元素的属性、类型或其他条件来缩小一个列表的范围。
像 elements.only_type(Flower).get_nearest_to(player)
这样的写法看起来比 min(filter(lambda i: isinstance(i, Flower), elements), lambda i: i.pos.distance_to(player.pos)
要简洁得多。
那么,下面这个做法在效率、代码整洁和简单性方面是否是个好主意呢?或者说,是否已经有好的方法、实现或设计模式可以做到这一点?
class Selector(object):
def __init__(self, selection):
self.s3l3ct1on = selection
def __getattr__(self, name):
return type(self)(getattr(el, name) for el in self.s3l3ct1on)
def __iter__(self):
return iter(self.s3l3ct1on)
def filter(self, function):
return type(self)(filter(function, self.s3l3ct1on))
这就是它的用法:(A
只是一个有两个属性:a
和 b
的类)
>>> sel = [A(3, 4), A(0, 9), A('test', 3), A(4,22), A(3, 9)]
>>> Selector(sel)
<__main__.Selector object at 0x13b0a90>
>>> list(Selector(sel))
[<__main__.A object at 0x13b0fd0>,
<__main__.A object at 0x13b0b50>,
<__main__.A object at 0x13b0150>,
<__main__.A object at 0x13b0710>,
<__main__.A object at 0x13b06d0>]
>>> set(Selector(sel).a)
{0, 'test', 3, 4}
>>> list(Selector(sel).b)
[4, 9, 3, 22, 9]
>>> s = Selector(sel).b.filter(lambda i: i%2 == 0)
>>> list(s)
[4, 22]
2 个回答
1
为了简单起见,你可以让这个类直接继承列表的功能。
class Elements(list):
def only_type(self, t):
return Elements(i for i in self if isinstance(i, t))
def get_nearest(self, who):
return min(self, key=lambda x: x.pos.distance_to(who.pos))
el = Elements([Flower(), Person(), Flower(), Something()])
el.only_type(Flower).get_nearest(player)
对Selector
也是一样的道理。
1
你提到的每一种选择过程都可以用一个叫做 itertools
的迭代器来定义。下面的内容就是对此的具体说明,而且可以很容易地扩展以支持更多类型的选择。使用它的语法看起来也很简单易懂。
虽然我没有进行性能测试,但我认为它的运行效率应该是不错的,因为大部分的额外开销都在构造方法里。唯一可能稍微快一点的做法,就是用相应的 itertools
迭代器函数来替代它的使用。所以,如果你打算经常进行这种处理,使用这个工具可能是个不错的选择。
import itertools
class Selector(object):
def __init__(self, iterable, **kwargs):
if not kwargs:
self.iterator = iterable
elif len(kwargs) > 1:
raise ValueError('only one selector type keyword allowed')
else:
selector, target = kwargs.items()[0]
if selector == 'by_attr':
self.iterator = itertools.imap(lambda obj: getattr(obj, target), iterable)
elif selector == 'by_type':
self.iterator = itertools.ifilter(lambda obj: isinstance(obj, target),
iterable)
elif selector == 'by_func':
self.iterator = itertools.ifilter(target, iterable)
else:
raise ValueError('unknown selector type keyword')
def __iter__(self):
return self.iterator
if __name__ == '__main__':
from selector import Selector
class A(object):
def __init__(self, a, b):
self.a, self.b = a, b
class Flower(object):
def __init__(self, name):
self.name = name
sel = [A(3, 4), A(0, 9), A('test', 3), A(4,22), A(3, 9)]
print list(Selector(sel, by_attr='a'))
sel = [42, Flower('Buttercup'), [1,2,3,5,8], A(20, 13), Flower('Rose')]
print list(Selector(sel, by_type=Flower))
sel = [4, 9, 3, 22, 9]
print list(Selector(sel, by_func=lambda i: i%2 == 0))