Python技巧匿名函数、回调函数和高阶函数

目录
  • 1、定义匿名或内联函数
  • 2、在匿名函数中绑定变量的值
  • 3、让带有n个参数的可调用对象以较少的参数调用
  • 4、在回调函数中携带额外的状态

1、定义匿名或内联函数

如果我们想提供一个短小的回调函数供sort()这样的函数用,但不想用def这样的语句编写一个单行的函数,我们可以借助lambda表达式来编写“内联”式的函数。

如下图所示:

add = lambda x, y: x + y
print(add(2, 3)) # 5
print(add("hello", "world!")) # helloworld

可以看到,这里用到的lambda表达式和普通的函数定义有着相同的功能。
lambda表达式常常做为回调函数使用,有在排序以及对数据进行预处理时有许多用武之地,

如下所示:

names = [ 'David Beazley', 'Brian Jones', 'Reymond Hettinger', 'Ned Batchelder']
sorted_names = sorted(names, key=lambda name: name.split()[-1].lower())
print(sorted_names)
# ['Ned Batchelder', 'David Beazley', 'Reymond Hettinger', 'Brian Jones']

lambda虽然灵活易用,但是局限性也大,相当于其函数体中只能定义一条语句,不能执行条件分支、迭代、异常处理等操作。

2、在匿名函数中绑定变量的值

现在我们想在匿名函数定义时完成对特定变量(一般是常量)的绑定,以便后期使用。

如果我们这样写:

x = 10
a = lambda y: x + y
x = 20
b = lambda y: x + y

然后计算a(10)和b(10)。你可能希望结果是20和30,然而实际程序的运行结果会出人意料:结果是30和30。
这个问题的关键在于lambda表达式中的x是个自由变量(未绑定到本地作用域的变量),在运行时绑定而不是定义的时候绑定(其实普通函数中使用自由变量同理),而这里执行a(10)的时候x已经变成了20,故最终a(10)的值为30。如果希望匿名函数在定义的时候绑定变量,而之后绑定值不再变化,那我们可以将想要绑定的变量做为默认参数,

如下所示:

x = 10
a = lambda y, x=x: x + y
x = 20
b = lambda y, x=x: x + y
print(a(10)) # 20
print(b(10)) # 30

上面我们提到的这个陷阱常见于一些对lambda函数过于“聪明”的应用中。比如我们想用列表推导式来创建一个列表的lambda函数并期望lambda函数能记住迭代变量。

funcs = [lambda x: x + n for n in range(5)]
for f in funcs:
    print(f(0))
# 4
# 4
# 4
# 4
# 4

可以看到与我们期望的不同,所有lambda函数都认为n是4。

如上所述,我们修改成以下代码即可:

funcs = [lambda x, n=n: x + n for n in range(5)]
for f in funcs:
    print(f(0))
# 0
# 1
# 2
# 3
# 4

3、让带有n个参数的可调用对象以较少的参数调用

假设我们现在有个n个参数的函数做为回调函数使用,但这个函数需要的参数过多,而回调函数只能有个参数。如果需要减少函数的参数数量,需要时用functools包。functools这个包内的函数全部为高阶函数。高阶函数即参数或(和)返回值为其他函数的函数。通常来说,此模块的功能适用于所有可调用对象。

比如functools.partial()就是一个高阶函数, 它的原型如下:

functools.partial(func, /, *args, **keywords)

它接受一个func函数做为参数,并且它会返回一个新的newfunc对象,这个新的newfunc对象已经附带了位置参数args和关键字参数keywords,之后在调用newfunc时就可以不用再传已经设定好的参数了。

如下所示:

def spam(a, b, c, d):
  print(a, b, c, d)

from functools import partial
s1 = partial(spam, 1) # 设定好a = 1(如果没指定参数名,默认按顺序设定)
s1(2, 3, 4) # 1 2 3 4

s2 = partial(spam, d=42) # 设定好d为42
s2(1, 2, 3) # 1 2 3 42

s3 = partial(spam, 1, 2, d=42) #设定好a = 1, b = 2, d = 42
s3(3) # 1 2 3 42

上面提到的技术常常用于将不兼容的代码“粘”起来,尤其是在你调用别人的轮子,而别人写好的函数不能修改的时候。比如我们有以下一组元组表示的点的坐标:

points = [(1, 2), (3, 4), (5, 6), (7, 8)]

有已知的一个distance()函数可供使用,假设这是别人造的轮子不能修改。

import math
def distance(p1, p2):
    x1, y1 = p1
    x2, y2 = p2
    return math.hypot(x2 - x1, y2 - y1)

接下来我们想根据列表中这些点到一个定点pt=(4, 3)的距离来排序。我们知道列表的sort()方法
可以接受一个key参数(传入一个回调函数)来做自定义的排序处理。但传入的回调函数只能有一个参数,这里的distance()函数有两个参数,显然不能直接做为回调函数使用。

下面我们用partical()来解决这个问题:

pt = (4, 3)
points.sort(key=partial(distance, pt)) # 先指定好一个参数为pt=(4,3)
print(points)
# [(3, 4), (1, 2), (5, 6), (7, 8)]

可以看到,排序正确运行。还有一种方法要臃肿些,那就是将回调函数distance嵌套进另一个只有一个参数的lambda函数中:

pt = (4, 3)
points.sort(key=lambda p: distance(p, pt))
print(points)
# [(3, 4), (1, 2), (5, 6), (7, 8)]

这种方法一来臃肿,二来仍然存在我们上面提到过的一个毛病,如果我们定义回调函数后对pt有所修改,就会发生我们上面所说的不愉快的事情:

pt = (4, 3)
func_key = lambda p: distance(p ,pt)
pt = (0, 0) # 像这样,后面pt变了就GG
points.sort(key=func_key)
print(points)
# [(1, 2), (3, 4), (5, 6), (7, 8)]

可以看到,最终排序的结果由于后面pt的改变而变得完全不同了。所以我们还是建议大家采用使用functools.partial()函数来达成目的。
下面这段代码也是用partial()函数来调整函数签名的例子。这段代码利用multiprocessing模块以异步方式计算某个结果,然后用一个回调函数来打印该结果,该回调函数可接受这个结果和一个事先指定好的日志参数。

# result:回调函数本身该接受的参数, log是我想使其扩展的参数
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()

# DEBUG:test:Got: 7

下面这个例子则源于一个在编写网络服务器中所面对的问题。比如我们在socketServer模块的基础上,

编写了下面这个简单的echo服务程序:

from socketserver import StreamRequestHandler, TCPServer
class EchoHandler(StreamRequestHandler):
    def handle(self):
        for line in self.rfile:
            self.wfile.write(b'GoT:' + line)

serv = TCPServer(('', 15000), EchoHandler)
serv.serve_forever()

现在,我们想在EchoHandler类中增加一个__init__()方法,它接受额外的一个配置参数,用于事先指定ack。即:

class EchoHandler(StreamRequestHandler):
    def __init__(self, *args, ack, **kwargs):
        self.ack = ack
        super().__init__(*args, **kwargs)
    def handle(self) -> None:
        for line in self.rfile:
            self.wfile.write(self.ack + line)

假如我们就这样直接改动,就会发现后面会提示__init__()函数缺少keyword-only参数ack(这里调用EchoHandler()初始化对象的时候会隐式调用__init__()函数)。 我们用partical()也能轻松解决这个问题,即为EchoHandler()事先提供好ack参数。

from functools import partial
serv = TCPServer(('', 15000), partial(EchoHandler, ack=b'RECEIVED'))
serv.serve_forever()

4、在回调函数中携带额外的状态

我们知道,我们调用回调函数后,就会跳转到一个全新的环境,此时会丢失我们原本的环境状态。接下来我们讨论如何在回调函数中携带额外的状态以便在回调函数内部使用。
因为对回调函数的应用在与异步处理相关的库和框架中比较常见,我们下面的例子也多和异步处理相关。现在我们定义了一个异步处理函数,它会调用一个回调函数。

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
apply_async(add, ('hello', 'world'), callback=print_result)
# Got: helloworld

现在我们希望回调函数print_reuslt()能够接受更多的参数,比如其他变量或者环境状态信息。比如我们想让print_result()函数每次的打印信息都包括一个序列号,以表示这是第几次被调用,如[1] ...、[2] ...这样。首先我们想到,可以用额外的参数在回调函数中携带状态,然后用partial()来处理参数个数问题:

class SequenceNo:
    def __init__(self) -> None:
        self.sequence = 0

def handler(result, seq):
    seq.sequence += 1
    print("[{}] Got: {}".format(seq.sequence, result))

seq = SequenceNo()
from functools import partial
apply_async(add, (2, 3), callback=partial(handler, seq=seq))
# [1] Got: 5
apply_async(add, ('hello', 'world'), callback=partial(handler, seq=seq))
# [2] Got: helloworld

看起来整个代码有点松散繁琐,我们有没有什么更简洁紧凑的方法能够处理这个问题呢?答案是直接使用和其他类绑定的方法(bound-method)。比如面这段代码就将print_result做为一个类的方法,这个类保存了计数用的ack序列号,每当调用print_reuslt()打印一个结果时就递增1:

class ResultHandler:
    def __init__(self) -> None:
        self.sequence = 0
    def handler(self, result):
        self.sequence += 1
        print("[{}] Got: {}".format(self.sequence, result))

apply_async(add, (2, 3), callback=r.handler)
# [1] Got: 5
apply_async(add, ('hello', 'world'), callback=r.handler)
# [2] Got: helloworld

还有一种实现方法是使用闭包,这种方法和使用类绑定方法相似。但闭包更简洁优雅,运行速度也更快:

def make_handler():
    sequence = 0
    def handler(result):
        nonlocal sequence # 在闭包中编写函数来修改内层变量,需要用nonlocal声明
        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, ('hello', 'world'), callback=handler)
# [2] Got: helloworld

最后一种方法,则是利用协程(coroutine)来完成同样的任务:

def make_handler_cor():
    sequence = 0
    while True:
        result = yield
        sequence += 1
        print("[{}] Got: {}".format(sequence, result))

handler = make_handler_cor()
next(handler) # 切记在yield之前一定要加这一句
apply_async(add, (2, 3), callback=handler.send) #对于协程来说,可以使用它的send()方法来做为回调函数
# [1] Got: 5
apply_async(add, ('hello', 'world'), callback=handler.send)
# [2] Got: helloworld

到此这篇关于Python技巧匿名函数、回调函数和高阶函数 的文章就介绍到这了,更多相关Python匿名函数、回调函数和高阶函数 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2021-12-03

python基础之引用和匿名函数

a=1 #1 为对象, def func(x): print('x的地址{}'.format(id(x))) x=2 print('x的地址{}'.format(id(x))) pass # 调用函数 print('a的地址:{}'.format(id(a))) func(a) # 不可变类型 a=1 #1 为对象, ##传递的是一个对象的引用,并不是一个值 def func(x): print('x的地址{}'.format(id(x))) x=2 print('x的地址{}'.format(

Python匿名函数详情

1.匿名函数 在python中,除了一般使用def定义的函数外,还有一种使用lambda定义的匿名函数.这种函数可以用在任何普通函数可以使用的地方,但在定义时被严格限定为单一表达式.从语义上讲,它只是普通函数的语法糖. 如果我们需要定义一个特别简单的函数,例如 def add(a, b):     s = a + b     return s 这就出现问题了,这么优雅的Python怎么可以出现这种难看的代码呢,有没有办法可以将其简化为1行代码呢?这么优雅的Python肯定有办法将其简化的方法啊!

Python3-异步进程回调函数(callback())介绍

废话不多说,大家之家看代码吧! #异步 ''' 举例: 你喊你朋友吃饭,你朋友正忙, 如果你一直在那等他,等你朋友忙完了,你们一块去.--同步调用 你喊你朋友吃饭,你朋友正忙, 如果你自己做你自己的事,你朋友忙完,找到你,一块去吃饭.--异步调用 ''' # from bs4 import BeautifulSoup from multiprocessing import Process,Pool import os import time #子进程任务 def download(): prin

对Python3之进程池与回调函数的实例详解

进程池 代码演示 方式一 from multiprocessing import Pool def deal_task(n): n -= 1 return n if __name__ == '__main__': n = 10 p = Pool(4) for i in range(4): res = p.apply(deal_task, args=(n,)) #调用apply是一个串行的效果,任务会被进程一个一个的处理,直接得到结果 #前提是执行的任务必须要有返回值 print(res) 方式二

python基础之匿名函数详解

目录 1.匿名函数介绍 2.语法 3.使用场景 4.匿名函数和普通函数的对比 5.匿名函数的多种形式 6.lambda 作为一个参数传递 7. lambda函数与python内置函数配合使用 8.lambda 作为函数的返回值 1.匿名函数介绍 匿名函数指一类无须定义标识符的函数或子程序.Python用lambda语法定义匿名函数,只需用表达式而无需申明. 在python中,不通过def来声明函数名字,而是通过 lambda 关键字来定义的函数称为匿名函数. lambda函数能接收任何数量(可以

深入了解python高阶函数编写与使用

目录 1.变量可以指向函数 2.函数名也可以是变量. 3.传入函数 总结 何为高阶函数,以实际代码为例子一步步深入概念. 1.变量可以指向函数 以abs()为例: >>>abs(-10) 10 但是只写abs呢? >>>abs <built-in function abs> abs(-10)是调用函数而abs是函数本身 . 把函数本身赋给变量呢? >>>f=abs >>>f <built-in function ab

Python 内置高阶函数详细

目录 1.Python的内置高阶函数 1.1 map() 1.2 reduce() 函数 1.3 reduce() 函数 1.4 sorted() 函数 1.Python的内置高阶函数 1.1 map() map()会根据提供的函数对指定序列做映射 语法格式: map(function, iterable, ...) 第一个参数function以参数序列中的每一个元素调用function函数, 第二个参数iterable一个或多个序列 返回包含每次 function 函数返回值的新列表. 示例代

Python全栈之迭代器和高阶函数

目录 1. lambda表达式 2. locals和globals 3. 迭代器 小提示: 4. map高阶函数 5. reduce高阶函数 6. filter高阶函数 7. sorted高阶函数 8. 小练习 总结 1. lambda表达式 # ### 匿名函数 : lambda表达式 """ 概念: 用一句话来表达只有返回值的函数 语法: lambda 参数 : 返回值 特点: 简洁,高效 """ # (1) 无参的lambda表达式 def

Python中常用的高阶函数实例详解

前言 高阶函数指的是能接收函数作为参数的函数或类:python中有一些内置的高阶函数,在某些场合使用可以提高代码的效率. lambda 当在使用一些函数的时候,我们不需要显式定义函数名称,直接传入lambda匿名函数即可.lambda匿名函数通常和其他函数搭配使用. 比如可以直接使用如下的lambda表达式计算当x=3时,y = x * 3 + 5的函数值. In [1]: (lambda x: x * 3 + 5)(3) Out[1]: 14 map map函数将一个函数和序列/迭代器(可以传

Python高阶函数、常用内置函数用法实例分析

本文实例讲述了Python高阶函数.常用内置函数用法.分享给大家供大家参考,具体如下: 高阶函数: 允许将函数作为参数传入另一个函数: 允许返回一个函数. #返回值为函数的函数 sum=lambda x,y:x+y sub=lambda x,y:x-y calc_dict={"+":sum,"-":sub} def calc(x): return calc_dict[x] print(calc('-')(5,6)) print(calc('+')(5,6)) #参数

python lambda函数及三个常用的高阶函数

进行编程时,一般我们会给一个函数或者变量起一个名字,该名称是用于引用或寻址函数变量.但是有一个低调的函数,你不需要赋予它名字,因此该函数也叫匿名函数.该函数就是Python中的Lambda函数,下面就来为大家解析python-lambda函数,三个常用的高阶函数. 为什么要使用Python Lambda函数? 匿名函数可以在程序中任何需要的地方使用,但是这个函数只能使用一次,即一次性的.因此Python Lambda函数也称为丢弃函数,它可以与其他预定义函数(如filter(),map()等)一

详谈Python高阶函数与函数装饰器(推荐)

一.上节回顾 Python2与Python3字符编码问题,不管你是初学者还是已经对Python的项目了如指掌了,都会犯一些编码上面的错误.我在这里简单归纳Python3和Python2各自的区别. 首先是Python3-->代码文件都是用utf-8来解释的.将代码和文件读到内存中就变成了Unicode,这也就是为什么Python只有encode没有decode了,因为内存中都将字符编码变成了Unicode,而Unicode是万国码,可以"翻译"所以格式编码的格式.Python3中

python利用高阶函数实现剪枝函数

本文为大家分享了python利用高阶函数实现剪枝函数的具体代码,供大家参考,具体内容如下 案例: 某些时候,我们想要为多个函数,添加某种功能,比如计时统计,记录日志,缓存运算结果等等 需求: 在每个函数中不需要添加完全相同的代码 如何解决? 把相同的代码抽调出来,定义成装饰器 求斐波那契数列(黄金分割数列),从数列的第3项开始,每一项都等于前两项之和 求一个共有10个台阶的楼梯,从下走到上面,一次只能迈出1~3个台阶,并且不能后退,有多少中方法? 上台阶问题逻辑整理: 每次迈出都是 1~3 个台

Python的高阶函数用法实例分析

本文实例讲述了Python的高阶函数用法.分享给大家供大家参考,具体如下: 高阶函数 1.MapReduce MapReduce主要应用于分布式中. 大数据实际上是在15年下半年开始火起来的. 分布式思想:将一个连续的字符串转为列表,元素类型为字符串类型,将其都变成数字类型,使用分布式思想[类似于一件事一个人干起来慢,但是如果人多呢?效率则可以相应的提高],同理,一台电脑处理数据比较慢,但是如果有100台电脑同时处理,则效率则会快很多,最终将每台电脑上处理的数据进行整合. python的优点:内

简单了解python高阶函数map/reduce

高阶函数map/reduce Python内建了map()和reduce()函数. 我们先看map.map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回. 举例说明,比如我们有一个函数f(x)=x2,要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map()实现如下: 现在,我们用Python代码实现: def f(x): return x * x r =

python全栈要学什么 python全栈学习路线

IT行业,技术要比学历.年龄.从业经验更为重要,技术水平直接决定就业薪资,想要学好python,首先要先了解精通Python语言基础.Python web开发.Python爬虫.Python数据分析这四大方面. 全栈即指的是全栈工程师,指掌握多种技能,并能利用多种技能独立完成产品的人.就是与这项技能有关的都会,都能够独立的完成. 全栈只是个概念,也分很多种类.真正的全栈工程师涵盖了web开发.DBA .爬虫 .测试.运维,要学的内容那是相当的巨量.就web开发方向而言需要学习的内容:前端知识 包

python全栈知识点总结

全栈即指的是全栈工程师,指掌握多种技能,并能利用多种技能独立完成产品的人.就是与这项技能有关的都会,都能够独立的完成. 全栈只是个概念,也分很多种类.真正的全栈工程师涵盖了web开发.DBA .爬虫 .测试.运维,要学的内容那是相当的巨量.就web开发方向而言需要学习的内容:前端知识 包括HTML5 CSS3 JS Jquery Ajax,后端至少需要能够熟练使用Django和tornado,当然会flask更好. 扩展资料: 全栈工程师的厉害之处并不是他掌握很多知识,可以一个人干多份工作.而是

python高级特性和高阶函数及使用详解

python高级特性 1.集合的推导式 •列表推导式,使用一句表达式构造一个新列表,可包含过滤.转换等操作. 语法:[exp for item in collection if codition] if codition - 可选 •字典推导式,使用一句表达式构造一个新列表,可包含过滤.转换等操作. 语法:{key_exp:value_exp for item in collection if codition} •集合推导式 语法:{exp for item in collection if