Python高级(三):函数式编程

是小鱼呀·2022-07-29 22:33·154 次阅读

Python高级(三):函数式编程

返回函数#

函数作为返回值#

Copy
def lazy_sum(*args): def sum(): ax = 0 for n in args: ax = ax + n return ax return sum

调用lazy_sum()时,返回的并不是求和结果,而是求和函数:

Copy
>>> f = lazy_sum(1, 3, 5, 7, 9) >>> f <function lazy_sum.<locals>.sum at 0x101c6ed90>

调用函数f时,才真正计算求和结果:

Copy
>>> f() 25

注意:
1.调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数
2.内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为”闭包“
3.返回的函数并没有立刻执行,而是直到调用了f()才执行

闭包#

  1. 使用闭包,就是内层函数引用了外层函数的局部变量。如果只是读外层变量的值,我们会发现返回的闭包函数调用一切正常:
Copy
def inc(): x = 0 def fn(): # 仅读取x的值: return x + 1 return fn f = inc() print(f()) # 1 print(f()) # 1
  1. 但是,如果对外层变量赋值,由于Python解释器会把x当作函数fn()的局部变量,它会报错:
Copy
def inc(): x = 0 def fn(): # nonlocal x x = x + 1 return x return fn f = inc() print(f()) # 1 print(f()) # 2
  1. 返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
Copy
def count(): fs = [] for i in range(1, 4): def f(): return i*i fs.append(f) return fs f1, f2, f3 = count()

你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果是,全部都是9!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9。

  1. 如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
Copy
def count(): def f(j): def g(): return j*j return g fs = [] for i in range(1, 4): fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f() return fs

偏函数#

将字符串转换为整数的函数int(),将二进制转为整数如下:

Copy
def int2(x, base=2): return int(x, base)

functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:

Copy
>>> import functools >>> int2 = functools.partial(int, base=2) >>> int2('1000000') 64

简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

例子2:

Copy
max2 = functools.partial(max,10) print(max2(5, 6, 7))

相当于

Copy
args = (10, 5, 6, 7) max(*args)

结果为10。

匿名函数#

在Python中,对匿名函数提供了有限支持。还是以map()函数为例,计算f(x)=x2时,除了定义一个f(x)的函数外,还可以直接传入匿名函数:

Copy
>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])) [1, 4, 9, 16, 25, 36, 49, 64, 81]

通过对比可以看出,匿名函数lambda x: x * x实际上就是:

Copy
def f(x): return x * x

关键字lambda表示匿名函数,冒号前面的x表示函数参数。

匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

Copy
>>> f = lambda x: x * x >>> f <function <lambda> at 0x101c6ef28> >>> f(5) 25

同样,也可以把匿名函数作为返回值返回,比如:

Copy
def build(x, y): return lambda: x * x + y * y

装饰器#

  1. 定义:增加函数或类的功能的一个函数。通过装饰器函数,在不修改原函数的前提下,来对函数的功能进行合理的扩充。

举个例子:如何计算函数的执行时间?
如下,你需要计算 add 函数的执行时间。​​​​​​​

Copy
def add2(a, b): res=a+b return res

你可能会这么写​​​​​​​

Copy
def add(a, b): start_time = time.time() res = a + b end_time = time.time() exec_time = end_time - start_time print("add函数,花费的时间是:{}".format(exec_time)) return res

这个时候,老板又让你计算减法函数(sub)的时间。不用装饰器的话,你又得重复写一段减法的代码。

Copy
def sub(a, b): start_time = time.time() res = a - b end_time = time.time() exec_time = end_time - start_time print("add函数,花费的时间是:{}".format(exec_time)) return res

这样显得很麻烦,也不灵活,万一计算时间的代码有改动,你得每个函数都要改动。
所以,我们需要引入装饰器

不带参数的装饰器#

  1. 定义装饰器
Copy
def time_calc(func): @functools.wraps(func) def wrapper(*args, **kargs): # *args 和 **kwargs 表示接受任意数量和类型的参数 start_time = time.time() f = func(*args, **kargs) end_time = time.time() exec_time = end_time - start_time print("花费的时间是:{}".format(exec_time)) return f return wrapper

time_calc是一个装饰器,接受一个函数作为参数,并返回一个函数。借助@语法,把装饰器置于需要增强的函数处
2. 调用装饰器

Copy
@time_calc def add(a, b): res = a + b return res

调用add()函数,不仅会运行函数本身,还会运行装饰器执行的结果。
wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先调用原始函数,再紧接着打印执行时间。

带参数的装饰器#

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:

Copy
def log(text): def decorator(func): def wrapper(*args, **kw): print('%s %s():' % (text, func.__name__)) return func(*args, **kw) return wrapper return decorator

这个3层嵌套的decorator用法如下:

Copy
@log('execute') def now(): print('2015-3-25')

执行结果如下:

Copy
>>> now() execute now(): 2015-3-25

和两层嵌套的decorator相比,3层嵌套的效果是这样的:

Copy
>>> now = log('execute')(now)

我们来剖析上面的语句,首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。

functools.wraps#

以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper'
因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。
不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

Copy
import functools def log(func): @functools.wraps(func) def wrapper(*args, **kw): print('call %s():' % func.__name__) return func(*args, **kw) return wrapper

或者针对带参数的decorator:

Copy
import functools def log(text): def decorator(func): @functools.wraps(func) def wrapper(*args, **kw): print('%s %s():' % (text, func.__name__)) return func(*args, **kw) return wrapper return decorator

内置装饰器#

常见的内置装饰器有三种,@property@staticmethod@classmethod

高阶函数#

1. map()函数#

  • 用法:map(函数名,列表/元组/集合)
  • 说明:map()将传入的函数依次作用到序列的每个元素,处理完后返回的是生成器类型,需要用list生成数据

举例说明,函数f(x)=x2,要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map()实现如下:

Copy
def f(x): return x*x r=map(f,[1,2,3,4,5]) print(list(r)) [1, 4, 9, 16, 25]

举例,把list所有数字转为字符串

Copy
>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])) ['1', '2', '3', '4', '5', '6', '7', '8', '9']

2. reduce()函数#

  • 用法:reduce(函数名,列表)
  • 说明:reduce把函数结果继续和序列的下一个元素做累计计算

举例说明:将序列[1,2,3,4,5]变成整数12345

Copy
from functools import reduce def add(x,y): return x*10+y print(reduce(add,[1,2,3,4,5])) 12345

举例说明:将字符串转化为整数

Copy
from functools import reduce def add(x,y): return x*10+y def char2num(s): digits={'1':1,'2':2,'3':3,'4':4,'5':5} return digits[s] print(reduce(add,map(char2num,'12345'))) 12345
Copy
from functools import reduce DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9} def str2int(s): def fn(x, y): return x * 10 + y def char2num(s): return DIGITS[s] return reduce(fn, map(char2num, s))

3. filter()#

  • 用法:filter(函数名,列表/元组/集合)
  • 说明:实现筛选功能,把传入的函数依次作用到每个元素,根据返回值保留或丢弃该元素。返回True则保留元素,返回False则丢弃该元素。需要用list生成数据

举例说明

Copy
def is_odd(n): return n % 2 == 1 print(list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))) [1, 5, 9, 15]

4. sorted#

  • 用法:可以对列表进行排序
Copy
>>>sorted([36, 5, -12, 9, -21]) [-21, -12, 5, 9, 36]
  • 用法:也可以接收一个key函数实现自定义的排序。
  • 说明:key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序

举例说明:

Copy
# 按绝对值大小排序 >>> sorted([36, 5, -12, 9, -21], key=abs) [5, 9, -12, -21, 36]

举例说明:字符串排序,忽略大小写

Copy
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower) ['about', 'bob', 'Credit', 'Zoo']
  • 要进行反向排序,可以传入第三个参数reverse=True
Copy
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True) ['Zoo', 'Credit', 'bob', 'about']
posted @   是小鱼呀  阅读(154)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
点击右上角即可分享
微信分享提示
目录