Python - 使用'partial'解决不适合lambda的延迟绑定问题的成本与收益?

2 投票
3 回答
1181 浏览
提问于 2025-04-18 04:57

注意: 我想知道有没有一种更“Pythonic”的方法来做这个(使用默认参数似乎不如使用partial更符合Python的风格),以及这两种方法是否有显著的限制(“成本” - 我不认为时间上会有太大差别,但也许还有其他我没有注意到的限制,这可能会让我们更倾向于某种方法)。

我正在尝试理解在某些情况下使用'partial'的成本,尤其是在lambda不适用的情况下。我根据这个指南创建了一些示例代码来说明这个问题。

下面的代码由于晚绑定的问题而无法按预期工作:

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和partial的讨论,但在一些lambda不太适用的情况下(比如非常复杂的函数)或者根本不适用的情况下(比如有多个表达式的函数),使用partial是否是最佳选择,还是有更好的方法,而不需要强行把它转化为lambda表达式呢?

3 个回答

3

有几种方法可以实现早绑定。最常见的几种方法是默认参数、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

在创建函数的时候,使用默认参数值来绑定这个值:

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

使用 partial 的话,就不需要为每个 i 的值单独定义一次 thingy,因为 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)

至于成本,你应该进行性能测试,看看表现是否可以接受。


这里有一个快速测试,用来比较三种选择:

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
''')

撰写回答