Python - 使用'partial'解决不适合lambda的延迟绑定问题的成本与收益?
注意: 我想知道有没有一种更“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
''')