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匿名函数、回调函数和高阶函数 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深入了解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

  • 对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) 方式二

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

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

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

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

  • python基础之匿名函数详解

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

  • 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基础之引用和匿名函数

    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肯定有办法将其简化的方法啊!

  • Python编程functools模块创建修改的高阶函数解析

    目录 partial 函数 装饰器 @lru_cache reduce 函数 partial 函数 partial 为偏函数(有的地方也叫做部分应用函数),它是对函数的二次封装,将现有函数的部分参数提前绑定为指定值,然后再进行计算. 由于偏函数的可变参数少,因此函数调用的难度低. 直接展示代码: from functools import partial # 原函数声明 def show(name, level): print("name:", name, "level:&q

  • 浅谈python之高阶函数和匿名函数

    map() map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回. def func(x): return x*x r = map(func, [1, 2, 3, 4, 5]) print(type(r)) r = list(r) print(r) 输出结果: <class 'map'> [1, 4, 9, 16, 25] 可以看出,map让函数func作用于列表的每一项,使列表的每一项都被函数func

  • 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

  • Python编程functools模块中创建修改函数的高阶函数解析

    partial 函数 partial 为偏函数(有的地方也叫做部分应用函数),它是对函数的二次封装,将现有函数的部分参数提前绑定为指定值,然后再进行计算. 由于偏函数的可变参数少,因此函数调用的难度低. 直接展示代码: from functools import partial # 原函数声明 def show(name, level): print("name:", name, "level:", level) # 定义偏函数,封装 show() 函数,并为 na

  • Python常见内置高阶函数即高阶函数用法

    目录 1.什么是高阶函数? 2.高阶函数-map.filter.reduce 2.1map函数 2.2filter函数 2.3reduce函数 1.什么是高阶函数? 高阶函数:一个函数可以作为参数传给另外一个函数,或者一个函数的返回值为另外一个函数(若返回值为该函数本身,则为递归),满足其一则为高阶函数. 参数为函数: #参数为函数 def bar(): print("in the bar..") def foo(func): func() print("in the foo

  • 详解Python函数式编程—高阶函数

    函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用.而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的. 函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数! Python对函数式编程提供部分支持.由于Python允许使用变量,因此,Python不是纯函数式编程语言. 高阶函数 变量

  • Python高阶函数与装饰器函数的深入讲解

    本文主要介绍的是Python高阶函数与装饰器函数的相关内容,分享给大家,下面话不多说了,来一起看看详细的介绍吧 高阶函数 1.可以使用函数对象作为参数的函数 2.或可以将函数作为返回值的函数 3.函数对象:定义好的函数,使用函数名调用(不要加括号) #将函数作为参数的高阶函数,通过传入不同的函数,可以使执行的结果不同 4.内置高阶函数 (1)map数据映射函数 map函数接收的是两个参数,一个函数,一个序列,其功能是将序列中的值处理再依次返回至列表内.其返回值为一个迭代器对象 (2)reduce

  • 简析Python函数式编程字符串和元组及函数分类与高阶函数

    目录 函数式编程中的字符串 不变类型元组 普通元组的声明与访问 函数式的分类 any() .all() .len().sum() 对比学习 zip().reversed().enumerate() 高阶函数 函数 max 和 min() map 函数 filter 函数 sorted 函数 同一需求的不同效率问题 函数式编程中的字符串 在函数式编程中,经常用到 Python 字符串,因其是不可变数据结构. 字符串本身是一个对象,具备很多对象方法,与常识中函数的使用不太相同,例如下述代码 my_s

  • 详解Python中的普通函数和高阶函数

    目录 什么是函数 函数的嵌套调用 高阶函数 我们思考一下计算圆形和方形的面积 为何高阶函数能够降低维度 总结 什么是函数 每个语言都有函数,甚至大家用的Excel里面也有函数,我们以前学习的数学也很多各种各样的函数. Python中的函数也是一样的. def f(x): print("参数为:",x) return x 这里的函数 y = f(x), 在数学中表示为一条斜率为1的直线. 函数的嵌套调用 def z(x): pass def f(x): print("参数为:&

  • JS高阶函数原理与用法实例分析

    本文实例讲述了JS高阶函数原理与用法.分享给大家供大家参考,具体如下: 如果您正在学习JavaScript,那么您必须遇到高阶函数这个术语.这听起来复杂,其实不然. 使JavaScript适合函数式编程的原因是它接受高阶函数. 高阶函数在JavaScript中广泛使用.如果你已经用JavaScript编程了一段时间,你可能已经使用它们甚至不知道. 要完全理解这个概念,首先必须了解函数式编程是什么一等函数(first-Class Function)以及的概念. 函数式编程 在大多数简单的术语中,函

随机推荐