Python - 对于不适合lambda的后期绑定问题使用'partial' - 成本与收益?

时间:2014-05-01 01:28:09

标签: python lambda late-binding

注意: 我问是否有一种Pythonic方法可以做到这一点(使用默认args似乎比使用partial更少Pythonic)以及是否存在重大限制任何一种方法("成本" - 我不会期望时间显着不同,但也许还有其他限制,我没有看到这种方法与另一种方法的平衡倾斜)。

我试图理解使用“部分”的成本。在lambda不可行的后期绑定情况下。我已经创建了一些示例代码based on this guide来举例说明这一点。

由于后期绑定,以下内容无法正常工作:

def create_thingies():
    thingies = []
    for i in range(1,6):
        def thingy(x):
            print("Some output", i)
            return i ** (x * i)
        thingies.append(thingy)
    return thingies

results=[]
for thingy in create_thingies():
    results.append(thingy(2))
print(results)

输出:

Some output 5
Some output 5
Some output 5
Some output 5
Some output 5
[9765625, 9765625, 9765625, 9765625, 9765625]

使用' partial'我们避免这个问题,但代价是什么?

from functools import partial
def create_thingies():
    thingies = []
    for i in range(1,6):
        def thingy(i, x):
            print("Some output", i)
            return i ** (x * i)
        thingies.append(partial(thingy, i))
    return thingies

results=[]
for thingy in create_thingies():
    results.append(thingy(2))
print(results)

输出:

Some output 1
Some output 2
Some output 3
Some output 4
Some output 5
[1, 16, 729, 65536, 9765625]

我在这里看到很多关于lambda vs partial的讨论,但是在lambda不能正常工作的情况下(一个非常复杂的函数),如果有的话(带有多个表达式的函数)是部分的方式还是有更好的方法将其强制转换为lambda表达式?

3 个答案:

答案 0 :(得分:4)

使用partial,无需为thingy的每个值定义一次i,因为thingy不使用任何免费/全局变量,而只是参数。

from functools import partial

def thingy(i, x):
    print("Some output", i)
    return i ** (x * i)

thingies = [partial(thingy, i) for i in range(1,6)]
results = [th(2) for th in thingies]
print(results)

至于成本,您应该剖析以确定性能是否可以接受。


这是一个比较3个选项的快速测试:

import timeit

# The fastest: define a function using a default parameter value
print timeit.timeit('results = [ th(2) for th in create_thingies()]', '''
def create_thingies():
    thingies = []
    for i in range(1,6):
        def thingy(x,i=i):
            #print("Some output", i)
            return i ** (x * i)
        thingies.append(thingy)
    return thingies
''')

# The slowest, but IMO the easiest to read.
print timeit.timeit('results = [ th(2) for th in create_thingies()]', '''
def create_thingies():
    from functools import partial
    def thingy(i,x):
        #print("Some output", i)
        return i ** (x * i)
    return [partial(thingy, i) for i in range(1,6)]
''')

# Only a little slower than the first
print timeit.timeit('results = [ th(2) for th in create_thingies()]', '''
def create_thingies():
    def make_thingy(i):
        def thingy(x):
            #print("Some output", i)
            return i ** (x * i)
        return thingy
    thingies = [make_thingy(i) for i in range(1,6)]
    return thingies
''')

答案 1 :(得分:2)

使用默认参数nalue在函数创建时绑定值:

def thingy(x, i=i):
    print("Some output", i)
    return i ** (x * i)

答案 2 :(得分:2)

有几种方法可以做早期绑定。一些最流行的是默认args,partial和制造商功能。至少在我使用我的python版本的机器上,它们都需要大约相同的时间。

以下是如何执行三者中的每一个的示例:

import time
from functools import partial
from contextlib import contextmanager

@contextmanager
def timer(what):
    t1 = time.time()
    yield
    print "%-30s: %5d millis" % (what, (time.time() - t1) * 1e3)

N = 5000

print
with timer('create bound'):
    thingies = []
    for i in xrange(N):
        def thingy(x, i=i):
            return i ** (x * i)
        thingies.append(thingy)
with timer('eval bound'):
    for t in thingies:
        t(2)

with timer('create partial'):
    def thingy(i, x):
        return i ** (x * i)
    thingies = [partial(thingy, i) for i in xrange(N)]
with timer('eval partial'):
    for t in thingies:
        t(2)

with timer('create maker'):
    def make_thingy(i):
        def thingy(x):
            return i ** (x * i)
        return thingy
    thingies = [make_thingy(i) for i in xrange(N)]
with timer('eval maker'):
    for t in thingies:
        t(2)

这是我观察的时间(Python 2.7.6 + Windows + Haswell):

create bound                  :     5 millis
eval bound                    :  1861 millis
create partial                :     2 millis
eval partial                  :  1832 millis
create maker                  :     2 millis
eval maker                    :  1829 millis

请注意,创建绑定方法的成本更高,但所有3个版本的调用开销都非常接近。

我通常使用partials和maker函数的混合,具体取决于给定位代码的哪一个最清楚。