这 查看效果更好
7.7匿名函数捕获变量值
问题:利用lambda定义了一个函数,想在定义时捕获变量的值 方案:将参数定义为默认参数
x = 10
a = lambda y: y + x
x = 20
b = lambda y: y + x
a(10 )
30
b(10 )
30
这其中的奥妙在于 lambda 表达式中的 x 是一个自由变量,在运行时绑定值,而不是定义时就绑定,这跟函数的默认值参数定义是不同的。因此,在调用这个 lambda 表达式的时候, x 的值是执行时的值。
x = 100
a(10 )
110
b(10 )
110
如果想让某个匿名函数在定义时就捕获到该值,可以将该参数定义成默认参数
x = 10
a = lambda y, x=x: x+y
x = 20
b = lambda y, x=x: x+y
a(20 )
30
b(20 )
40
x = 100
a(10 )
20
这里有一个很容易犯的错误:想通过一个循环或者列表推导创建一个lambda表达式列表,并期望函数能够在定义时记住每次迭代的值,如下(并不能满足期望):
funcs = [lambda x:x+n for n in range(4 )]
for f in funcs:
print(f(10 ),end=' ' )
13 13 13 13
funcs = [lambda x,n=n:x+n for n in range(4 )]
for f in funcs:
print(f(10 ),end=' ' )
10 11 12 13
7.8减少可调用对象的参数个数
问题:有一个可调用函数,其参数过多,调用时容易出错 方案:使用functools.partial()。该函数允许你给一个或者多个参数设定固定的值,减少接下来被调用时的参数个数
def spam (a, b, c, d) :
print(a, b, c, d)
from functools import partial
s1 = partial(spam,1 )
s1(2 ,3 ,4 )
1 2 3 4
s1(3 ,4 ,5 )
1 3 4 5
s2 = partial(spam, d=10 )
s2(1 ,2 ,3 )
1 2 3 10
s3 = partial(spam, 1 ,2 ,d=100 )
s3(10 )
1 2 10 100
上述例子,partial()固定某些参数,并返回一个可调用的对象,该对象接收未赋值的参数,然后和之前固定的参数一起传入原始函数
points = [(1 ,2 ),(3 ,4 ),(5 ,6 ),(7 ,8 )]
import math
def distance (p1, p2) :
x1,y1 = p1
x2,y2 = p2
return math.hypot(x2-x1,y2-y1)
现在假设你想以某个点为基点,根据点和基点之间的距离来排序所有的这些点。列表的 sort() 方法接受一个关键字参数来自定义排序逻辑,但是它只能接受一个单个参数的函数 (distance() 很明显是不符合条件的)。现在我们可以通过使用 partial() 来解决这个问题:
pt = (4 ,3 )
points.sort(key=partial(distance,pt))
points
[(3, 4), (1, 2), (5, 6), (7, 8)]
d = partial(distance,pt)
dis = [d(point) for point in points]
dis
[1.4142135623730951, 3.1622776601683795, 3.1622776601683795, 5.830951894845301]
dis.sort()
dis
[1.4142135623730951, 3.1622776601683795, 3.1622776601683795, 5.830951894845301]
partial() 通常被用来微调其他库函数所使用的回调函数的参数。例如,下面是一段代码,使用 multiprocessing 来异步计算一个结果值,然后这个值被传递给一个接受一个 result 值和一个可选 logging 参数的回调函数:
def output_result (result, log=None) :
if log is not None :
log.debug('Got: %r' ,result)
def add (x,y) :
return x+y
if __name__=='__main__' :
import logging
from multiprocessing import Pool
from functools import partial
logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger('test' )
p = Pool()
p.apply_async(add,(3 ,4 ),callback=partial(output_result,log=log))
p.close()
p.join()
7.9将简单的类转化为函数
问题:有一个除了init ()方法只定义了一个方法的类。为了简化代码将其转化为一个函数 方案:可以使用闭包来将单个方法的类转化成函数
from urllib.request import urlopen
class UrlTemplate :
def __init__ (self,template) :
self.template = template
def open (self, **kwargs) :
return urlopen(self.template.format_map(kwargs))
yahoo = UrlTemplate('http://finance.yahoo.com/d/quotes.csv?s={names}&f={fields}' )
for line in yahoo.open(names = 'IBM,AAPL,FB' ,fields = 'sl1c1v' ):
print(line.decode('utf-8' ))
一个闭包就是 一个函数,只不过在函数内部带上了一个额外的变量环境。闭包关键特点就是它会记 住自己被定义时的环境。
def urltemplate (template) :
def opener (**kwargs) :
return urlopen(template.format_map(kwargs))
return opener
带额外状态信息的回调函数
问题:你的代码中需要依赖到回调函数的使用 (比如事件处理器、等待后台任务完成后的 回调等),并且你还需要让回调函数拥有额外的状态值,以便在它的内部使用到 方案:如下
def apply_async (func, args, * ,callback) :
result = func(*args)
callback(result)
def print_result (result) :
print('Got: ' ,result)
def add (x,y) :
return x+y
apply_async(add,(2 ,3 ),callback=print_result)
Got: 5
下面的类会保存一个内部序列号,每次接收到一个result时,序列号加1
class ResultHandler :
def __init__ (self) :
self.sequence = 0
def handler (self,result) :
self.sequence += 1
print('[{}] Gpt: {}' .format(self.sequence,result))
r = ResultHandler()
apply_async(add,('hello' ,'world' ),callback=r.handler)
[1] Gpt: helloworld
apply_async(add,(1 ,2 ),callback=r.handler)
[2] Gpt: 3
apply_async(add,('hel' ,'world' ),callback=r.handler)
[3] Gpt: helworld
def make_handler () :
sequence = 0
def handler (result) :
nonlocal sequence
sequence += 1
print('[{}] Got: {}' .format(sequence,result))
return handler
handler = make_handler()
apply_async(add,(2 ,3 ),callback=handler)
[1] Got: 5
apply_async(add,(4 ,3 ),callback=handler)
[2] Got: 7
def make_handler () :
sequence = 0
while True :
result = yield
sequence += 1
print('[{}] Got: {}' .format(sequence,result))
handler = make_handler()
next(handler)
apply_async(add,(10 ,20 ),callback=handler.send)
[1] Got: 30
apply_async(add,(100 ,20 ),callback=handler.send)
[2] Got: 120
7.11内联回调函数
问题:当你编写使用回调函数的代码的时候,担心很多小函数的扩张可能会弄乱程序控 制流。你希望找到某个方法来让代码看上去更像是一个普通的执行序列。 方案:通过使用生成器和协程可以使得回调函数内联在某个函数中。
def apply_async (func, args, *, callback) :
result = func(*args)
callback(result)
from queue import Queue
from functools import wraps
class Async :
def __init__ (self, func,args) :
self.func = func
self.args = args
def inlined_async (func) :
@wraps(func)
def wrapper (*args) :
f = func(*args)
result_queue = Queue()
result_queue.put(None )
while True :
result = result_queue.get()
try :
a = f.send(result)
apply_async(a.func, a.args, callback=result_queue.put)
except StopIteration:
break
return wrapper
def add (x, y) :
return x+y
@inlined_async
def test () :
r = yield Async(add,(2 ,3 ))
print(r)
r = yield Async(add,('hello' ,'world' ))
print(r)
for n in range(5 ):
r = yield Async(add,(n,n))
print(r)
print('goodbye' )
test()
5
helloworld
0
2
4
6
8
goodbye
if __name__ == "__main__" :
import multiprocessing
pool = multiprocessing.Pool()
apply_async = pool.apply_async
test()
7.12 访问闭包中定义的变量
问题:想要扩展函数中的某个闭包,允许它能够访问和修改函数的内部变量 方案:通常,闭包的内部变量对于外界是完全隐藏的,但是可以通过编写访问函数并将其作为函数的属性绑定到闭包上来实现这个目的
def sample () :
n = 0
def func () :
print('n= ' ,n)
def get_n () :
return n
def set_n (value) :
nonlocal n
n = value
func.get_n = get_n
func.set_n = set_n
return func
f = sample()
f()
n= 0
f.set_n(10 )
f.get_n()
10
import sys
class ClosureInstance :
def __init__ (self,locals=None) :
if locals is None :
locals = sys._getframe(1 ).f_locals
self.__dict__.update((key,value) for key ,value in locals.items())
def __len__ (self) :
return self.__dict__['__len__' ]()
def Stack () :
items = []
def push (item) :
items.append(item)
def pop () :
return items.pop()
def __len__ () :
return len(items)
return ClosureInstance()
s = Stack()
s
<__main__.ClosureInstance at 0x5934570>
s.push(10 )
s.push(20 )
s.push('hello' )
len(s)
3
s.pop()
'hello'
s.pop()
20