【问题标题】:Optimise filtering lists in Python 2.7在 Python 2.7 中优化过滤列表
【发布时间】:2012-04-27 11:27:58
【问题描述】:

我需要多次过滤大型列表,但我关心代码的简单性和执行效率。举个例子:

all_things # huge collection of all things

# inefficient but clean code
def get_clothes():
    return filter(lambda t: t.garment, allThings)

def get_hats():
    return filter(lambda t: t.headgear, get_clothes())

我担心我正在迭代衣服列表,而实际上它已经被迭代了。我还想将两个过滤器操作分开,因为它们属于两个不同的类,并且我不想复制 hats 类中的第一个 lambda 函数。

# efficient but duplication of code
def get_clothes():
    return filter(lambda t: t.garment, allThings)

def get_hats():
    return filter(lambda t: t.headgear and t.garment, allThings)

我一直在研究生成器函数,因为它们似乎是可行的方法,但我还没有弄清楚如何。

【问题讨论】:

  • 如果您担心性能,您是否测试性能?
  • 如果我认为这并不明显,我会这样做的。
  • “明显”在性能方面是一个危险的词。
  • 经过测试:根据我的粗略测试,衣服大约是完整列表的 30%,执行时间减少了大约 40%。相当扎实。

标签: python generator python-2.7


【解决方案1】:

首先使用filter/lambda 组合将被弃用。当前的函数式编程风格在Python Functional Programming HOWTO 中进行了描述。

其次,如果你关心效率,而不是构造列表,你应该返回generators。在这种情况下,它们很简单,可以使用 generator expressions

def get_clothes():
    return (t for t in allThings if t.garment)

def get_hats():
    return (t for t in get_clothes() if t.headgear)

或者,如果您愿意,也可以使用真正的生成器(据称更 Pythonic):

def get_clothes():
    for t in allThings:
       if t.garment:
           yield t

def get_hats():
    for t in get_clothes():
        if t.headgear:
            yield t

如果由于某种原因,有时您需要list 而不是iterator,您可以通过简单的强制转换来构造列表:

hats_list = list(get_hats())

注意,上面将不会构造衣服列表,因此效率接近你的重复代码版本。

【讨论】:

  • 1) filter/lambda 组合未被弃用。 2) PEP 8 建议不要返回生成器表达式——无论它们是否被创建,它们都应该在相同的范围内使用——而是使用常规生成器。 3)。如果需要一个列表,则 OP 应该使用列表推导而不是 list 包裹在 genexp 周围。
  • @RaymondHettinger:1)由于强烈反对,它还没有被正式弃用,但它已经被考虑放弃超过 7 年了。 2) 在 PEP-8 3) 中没有真正找到任何与此相关的内容,当且仅当他总是需要列表。
  • @vartec:返回 genexp 很危险的一个例子:programmaticallyspeaking.com/?p=471。您的 get_hats 等应该是真正的生成器本身 (for t... if t.headgear: yield t) 而不是返回 genexps。 // 好像已经修复了。
  • @thg435:很公平,但是就像我在回答中所说的那样,在这种特殊情况下,生成器表达式是可以的。在您链接到的示例中,使用闭包更多的是一个问题,在这种情况下,闭包是生成器表达式并不重要。
  • 就个人而言,我有时发现filter/map/reduce 比等效的列表推导或循环更容易阅读。在 OP 的情况下,listcomp 会更简单、更易于阅读,但并非总是如此。
【解决方案2】:

我一直在寻找类似的列表过滤,但希望与此处显示的格式略有不同。

上面的get_hats() 调用很好,但重用性有限。我一直在寻找更像 get_hats(get_clothes(all_things)) 的东西,您可以在其中指定源 (all_things),然后根据需要指定尽可能少或尽可能多的过滤器级别 get_hats()get_clothes()

我找到了一种使用生成器的方法:

def get_clothes(in_list):
    for item in in_list:
        if item.garment:
            yield item

def get_hats(in_list):
    for item in in_list:
        if item.headgear:
            yield item

然后可以通过以下方式调用它:

get_hats(get_clothes(all_things))

我测试了原来的解决方案、vartec 的解决方案和这个附加的解决方案,看看效率,结果有点惊讶。代码如下:

设置:

class Thing:
    def __init__(self):
        self.garment = False
        self.headgear = False

all_things = [Thing() for i in range(1000000)]

for i, thing in enumerate(all_things):
    if i % 2 == 0:
        thing.garment = True
    if i % 4 == 0:
        thing.headgear = True

原始解决方案:

def get_clothes():
    return filter(lambda t: t.garment, all_things)

def get_hats():
    return filter(lambda t: t.headgear, get_clothes())

def get_clothes2():
    return filter(lambda t: t.garment, all_things)

def get_hats2():
    return filter(lambda t: t.headgear and t.garment, all_things)

我的解决方案:

def get_clothes3(in_list):
    for item in in_list:
        if item.garment:
            yield item

def get_hats3(in_list):
    for item in in_list:
        if item.headgear:
            yield item

vartec 的解决方案:

def get_clothes4():
    for t in all_things:
       if t.garment:
           yield t

def get_hats4():
    for t in get_clothes4():
        if t.headgear:
            yield t

计时码:

import timeit

print 'get_hats()'
print timeit.timeit('get_hats()', 'from __main__ import get_hats', number=1000)

print 'get_hats2()'
print timeit.timeit('get_hats2()', 'from __main__ import get_hats2', number=1000)

print '[x for x in get_hats3(get_clothes3(all_things))]'
print timeit.timeit('[x for x in get_hats3(get_clothes3(all_things))]',
                    'from __main__ import get_hats3, get_clothes3, all_things',
                    number=1000)

print '[x for x in get_hats4()]'
print timeit.timeit('[x for x in get_hats4()]',
                    'from __main__ import get_hats4', number=1000)

结果:

get_hats()
379.334653854
get_hats2()
232.768362999
[x for x in get_hats3(get_clothes3(all_things))]
214.376812935
[x for x in get_hats4()]
218.250688076

生成器表达式似乎稍快一些,我的解决方案和 vartec 的解决方案之间的时间差异可能只是噪音。但我更喜欢能够以任何顺序应用所需过滤器的灵活性。

【讨论】:

    【解决方案3】:

    一次性完成(伪代码):

    clothes = list()
    hats = list()
    for thing in things:
        if thing is a garment:
            clothes.append(thing)
            if thing is a hat:
                hats.append(thing)
    

    以一大通和一小通来完成(列表推导):

    clothes = [ x for x in things if x is garment ]
    hats = [ x for x in clothes if x is hat ]
    

    如果你想创建整个列表,使用生成器表达式进行惰性求值是没有意义的,因为你不会是惰性的。

    如果您只想一次处理几件事情,或者如果您的内存有限,请使用@vartec 的生成器解决方案。

    【讨论】:

    • 您可能需要修复 thing in things 的使用情况
    • @okm:没看到,抱歉——你能详细说明一下吗?
    • 我的意思是 [thing in clothes if thing is hat] 语法不正确,不是吗?
    猜你喜欢
    • 1970-01-01
    • 2021-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-29
    • 2019-04-18
    • 2012-09-15
    相关资源
    最近更新 更多