Python函数式编程总结
一、生成器generator
1.引出生成器
通过列表⽣成式
,我们可以直接创建⼀个列表
。但是,受到内存限制,列表容量肯定是有限的。⽽且,创建⼀个包含非常多个元素的列表,不仅占⽤很⼤的存储空间,如果我们仅仅需要访问前⾯⼏个元素,那后⾯绝⼤多数元素占⽤的空间都⽩⽩浪费了。
# -*- coding: utf-8 -*-
# @File : 引出生成器.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/20 21:00
# 列表生成式
lst = [i for i in range(10)]
print(lst)
print(type(lst))
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# <class 'list'>
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?接下来,引出我们的
生成器
。
2.创建生成器
- 通过列表生成式创建
# -*- coding: utf-8 -*-
# @File : 创建生成器.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/20 20:59
g = (i for i in range(10))
print(g)
print(type(g))
# <generator object <genexpr> at 0x0000021B19A11D60>
# <class 'generator'>
这样就不必创建完整的list
,从⽽节省⼤量的空间。在Pytho
n中,这种⼀边循环⼀边计算的机制,称为⽣成器:generator
。创建
列表
和生成器
的区别仅在于最外层的[ ]
和( )
,lst
是⼀个列表,⽽g
是⼀个⽣成器。我们可以直接打印出lst
的每⼀个元素,但我们怎么打印出g
的每⼀个元素呢?如果要⼀个⼀个打印出来,可以通过next()
函数获得⽣成器的下⼀个返回值:
- 通过
next()
函数获得⽣成器的下⼀个返回值
# -*- coding: utf-8 -*-
# @File : 创建生成器.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/20 20:59
lst = [i for i in range(10)]
g = (i for i in range(10))
print(g)
print(type(g))
print(next(g)) # 0
print(next(g)) # 1
print(next(g)) # 2
print(next(g)) # 3
print(next(g)) # 4
print(next(g)) # 5
print(next(g)) # 6
print(next(g)) # 7
print(next(g)) # 8
print(next(g)) # 9
print(next(g))
⽣成器保存的是算法,每次调⽤next(g)
,就计算出g
的下⼀个元素的值,直到计算到最后⼀个元素,没有更多的 元素时,抛出StopIteration
的异常。当然,这种不断调⽤
next()
实在是太繁琐了,虽然是点一次出现一次,但正 确的⽅法是使⽤for
循环,因为⽣成器也是可迭代对象。
- 也可以通过
for-in
循环打印出来
# -*- coding: utf-8 -*-
# @File : 创建生成器.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/20 20:59
g = (i for i in range(10))
print(g)
print(type(g))
# print(next(g)) # 0
# print(next(g)) # 1
# print(next(g)) # 2
# print(next(g)) # 3
# print(next(g)) # 4
# print(next(g)) # 5
# print(next(g)) # 6
# print(next(g)) # 7
# print(next(g)) # 8
# print(next(g)) # 9
# print(next(g))
for i in g:
print(i)
所以,我们创建了⼀个⽣成器后,基本上永远不会调⽤next()
,⽽是通过for
循环来迭代它,并且不需要关心StopIteration
异常。
generator
非常强大。如果推算的算法比较复杂,用类似列表生成式的for
循环无法实 现的时候,还可以用函数来实现。比如,著名的斐波拉契数列Fibonacci
,除第一个和第二个数外,任意一个 数都可由前两个数相加得到:1, 1, 2, 3, 5, 8, 13, 21, 34, ...
斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
代码如下
# -*- coding: utf-8 -*-
# @File : 函数写斐波那契数列.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/20 21:14
# 定义一个斐波那契函数
def fib(times):
# 初始化
n = 0
a, b = 0, 1
while n < times:
print(b)
a, b = b, a+b
n += 1
fib(6)
仔细观察,可以看出,fib
函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator
。也就是说,上面的函数离
generator
仅一步之遥。要把fib
函数变 成generator
,只需要把print(b)
改为yield(b)
就可以了。
- 通过
yield
关键字创建
yield
一般用于创建生成器:工作后返回变量值给生成器。
# -*- coding: utf-8 -*-
# @File : yield创建生成器.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/20 21:17
def fib(times):
# 初始化
n = 0
a, b = 0, 1
while n < times:
yield b
a, b = b, a+b
n += 1
f = fib(6)
print(f)
# <generator object fib at 0x00000197C5E56350>
# f 是一个生成器对象
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))
print(next(f))
在上⾯fib
的例⼦,我们在循环过程中不断调⽤yield
,就会不断中断。当然要给循环设置⼀个条件来退出循环,不然就会产⽣⼀个⽆限数列出来。同样的,把函数改成generator
后,我们基本上从来不会⽤next()
来获取下⼀个返 回值,⽽是直接使⽤for
循环来迭代:
# -*- coding: utf-8 -*-
# @File : yield创建生成器.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/20 21:17
def fib(times):
# 初始化
n = 0
a, b = 0, 1
while n < times:
yield b
a, b = b, a+b
n += 1
f = fib(6)
print(f)
# <generator object fib at 0x00000197C5E56350>
# f 是一个生成器对象
for i in f:
print(i)
3.遍历生成器
- 通过
for- in
循环打印 - 通过
next()
函数,已经遍历到生成器的结尾抛异常 objict
内置的__next__()
方法,已经遍历到生成器的结尾抛异常send()
方法,生成器的第一个值必须是send(None)
,后面没有限制
# -*- coding: utf-8 -*-
# @File : 遍历生成器.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/20 21:24
# 创建一个生成器g
g = (i for i in range(10))
# 1.使用next(g),当已经遍历到生成器的结尾抛异常
print(next(g)) # 0
print(next(g)) # 1
# 2.object内置的__next__:当已经遍历到生成器的结尾抛异常
print(g.__next__()) # 2
print(g.__next__()) # 3
# 3.send 函数,生成器第一个值必须是send(None),后面的值没有限制
print(g.send(None)) # 4
print(g.send('')) # 5
print(g.send(1)) # 6
# 4.使用for-in循环
for i in g:
print(i)
# 7
# 8
# 9
4.生成器总结
⽣成器
是这样⼀个函数,它记住上⼀次返回时在函数体中的位置。对⽣成器函数的第⼆次(或第 n 次)调⽤跳转⾄该函数中间,⽽上次调⽤的所有局部变量都保持不变。⽣成器不仅
记住
了它数据状态;⽣成器还记住
了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。
生成器的特点:
- 节约内存
- 迭代到下⼀次的调⽤时,所使⽤的参数都是第⼀次所保留下的,在整个所有函数调⽤的参数都是第⼀次所调⽤时保留的,⽽不是新创建的。
二、迭代器iterator
1.什么是迭代器
迭代器是访问集合的一种方式,可以记住遍历位置的对象,迭代器从集合的第一个元素开始访问,直到所有的元素被访问完才结束,只能往往前,不能后退。
可以直接作用于for
循环的数据类型有以下几种:
- 一类是集合数据类型:如:
list
,tuple
,dict
,set
,str
等;
a = (1,) # 元组
b = [1, 2] # 列表
c = {} # 空字典
d = () # 元组
s = set()
s1 = {None} # 集合
print(type(c)) # 空集合 <class 'dict'>
print(type(d)) # <class 'tuple'>
print(type(s)) # 空集合 <class 'set'>
- 一类是
generator
,包括生成器和yield
关键字的生成器函数generator function
。
⽣成器不但可以作⽤于for
循环,还可以被next()
函数不断调⽤并返回下⼀个值,直到最后抛出StopIteration
错误表示⽆法继续返回下⼀个值了。
这些可以直接作用于for
循环的对象统称为可迭代对象
:Iterable
.
2.Iterable
可迭代对象判断
可以使用instance()
判断一个对象是否是Iterable
对象。
instance()
函数
def isinstance(x, A_tuple): # real signature unknown; restored from __doc__
"""
Return whether an object is an instance of a class or of a subclass thereof.
(返回一个对象是类的实例还是类的子类。)
A tuple, as in ``isinstance(x, (A, B, ...))``, may be given as the target to
check against. This is equivalent to ``isinstance(x, A) or isinstance(x, B)
or ...`` etc.
(一个元组,如' ' isinstance(x, (A, B,…))' ',可以被指定为目标
核对。这相当于' ' isinstance(x, A)或isinstance(x, B)
or ...`` etc.)
"""
pass
代码实现:
# -*- coding: utf-8 -*-
# @File : iterable对象判断.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/20 22:09
from collections.abc import Iterable
print(isinstance([1], Iterable)) # True
print(isinstance({0, 1}, Iterable)) # True
print(isinstance((1, ''), Iterable)) # True
print(isinstance({1: 10}, Iterable)) # True
print(isinstance((i for i in range(10)), Iterable)) # True
print(isinstance(10, Iterable)) # False
可以使⽤isinstance()
判断⼀个对象是 否是Iterable
对象,这里就产生一个疑问了,生成器都是Iterator
对象,那么list
、dict
、str
是不是Iterator
?为什么?。
3.Iterator
迭代器判断
# -*- coding: utf-8 -*-
# @File : iterator迭代器判断.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/20 22:40
from collections.abc import Iterator
print(isinstance((i for i in range(10) if i % 2 == 0), Iterator)) # True
print(isinstance([], Iterator)) # False
print(isinstance({}, Iterator)) # False
print(isinstance('abc', Iterator)) # False
可以得出list
、dict
、str
不是Iterator
,因为Pytho
n的Iterator
对象表示的是一个数据流,Iterator
对象可以 被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误。可以把这个数据流看做 是一个有序序列,但我们却不能提前知道序列的长度,
只能
不断通过next()
函数实现按需计算下一个数据,所以Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator
甚至可以表示一个无限大的数据流,例如全体自然数。而使用list
是永远不可能存储全体自然数的 那我们还可以通过isinstance()
来判断是否是Iterator
对象
注意: Iterator
和 Iterable
,一个是迭代器,一个是可迭代对象
但是可以使用iter()
函数将list
、dict
、str
等Iterable
变成Iterator
。
iter()
函数.py
# -*- coding: utf-8 -*-
# @File : iter()函数.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/20 22:46
from collections.abc import Iterator
print(isinstance(iter([]), Iterator)) # True
print(isinstance(iter({}), Iterator)) # True
print(isinstance(iter('abc'), Iterator)) # True
4.迭代器与可迭代对象
所有可以作用于for
循环的对象都是Iterable
可迭代对象类型;可以作用于
next()
函数的对象都是itreator
迭代器类型,他们表示一个惰性计算序列;集合数据类型
list
,dict
,str
等是Iterable
但不是Iterat
or,不过可以通过iter()
函数获得一个Iterator
对象。
# -*- coding: utf-8 -*-
# @File : 迭代器和可迭代对象.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/20 23:00
# 迭代器 可以被next()函数调⽤并不断返回下⼀个值的对象称为迭代器:Iterator。
from collections.abc import Iterator, Iterable
# iterable 可迭代对象
# iterator 迭代器
a = (1,) # 元组
b = [1, 2] # 列表
c = {} # 空字典
d = () # 元组
s = set()
s1 = {None} # 集合
print(type(c)) # 空集合 <class 'dict'>
print(type(d)) # <class 'tuple'>
print(type(s)) # 空集合 <class 'set'>
# isinstance判断
# 可作用于for循环的对象都是iterable类型
def fun(args):
if isinstance(args, Iterable):
print(f'{args}对象是可迭代对象')
else:
print(f'{args}对象不是可迭代对象')
fun(a) # 函数调用
fun(b)
fun(c)
fun(d)
fun(s)
print('---------------------------')
# 渴作用与next()函数的对象都是iterator类型,他们表示一个惰性计算的序列
def fun1(args):
if isinstance(args, Iterator):
print(f'{args}对象是可迭代器')
else:
print(f'{args}对象不是可迭代器')
g = (i for i in range(10))
fun1(g)
print(next(g))
print(next(g))
print(next(g))
# 用python内置函数iter()函数 把list dict str等 iterable变成iterator迭代器
fun1(iter(a))
fun1(iter(b))
三、匿名函数lambda
匿名函数lambda
:顾名思义,没有名字的函数,可以将其赋值给一个变量。
语法:lambda [list]: 表达式
参数介绍:
"""
[list]:表示参数列表,
注意:参数与表达式之间需要冒号来区分
表达式 :表达式方法非常多,表达形式也非常多
返回值 :为表达式的结果value
"""
代码实现
# -*- coding: utf-8 -*-
# @File : 匿名函数lambda.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 10:22
fun1 = lambda a, b: x + y
x = int(input('x='))
y = int(input('y='))
print('x+y=', fun1(x, y))
fun2 = lambda a, b: a ** b
f = fun2(x, y)
print(type(fun2))
print(f"a**b= {f}")
def add(a, b):
print('add=', a + b)
add(x, y)
四、高阶函数Higher-order function
1.函数式编程
函数
是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。而
函数式编程
——Functional Programming
,虽然也可以归结到面向过程的程序设计,但其思想更接近抽象的计算。我们首先要搞明白计算机(
Computer
)和计算(Compute
)的概念。
在计算机的层次上,CPU
执行的是加减乘除的指令代码,以及各种条件判断和跳转指令,所以,汇编语言
是最贴近计算机的语言。
而计算则指数学意义上的计算,越是抽象的计算,离计算机硬件
越远。对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如
C
语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Python
语言。函数式编程就是一种抽象程度很高的编程范式。
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
Python
对函数式编程提供了部分支持。由于Python
允许使用变量,因此,Python
不是纯函数式编程语言。
- 变量可以指向函数
例如Python内置求绝对值函数abs()
>>> abs(-5)
5
>>> f = abs
>>> f
<built-in function abs>
可以看见,只写abs
只是函数本身,而abs(-5)
才是函数调用,实际我们如果需要函数的结果,可以将函数赋值给变量
例如:
>>> i = abs(-5)
>>> i
5
>>> f = abs
>>> f
<built-in function abs>
>>> f(-5)
5
将调用函数的结果,赋值给变量i
,这样变量就可以打印出结果,如果将函数本身赋值给变量f
,那么变量也拥有这个函数的功能,这个变量将指向这个函数,使用变量f ()
来调用函数和直接调用abs()
效果是一样的。
- 函数名也可以是变量
函数是由def
定义,函数名,括号,括号参数,冒号,函数体组成,那么函数名是什么呢,可以发现, 函数名是指向函数的变量,例如abs()
这个函数,可以将abs
看成变量,它指向一个可以求绝对值的函数,如果把abs
指向其他的对象,例如我们给abs
赋值,那看看还会指向求绝对值的函数吗
>>> abs = 5
>>> abs(-5)
Traceback (most recent call last):
File "<pyshell#23>", line 1, in <module>
abs(-5)
TypeError: 'int' object is not callable
>>> abs
5
TypeError: 'int' object is not callable
提示,类型错误,int
类型是不可以被调用的,我们看到,abs
这个变量被赋值5
,然后使用abs(-5)
调动函数,发现异常,此时abs
变量指向的不是函数,而是一个int
类型的5
,实际上,我们工作或是开发中写代码,是不能这么写的,由此可以看出函数名其实就是变量。
注意:由于 abs
函数实际上是定义在 import builtins
模块中的,所以要让修改 abs
变量的指向在其它模块也生效可以使用。
import builtins
builtins.abs = 10
2.引出高阶函数
上面的例子,函数可以传参数,而函数名可以做变量,那我们函数里面的参数也可以为函数名。
代码中的total
为高阶函数
# -*- coding: utf-8 -*-
# @File : 引出高阶函数.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 10:00
def fun(i):
return i * 2
def total(x, y, fun):
return fun(x) + fun(y)
add_sum = total(1, 2, fun)
print(add_sum) # 6
下面代码test
称为高阶函数
# -*- coding: utf-8 -*-
# @File : 高阶函数.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 10:12
def fac(n): # 定义一个递归函数计算阶乘
if n == 1: # 递归终止条件
return 1
else:
# 在test()函数的函数体内调用该本身
return n*fac(n-1) # 递归调用
def test(list_, fun): # 将函数fac本身作为参数传进来,test称为高阶函数
new_list = []
for x in list_:
new_list.append(fun(x))
print(new_list)
ls = [1, 2, 3, 4, 5, 6, 7]
test(ls, fac) # 调用函数test 并把参数lst和fac传入
3.Python内置高阶函数
# -*- coding: utf-8 -*-
# @File : 内置高阶函数.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 14:10
from functools import reduce
'''
1.map函数:把一个可迭代对象中的每个元素转换为一个新的对象,最后返回一个新的可迭代对象'''
# map(fun, iterables)
lst = [1, 2, 3, 4, 5]
ite = map(lambda x: x ** 2, lst)
print(ite) # <map object at 0x000002A24AF06970>
for i in ite:
print(i, end=' ') # 1 4 9 16 25
'''
2.reduac() 把一个可迭代对象中的每个元素都做聚合处理,返回一个聚合之后的值
from functools import reduce
reduce(function, sequence[, initial]) -> value'''
# function:一共有两个参数的函数
# sequence:是一个序列,是一些数据的集合,或者是一组数据,可迭代对象
# initial:可选,初始参数 返回值:返回函数计算的结果
va = reduce(lambda x, y: x + y, lst) # 求累加
print('\ns =', va) # s = 15
def max_(x, y):
if x > y:
return x
max1 = reduce(max_, iter((3, 2))) # 求最大值
print(max1) # 3
'''
3.filter 把一个可迭代对象中的元素做过滤操作,若果func返回值为True是留下,否则过滤掉'''
staff = [
{'name': '张三', 'age': 18, 'salary': 2000},
{'name': '李四', 'age': 20, 'salary': 4000},
{'name': '麻子', 'age': 22, 'salary': 6000}]
# 过滤留下大于18岁的员工
lst_age = filter(lambda x: x['age'] > 18, staff)
print('大于18岁的员工:', list(lst_age))
# 工资大于4000的员工
lst_salary = filter(lambda x: x['salary'] > 4000, staff)
print('工资大于4000的员工:', list(lst_salary))
'''
4.max 和 min'''
# 计算最大工资的员工
print('最高工资的员工:', max(staff, key=lambda x: x['salary']))
# 计算最小年龄的员工
print('最低工资的员工:', min(staff, key=lambda x: x['age']))
'''
5.sorted把一个迭代对象里的每个元素做排序,最终返回一个列表'''
# 根据员工年龄降序排序
list_sorted = sorted(staff, key=lambda x: x['age'], reverse=True)
print('工资降序排序:', list_sorted)
4.map
函数
map()
函数,把一个可迭代对象中的每一个元素换成一个新的对象,最终返回一个迭代器
。
Python内置map()
函数:
class map(object):
"""
map(func, *iterables) --> map object
Make an iterator that computes the function using arguments from
each of the iterables. Stops when the shortest iterable is exhausted.
"""
def __getattribute__(self, *args, **kwargs): # real signature unknown
""" Return getattr(self, name). """
pass
def __init__(self, func, *iterables): # real signature unknown; restored from __doc__
pass
def __iter__(self, *args, **kwargs): # real signature unknown
""" Implement iter(self). """
pass
@staticmethod # known case of __new__
def __new__(*args, **kwargs): # real signature unknown
""" Create and return a new object. See help(type) for accurate signature. """
pass
def __next__(self, *args, **kwargs): # real signature unknown
""" Implement next(self). """
pass
def __reduce__(self, *args, **kwargs): # real signature unknown
""" Return state information for pickling. """
pass
map()
函数的返回值:
map(func, *iterables) --> map object
参数详解:
"""
func:代表传入参数为函数,这里的函数指定指向函数的函数名
*iterables:代表参数指定的可迭代的
返回值:返回处理好的数据
map()函数:是将传入的func函数作用于可迭代的数据里的面每个元素,并将处理好的新的结果返回
"""
代码实现
# -*- coding: utf-8 -*-
# @File : map函数.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 10:03
lst = [1, 2, 3, 4, 5]
ite = map(lambda x: x**2, lst)
print(ite) # <map object at 0x000002A24AF06970>
for i in ite:
print(i, end=' ')
5.聚合函数reduce
reduce()
函数,把一个可迭代对象中的每个元素做聚合处理,最终返回一个聚合之后的值.
functools
函数reduce()
def reduce(function, sequence, initial=_initial_missing):
"""
reduce(function, sequence[, initial]) -> value
Apply a function of two arguments cumulatively to the items of a sequence,
from left to right, so as to reduce the sequence to a single value.
For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
((((1+2)+3)+4)+5). If initial is present, it is placed before the items
of the sequence in the calculation, and serves as a default when the
sequence is empty.
"""
it = iter(sequence)
if initial is _initial_missing:
try:
value = next(it)
except StopIteration:
raise TypeError("reduce() of empty sequence with no initial value") from None
else:
value = initial
for element in it:
value = function(value, element)
return value
try:
from _functools import reduce
except ImportError:
pass
reduce
函数的参数与返回值:
注意使用reduce
函数时需要先导入,reduce
函数是在 functools
模块里面
from functools import reduce
reduce(function, sequence[, initial]) -> value
# 参数详解
"""
function:一个有两个参数的函数
sequence:是一个序列,是一些数据的集合,或者是一组数据,可迭代对象
initial:可选,初始参数
返回值:返回函数计算的结果
reduce()函数,使用function函数(有两个参数)先对集合中的sequence第 1、2 个元素进行操作,如果存在
initial参数,则将会以sequence中的第一个元素和initial作为参数,用作调用,得到的结果再与sequence中的 下一个数据用 function 函数运算,最后得到一个结果。
"""
代码实现:
# -*- coding: utf-8 -*-
# @File : 聚合函数reduce.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 12:48
from functools import reduce
lst = [1, 2, 3, 4, 5]
va = reduce(lambda x, y: x+y, lst) # 求累加
print('s =', va)
def max_(x, y):
if x > y:
return x
max1 = reduce(max_, iter((3, 2))) # 比较大小求大值
print(f'max = {max1}')
6.过滤器filter
函数
filter()
函数 把一个可迭代对象中的元素做过滤操作,如果func
返回值为True
则留下,否则过滤掉。
Python内置的filter()
函数用于过滤序列,和map()
类似,filter()
也接收一个函数和一个序列,但是不同的是filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定元素的保留与丢弃。
Python内置函数filter()
class filter(object):
"""
filter(function or None, iterable) --> filter object
Return an iterator yielding those items of iterable for which function(item)
is true. If function is None, return the items that are true.
"""
def __getattribute__(self, *args, **kwargs): # real signature unknown
""" Return getattr(self, name). """
pass
def __init__(self, function_or_None, iterable): # real signature unknown; restored from __doc__
pass
def __iter__(self, *args, **kwargs): # real signature unknown
""" Implement iter(self). """
pass
@staticmethod # known case of __new__
def __new__(*args, **kwargs): # real signature unknown
""" Create and return a new object. See help(type) for accurate signature. """
pass
def __next__(self, *args, **kwargs): # real signature unknown
""" Implement next(self). """
pass
def __reduce__(self, *args, **kwargs): # real signature unknown
""" Return state information for pickling. """
pass
参数列表:
filter(function, iterable)
"""
function:判断函数。
iterable:序列,(可迭代对象)。
返回值:返回列表
filter函数,序列(可迭代对象)的每个元素作为参数传递给函数进行判断,然后返回 True 或 False,最后将返 回 True 的元素放到新列表中
"""
filter
函数实现过滤奇数:
# -*- coding: utf-8 -*-
# @File : filter函数.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 13:06
def not_odd(num):
return num % 2 == 0
# 过滤奇数
new_lst = filter(not_odd, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
print(list(new_lst))
这里定义了一个函数not_odd
,不是奇数的函数,这个函数,只有当参数为2的整数倍时返回True
。这里
filter
函数的两个参数第一个是过滤方法,第二个是需要过滤的列表,将列表里面的元素依次带入函数中进行运算,得到的结果如果为True
时,将此结果作为新的filter
对象保留,等待函数里面的列表执行完成后,返回最终的值,这里的值为列表
,也就是过滤掉了False
的数据或元素。
filter
函数过滤操作
# -*- coding: utf-8 -*-
# @File : filter函数.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 13:06
# filter 把一个可迭代对象中的元素做过滤操作,若果func返回值为True是留下,否则过滤掉
staff = [
{'name': '张三', 'age': 18, 'salary': 2000},
{'name': '李四', 'age': 20, 'salary': 4000},
{'name': '麻子', 'age': 22, 'salary': 6000}]
# 过滤留下大于18岁的员工
lst_age = filter(lambda x: x['age'] > 18, staff)
print(list(lst_age))
# 过滤留下工资大于4000的员工
lst_salary = filter(lambda x: x['salary'] > 4000, staff)
print(list(lst_salary))
五、返回函数
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
看代码:
# -*- coding: utf-8 -*-
# @File : 返回函数的高阶函数.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 14:48
def sum_fun(*args):
def add_fun():
s = 0
for i in args:
s += i
return s
return add_fun
f = sum_fun(1, 2, 3, 4, 5, 6, 7, 8, 9)
add = f()
print(add) # 45
当我们调用 sum_fun
时,返回的并不是求和结果,而是求和函数 add_fun
, 将其赋值给f
,当我们在调f
函数时才返回求和结果s
。
用filter
函数来计算素数
用filter
来计算素数其中一个方法是埃氏筛法。
给出要筛数值的范围n
,找出以内的素数。先用2
去筛,即把2
留下,把2
的倍数剔除掉;再用下一个质数,也就是3
筛,把3
留下,把3
的倍数剔除掉;接下去用下一个质数5
筛,把5
留下,把5
的倍数剔除掉;不断重复下去......
用Python
高阶函数来实现这个算法:
- 我们先写一个生成器构造一个从3开始的无限奇数序列,首先排除偶数。
def odd_num(): # 奇数生成器函数
n = 1
while True:
n += 2
yield n
- 写一个筛选的函数,这里使用了匿名函数,返回判断是否为可整除数
def un_divisible(n): # 判断是否为可整除数
return lambda x: x % n > 0
- 使用
filter
来过滤,不断返回素数的生成迭代
def primes(): # 素数生成器函数
yield 2
it = odd_num()
while True:
n = next(it)
yield n
it = filter(un_divisible(n), it) # 过滤出不可以整除的数
- 判断素数方法就产生了,这里需要手动结束一下
for i in primes(): # 打印小于100的素数
if i < 100:
print(i)
else:
break
高阶函数实现打印小于100的素数:
# -*- coding: utf-8 -*-
# @File : 返回函数计算质数.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 15:17
# 得到所有的质数,打印小于100的所有的质数
# 最小的质数是2, 质数:只能被1和它本身整除的数
# 思路:先得到所有大于1的奇数, --> 生成器,在把生成器中的所有元素过滤去掉: 那些可以被小于元素本身的质数整除的数
# 1.得到所有的大于1奇数的生
def odd_num():
n = 1
while True:
n += 2
yield n
def un_divisible(n): # 判断是否能够整除的函数,n代表从生成器中拿到的一个大于1的质数
return lambda x: x % n > 0 # x是某一个奇数,n:小于当前x的一个质数
# 2.创建一个质数的生成器,最小的质数是2
def primes():
yield 2
n = odd_num() # g为大于1的奇数生成器
while True:
x = next(n)
g = filter(un_divisible(n), n)
yield x
for i in primes():
if i < 100:
print(i, end=' ')
else:
break
第一段代码生成了以3开始的奇数序列
第二段代码自定义过滤函数,包含匿名函数,判断值的取余是否能被整除
第三段代码用来返回素数,这里先返回一个2为素数,因为偶数都被排除了所
这就是100以内的所有素数:
六、闭包
1.什么是闭包
什么是闭包
:一个函数定义中引入了函数定义以外的变量,并且该函数可以在其定义以外被执行,这个函数以及引用到外部的变量称为闭包。
闭包的三个条件,缺一不可
"""
1)必须有一个内嵌函数(函数里定义的函数)——这对应函数之间的嵌套
2)内嵌函数必须引用一个定义在闭合范围内(外部函数里)的变量——内部函数引用外部变量
3)外部函数必须返回内嵌函数——必须返回那个内部函数
"""
闭包代码实现:
# -*- coding: utf-8 -*-
# @File : 闭包.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 15:58
# 定义一个函数
def fun_a(num_a):
# 在函数内部再定义⼀个函数
# 并且这个内部函数⽤到了外部的变量,这个函数以及⽤到外部函数的变量及参数叫 闭包
def fun_b(num_b):
print('内嵌函数fun_b的参数是:%s,外部函数fun_a的参数是:%s' % (num_b, num_a))
return num_a + num_b
# 这里返回的就是闭包的结果
return fun_b
# 给fun_a函数赋值,这个10就是传参给fun_a
ret = fun_a(10)
# 注意这里的10其实是赋值给fun_b
print(ret(10))
# 注意这里的90其实是赋值给fun_b
print(ret(90))
此时,内部函数对外部函数作⽤域⾥变量的引⽤(⾮全局变量),则称内部函数为闭包。
2.闭包修改外部变量
python
交互环境idle
>>> def counter(start = 0):
count = [start]
def incr():
count[0] += 1
return count[0]
return incr
>>> c1 = counter(5)
>>> print(c1())
6
>>> print(c1())
7
>>> print(c2())
51
>>> print(c2())
52
>>>
当一个函数在本地作用域找不到变量申明时会向外层函数寻找,这在函数闭包中很常见但是在本地作用域中使用的变量后,还想对此变量进行更改就会报错。
看一段代码:
# -*- coding: utf-8 -*-
# @File : 闭包修改外部变量.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 16:30
# 闭包修改外部变量的值
def test1():
c = 1
# c不是局部变量,是介于局部变量和全局变量之间的一种变量,用 nonlocal标识
def add1():
print(c) # 1
c += 1
return c # 2
return add1
print(test1()())
报错信息:
此时,如果我在函数内加一行nonlocal c
就可解决这个问题
代码:
# -*- coding: utf-8 -*-
# @File : 闭包修改外部变量.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 16:30
# 闭包修改外部变量的值
def test1():
c = 1
# c不是局部变量,是介于局部变量和全局变量之间的一种变量,用 nonlocal标识
def add1():
nonlocal c
print(c) # 1
c += 1
return c # 2
return add1
print(test1()())
nonlocal
声明的变量不是局部变量,也不是全局变量,而是外部嵌套函数内的变量(介于局部变量和全局变量之间的一种变量)。
使用
nonlocal
的好处是,在为函数添加状态时不用额外地添加全局变量,因此可以大量地调用此函数并同时记录着多个函数状态,每个函数都是独立、独特的。
3.闭包的应用
闭包实现 y = a*x + b
# -*- coding: utf-8 -*-
# @File : 闭包的应用.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 16:18
# y = a*x + b
def create_line(a, b):
def line(x):
return a * x + b
return line
line1 = create_line(1, 1) # a:1 b:1
line2 = create_line(4, 5) # a:4 b:5
print(line1(5)) # 6
print(line2(5)) # 25
从这段代码中,函数line
与变量a,b
构成闭包。在创建闭包的时候,我们通过create_line
的参数a,b
说明了这两个变量的取值,这样,我们就确定了函数的最终形式(y = x + 1
和y = 4x + 5
)。我们只需要变换参数a,b
,就可以获得不同的直线表达函数。由此,我们可以看到,闭包也具有提⾼代码可复⽤性的作⽤。
如果没有闭包,我们需要每次创建函数的时候同时说明
a,b,x
。这样,我们就需要更多的参数传递,也减少了代码的可移植性。
1.闭包优化了变量,原来需要类对象完成的⼯作,闭包也可以完成
2.由于闭包引⽤了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存
4.闭包的陷阱
函数内部函数,引用外部函数参数或值,进行内部函数运算执行,并不是完全返回一个函数,也有可能是一个在外部函数的值,我们还需要知道返回的函数不会立刻执行,而是直到调用了函数才会执行。
看代码:
# -*- coding: utf-8 -*-
# @File : 闭包的陷阱.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 17:09
def fun_a():
fun_list = []
for i in range(1, 4):
def fun_b():
return i * i
fun_list.append(fun_b)
return fun_list
f1, f2, f3 = fun_a()
print(f1(), f2(), f3())
# 9 9 9
这里创建了一个fun_a
函数,外部函数的参数fun_list
定义了一个列表,在进行遍历,循环函数fun_b
,引用外部变量i 计算返回结果,加入列表,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了,但是实际结果并不是我们想要的1
,4
,9
,而是9
,9
,9
,这是为什么呢?这是因为,返回的函数引用了变量
i
,但不是立刻执行。等到3
个函数都返回时,它们所引用的变量i
已经变成了3
,每一个独立的函数引用的对象是相同的变量,但是返回的值时候,3
个函数都返回时,此时值已经完整了运算,并存储,当调用函数,产生值不会达成想要的,返回函数不要引用任何循环变量,或者将来会发生变化的变量,但是如果一定需要呢,如何修改这个函数呢?
我们fun_b(
)把这里的参数i
赋值给x
就可以解决
# -*- coding: utf-8 -*-
# @File : 闭包的陷阱.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 17:09
def fun_a():
fun_list = []
for i in range(1, 4):
def fun_b(x=i):
return x ** 2
fun_list.append(fun_b)
return fun_list
f1, f2, f3 = fun_a()
print(f1(), f2(), f3())
# 1 4 9
可以再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变,那我们就可以完成下面的代码:
# -*- coding: UTF-8 -*- #
def fun_a():
def fun_c(i):
def fun_b():
return i * i
return fun_b
fun_list = []
for i in range(1, 4):
# f(i)立刻被执行,因此i的当前值被传入f()
fun_list.append(fun_c(i))
return fun_list
f1, f2, f3 = fun_a()
print(f1(), f2(), f3())
# 1 4 9
七、装饰器
1.什么是装饰器
先看一段代码:
# -*- coding: utf-8 -*-
# @File : 什么是装饰器.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 19:38
def fun():
print('洗脸')
fun() # 调用fun函数
# 洗脸
# 想给fun函数增加 起床 和 吃早点 这两个功能
def test1(func):
def test2():
print('起床')
func()
print('吃早点')
return test2
test1(fun)()
# 起床
# 洗脸
# 吃早点
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。也可以将函数赋值变量,做参传入另一个函数。
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值 也是一个函数对象。它经常用于有以下场景,比如:
插入日志
、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。
2.装饰器的作用
装饰器的作用:为已经存在的对象添加额外的功能
看代码代码:
# -*- coding: utf-8 -*-
# @File : 装饰器.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 19:46
def test1(func):
def test2():
print('起床')
func()
print('吃早点')
return test2
@test1 # 装饰器
def fun():
print('洗脸')
fun()
# 起床
# 洗脸
# 吃早点
我们没有直接将fun
函数作为参数传入test1
中,只是将test1
函数以@方式装饰在fun
函数上。也就是说,被装饰的函数,函数名作为参数,传入到装饰器函数上,不影响
fun
函数的功能,再此基础上可以根据业务或者功能增加条件或者信息。
(注意:@
在装饰器这里是作为Python
语法里面的语法糖写法,用来做修饰。)
但是我们这里就存在一个问题,这里引入魔术方法__name__
,这是属于python
中的内置类属性,它天生就存在于一个python
程序中,代表对应程序名称,一般一段程序作为主线运行程序时其内置名称就是__main__
,当自己作为模块被调用时就是自己的名字。
print(fun.__name__)
# test2
这并不是我们想要的!输出应该是fun
,这里的函数被test2
替代了。它重写了我们函数的名字和注释文档,那怎么阻止变化呢,Python提供functools
模块里面的wraps
函数解决了问题。
代码实现:
# @Time : 2022/8/21 20:04
from functools import wraps
def test1(func):
@wraps(func) # 使用fun来包装test2
def test2():
print('起床')
func()
print('吃早点')
return test2
@test1 # 装饰器
def fun():
print('洗脸')
fun()
print(fun.__name__) # fun
我们在装饰器函数内,作用fun
的test2
函数上也增加了一个装饰器wraps
还是带参数的,这个装饰器的功能就是不改变使用装饰器原有函数的结构。
3.装饰器记录日志功能
为work()
函数增加记录日志功能
# -*- coding: utf-8 -*-
# @File : 装饰器实现记录日志功能.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 20:09
import time
from functools import wraps
def log(fun):
@wraps(fun)
def write_log():
print("[info]--时间是:%s" % time.strftime("%Y-%m-%d %H:%M:%S"))
fun()
return write_log
@log
def work():
print("我在学习Python")
work()
4.带参装饰器
我们也看到装饰器
wraps
也是带参数的,那我们是不是也可以定义带参数的装饰器呢,我们可以使用一个函数来包裹装饰器,调入这个参数。
# -*- coding: utf-8 -*-
# @File : 带参数的装饰器.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 21:01
import time
from functools import wraps
def logs(func):
@wraps(func)
def write_log(*args, **kwargs):
print('[info]--时间:%s' % time.strftime('%Y-%m-%d %H:%M:%S'))
func(*args, **kwargs)
return write_log
@logs
def work():
print('我在工作')
@logs
def work2(name1, name2):
print('%s和%s在工作' % (name1, name2))
work2('张三', '李四')
# [info]--时间:2022-06-24 18:04:04
# 张三和李四在工作
5.函数做装饰器
把日志写入文件
# -*- coding: utf-8 -*-
# @File : 把日志写入文件.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 21:14
import time
from functools import wraps
def logger(log_file):
def logs(fun):
@wraps(fun)
def write_log(*args, **kwargs):
log = "[info]--时间是:%s" % time.strftime('%Y-%m-%d %H:%M:%S')
print(log)
with open(log_file, 'a+') as f:
f.write(log+'\n')
fun(*args, **kwargs)
return write_log
return logs
@logger('work.log') # 使用装饰器来给 work函数增加记录日志的功能
def work(name1, name2): # 1.当前 work可能有多个参数 2.自定义日志文件的名字和位置,记录日志级别
print(f"{name1}和{name2}正在工作")
work('张三', '李四')
终端输出:work.log
文件记录日志
这里我们将带参数的带入进去根据代码流程执行生成了文件并将日志信息打印进去现在我们有了能用于正式环境的
logger
装饰器,但当我们的应用的某些部分还比较脆弱时,异常也许是需要更紧急关注的事情。
6.类做装饰器
# -*- coding: utf-8 -*-
# @File : 把日志写入文件.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 21:14
import time
from functools import wraps
class Log(object):
def __init__(self, log_file, level="INFO"):
self.log_file = log_file
self.level = level
def __call__(self, fun): # 定义装饰器,需要有一个接受函数
@wraps(fun)
def write_log(*args, **kwargs):
log = "[%s]--时间是:%s" % (self.level, time.strftime('%Y-%m-%d %H:%M:%S'))
print(log)
with open(self.log_file, 'a+') as f:
f.write(log+'\n')
fun(*args, **kwargs)
return write_log
@Log('work3.png')
def learn(name):
print(f"{name}在学习Python")
@Log('work2.log') # 使用装饰器来给 work函数增加记录日志的功能
def work(name1, name2): # 1.当前 work可能有多个参数 2.自定义日志文件的名字和位置,记录日志级别
print(f"{name1}和{name2}正在工作")
learn("Flyme awei")
work('张三', '李四')
类实现装饰器有一个优势,在于比嵌套函数的方式更加整洁,而且包裹一个函数还是使用跟以前一样的语法。
八、偏函数Partial function
Python的functools
模块提供了很多有用的功能,其中一个就是偏函数(Partial function
)。要注意,这里的偏函数和数学意义上的偏函数不一样。
例如:int()
函数可以把字符串转换为整数,当仅传入数字字符串时,int()
函数默认按十进制转换
>>> int('123')
123
但int()
函数还提供额外的base
参数,默认值为10
。如果传入base
参数,就可以做进制的转换。
>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565
如果要转换大量的二进制字符串,每次都传入int(x, base=2)
非常麻烦,于是,我们想到,可以定义一个int2()
的函数,默认把base=2
传进去:
代码:
# 定一个转换义函数
def int_bin(num, base=2):
return int(num, base=2)
print(int_bin('1001')) # 9
把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单,继续优化,functools.partial
就是帮助我们创建一个偏函数的,不需要我们自己定义int_bin()
函数,可以直接使用下面的代码创 建一个新的函数。
# -*- coding: utf-8 -*-
# @File : 偏函数partial.py
# @author: Flyme awei
# @email : Flymeawei@163.com
# @Time : 2022/8/21 22:18
from functools import partial
int2 = partial(int, base=2)
print(int2('1001')) # 9
理清了functools.partial
的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。注意到上面的新的
int2
函数,仅仅是把base
参数重新设定默认值为2
,但也可以在函数调用时传入其他值实际上固定了int()
函数的关键字参数base
。
九、总结
1.生成器
⽣成器
是这样⼀个函数,它记住上⼀次返回时在函数体中的位置。对⽣成器函数的第⼆次(或第 n 次)调⽤跳转⾄该函数中间,⽽上次调⽤的所有局部变量都保持不变。⽣成器不仅
记住
了它数据状态;⽣成器还记住
了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。
2.迭代器
迭代器是访问集合的一种方式,可以记住遍历位置的对象,迭代器从集合的第一个元素开始访问,直到所有的元素被访问完才结束,只能往往前,不能后退。
3.匿名函数
匿名函数lambda
:顾名思义,没有名字的函数,可以将其赋值给一个变量。
语法:lambda 参数列表: 表达式
4.内置高阶函数
函数 | 说明 |
---|---|
map(func, *iterables) |
创建一个迭代器,使用from 的参数计算函数每个可迭代对象。当最短的可迭代对象耗尽时停止。 |
reduce(function, sequence[, initial]) |
对序列中的项累计应用两个参数的函数,从左到右,从而将序列缩减为单个值。 |
filter(function or None, iterable) |
返回一个迭代器,返回iterable 中function(item) 对应的元素是真的。如果function 为None ,则返回为true 的项。 |
max() |
返回其最大的项 |
min() |
返回其最小的项 |
sorted() |
返回一个新列表,其中包含可迭代对象中的所有项,按升序排列。 |
5.返回函数
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
6.闭包
一个函数定义中引入了函数定义以外的变量,并且该函数可以在其定义以外被执行,这个函数以及引用到外部的变量称为闭包。
7.装饰器
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值 也是一个函数对象。它经常用于有以下场景,比如:
插入日志
、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。
8.偏函数
Python的functools
模块提供了很多有用的功能,其中一个就是偏函数(Partial function
)。