Python高级(三):函数式编程
返回函数#
函数作为返回值#
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
调用lazy_sum()时,返回的并不是求和结果,而是求和函数:
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
调用函数f时,才真正计算求和结果:
>>> f()
25
注意:
1.调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数
2.内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为”闭包“
3.返回的函数并没有立刻执行,而是直到调用了f()才执行
闭包#
- 使用闭包,就是内层函数引用了外层函数的局部变量。如果只是读外层变量的值,我们会发现返回的闭包函数调用一切正常:
def inc():
x = 0
def fn():
# 仅读取x的值:
return x + 1
return fn
f = inc()
print(f()) # 1
print(f()) # 1
- 但是,如果对外层变量赋值,由于Python解释器会把x当作函数fn()的局部变量,它会报错:
def inc():
x = 0
def fn():
# nonlocal x
x = x + 1
return x
return fn
f = inc()
print(f()) # 1
print(f()) # 2
- 返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
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。
- 如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
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(),将二进制转为整数如下:
def int2(x, base=2):
return int(x, base)
functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
例子2:
max2 = functools.partial(max,10)
print(max2(5, 6, 7))
相当于
args = (10, 5, 6, 7)
max(*args)
结果为10。
匿名函数#
在Python中,对匿名函数提供了有限支持。还是以map()
函数为例,计算f(x)=x2时,除了定义一个f(x)
的函数外,还可以直接传入匿名函数:
>>> 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
实际上就是:
def f(x):
return x * x
关键字lambda
表示匿名函数,冒号前面的x
表示函数参数。
匿名函数有个限制,就是只能有一个表达式,不用写return
,返回值就是该表达式的结果。
用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x101c6ef28>
>>> f(5)
25
同样,也可以把匿名函数作为返回值返回,比如:
def build(x, y):
return lambda: x * x + y * y
装饰器#
- 定义:增加函数或类的功能的一个函数。通过装饰器函数,在不修改原函数的前提下,来对函数的功能进行合理的扩充。
举个例子:如何计算函数的执行时间?
如下,你需要计算 add 函数的执行时间。
def add2(a, b):
res=a+b
return res
你可能会这么写
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)的时间。不用装饰器的话,你又得重复写一段减法的代码。
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
这样显得很麻烦,也不灵活,万一计算时间的代码有改动,你得每个函数都要改动。
所以,我们需要引入装饰器
不带参数的装饰器#
- 定义装饰器
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. 调用装饰器
@time_calc
def add(a, b):
res = a + b
return res
调用add()
函数,不仅会运行函数本身,还会运行装饰器执行的结果。
wrapper()
函数的参数定义是(*args, **kw)
,因此,wrapper()
函数可以接受任意参数的调用。在wrapper()
函数内,首先调用原始函数,再紧接着打印执行时间。
带参数的装饰器#
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
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用法如下:
@log('execute')
def now():
print('2015-3-25')
执行结果如下:
>>> now()
execute now():
2015-3-25
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
>>> 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的写法如下:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
或者针对带参数的decorator:
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()
实现如下:
def f(x):
return x*x
r=map(f,[1,2,3,4,5])
print(list(r))
[1, 4, 9, 16, 25]
举例,把list所有数字转为字符串
>>> 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
from functools import reduce
def add(x,y):
return x*10+y
print(reduce(add,[1,2,3,4,5]))
12345
举例说明:将字符串转化为整数
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
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生成数据
举例说明
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#
- 用法:可以对列表进行排序
>>>sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
- 用法:也可以接收一个key函数实现自定义的排序。
- 说明:key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序
举例说明:
# 按绝对值大小排序
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
举例说明:字符串排序,忽略大小写
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']
- 要进行反向排序,可以传入第三个参数
reverse=True
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']
作者: 是小鱼呀
出处:https://www.cnblogs.com/sophia12138/p/16533900.html
本站使用「CC BY 4.0」创作共享协议,转载请在文章明显位置注明作者及出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)