Python----Python高级(函数式编程,迭代器,生成器,装饰器)
一、函数式编程
1.1、发展历程
函数式编程(functional programming)其实是个很古老的概念,诞生距今快60年啦!
最古老的函数式编程语言Lisp
新出现的函数式编程语言:比如Erlang、Scala、clojure等
热门语言:Python、java、JavaScript、C++等都增加了函数式编程的一些特性。
1.2、高阶函数和内存分析
函数式编程最鲜明的特点就是:函数是一等公民(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。
一个函数可以接收另一个函数作为参数,这种函数就称之为高阶函数。
Python内建的高阶函数有
map
、reduce
、filter
、sorted
#coding=utf-8
def test1():
print("I'm test!")
def test2(func): # test2就是一个高阶函数
func()
print("test2 running...")
if __name__ == '__main__':
print(id(test1)) # 2439745649248
print(type(test1)) # <class 'function'>
a = test1 # a和test1都指向了同一个函数对象
a() # I'm test!
test2(a) #a作为参数传递给test2() test2 running...
1.3、 lambda表达式和匿名函数
lambda表达式可以用来声明匿名函数。lambda函数是一种简单的、在同一行中定义函数的方法。lambda函数实际生成了一个函数对象。
lambda表达式只允许包含一个表达式,不能包含复杂语句,该表达式的计算结果就是函数的返回值。
lambda表达式的基本语法如下:
lambda arg1,arg2,arg3... : <表达式>
arg1 arg2 arg3为函数的参数。<表达式>相当于函数体。运算结果是:表达式的运算结果。
f = lambda a,b,c:a+b+c
print(id(f))#2439748267648
print(f(2,3,4))#9
g = [lambda a:a*2,lambda b:b*3,lambda c:c*4]
print(g[0](6),g[1](7),g[2](8))#12 21 32
1.4、偏函数
把一个函数某些参数固定住(也就是设置默认值),返回一个新的函数,调用这个新的函数会更简单。
int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换
print(int('12345'))#12345
int()函数还提供额外的base参数,默认值为10。如传入base参数,就可以做N进制的转换:
print('转换为八进制',int('12345', base=8))#转换为八进制 5349 print('转换为十六进制',int('12345', 16))#转换为十六进制 74565
functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用
import functools int2 = functools.partial(int, base=2) print(int2('1000000')) #64 print(int2('1010101')) #85 print(int2('1000000', base=10)) #也可以修改base的值
1.5、闭包closure
闭包可以理解为一个封闭的包裹,这个包裹就是一个函数。当然,还有函数内部对应的逻辑,包裹里面的东西就是自由变量(外部函数的局部变量),自由变量可以随着包裹到处游荡。
局部变量:
如果名称绑定再一个代码块中,则为该代码块的局部变量,除非声明为nonlocal或global
全局变量:
如果模块绑定在模块层级,则为全局变量
自由变量:
如果变量在一个代码块中被使用但不是在其中定义,则为自由变量
闭包的特点:
闭包是一个函数,而且存在于另一个函数当中
闭包可以访问到父级函数的变量,且该变量不会销毁
存在内外层函数嵌套的情况
内层函数引用了外层函数的变量或者参数(自由变量)
外层函数把内层函数本身当作返回值进行返回,而不是返回内层函数产生的某个值
def outer():
a = 1
def inner():
nonlocal a
print("a:",a)
a += 1
return inner
inn = outer()
inn()#a: 1
inn()#a: 1
闭包可以当成两个部分组成的整体:
函数
自由变量
闭包的作用
隐藏变量,避免全局污染
可以读取函数内部的变量
闭包缺点
导致变量不会被垃圾回收机制回收,造成内存消耗
不恰当的使用闭包可能会造成内存泄漏的问题
闭包也是装饰器的基础
1.6、map函数
map()函数接收两种参数,一是函数,一种是序列(可以传入多个序列),map将传入的函数依次作用到序列的每个元素,并把结果作为新的list返回。
def f(x):
return x * x
L=map(f,[1, 2, 3, 4, 5, 6, 7, 8, 9])
print(list(L))
L=map(lambda n:n*n,[1, 2, 3, 4, 5, 6, 7, 8, 9])
print(list(L))
def f2(x,y):
return x+y
L=map(f2,[1,2,3,4],[10,20,30])
print(list(L))
L=map(lambda x,y:x+y,[1,2,3,4],[10,20,30])
print(list(L))
1.7、reduce函数
reduce位于functools模块
from functools import reduce
def add(x, y):
return x + y
sum=reduce(add, [1, 3, 5, 7, 9])
print(sum)
1.8、filter函数
内置函数filter()用于过滤序列。filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False, 决定保留还是丢弃该元素。
# 在一个list中,删掉偶数,只保留奇数
def is_odd(n):
return n % 2 == 1
L=filter(is_odd, [1, 2, 4, 5])
print(list(L))
#filter序列中的空字符串删掉
def not_empty(s):
return s and s.strip()
L=filter(not_empty, ['A', '', 'B', None, 'C', ' '])
print(list(L))#['A', 'B', 'C']
1.9、sorted()函数
sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序
sorter1 = sorted([1,3,6,-20,34])
print("升序排列:",sorter1)
# sorted()函数也是高阶函数,它还可以接收一个key函数来实现自定义的排序
sorter2 = sorted([1,3,6,-20,-70],key=abs)
print("自定义排序:",sorter2)
sorter2 = sorted([1,3,6,-20,-70],key=abs,reverse=True)
print("自定义反向排序:",sorter2)
# 4.2 字符串排序依照ASCII
sorter3 = sorted(["ABC","abc","D","d"])
print("字符串排序:",sorter3)
# 4.3 忽略大小写排序
sorter4 = sorted(["ABC","abc","D","d"],key=str.lower)
print("忽略字符串大小写排序:",sorter4)
# 4.4 要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True:
sorter5 = sorted(["ABC","abc","D","d"],key=str.lower,reverse=True)
print("忽略字符串大小写反向排序:",sorter5)
from functools import cmp_to_key
class Student:
def __init__(self, age, name):
self.name = name
self.age = age
def custom_sorted(stu1,stu2):
if stu1.age < stu2.age:
return -1
if stu1.age > stu2.age:
return 1
return 0
stu1 = Student(41, 'aaa')
stu2 = Student(21, 'ccc')
stu3 = Student(31, 'bbb')
student_list = sorted([stu1, stu2, stu3], key=cmp_to_key(custom_sorted))
for stu in student_list:
print('name:', stu.name, 'age:', stu.age)
'''
name: ccc age: 21
name: bbb age: 31
name: aaa age: 41
'''
二、迭代器
2.1、迭代器
迭代器(Iterator)是一个实现了迭代器协议的对象。
两个方法:__iter()__和__next()__。
__iter()__方法:
当迭代器被创建时,这个方法会被调用,并且应该返回迭代器对象本身。这个方法使得对象能够被用在for循环以及其他需要迭代器的上下文中。
__next()__方法:
这个方法会在每次迭代中被调用,并且返回序列中的下一个元素。如果所有的元素都已经迭代完毕,会抛出一个StopIteration类型的异常,表示迭代已经完成。
2.2、可迭代对象的定义
可迭代对象(Iterable)是指能够返回一个迭代器的对象,实现了__iter()__方法
具有以下特点:
1. 实现了__iter()__方法:
可迭代对象必须拥有该方法,以便于返回一个迭代器对象。
2. 可以用于for循环:
可迭代对象可以用于for循环,在for循环时,Python会自动调用__iter()__方法来获取迭代器,然后不断调用迭代器的__next()__去获取下一个元素。比如Python内置的列表、元组、字符串等都是可迭代对象,在使用for循环去遍历时,会调用可迭代对象的__iter()__方法来获取一个迭代器,接着不断调用迭代器的__next()__方法去获取下一个元素,直到元素全部迭代完毕,抛出异常, for循环会自己捕获这个异常并退出循环,从而完成一次for循环。
2.3、迭代器与可迭代对象的关系
1. 所有迭代器都是可迭代对象:
因为迭代器都必须具有__iter()__和__next()__方法, 并且通常返回自身,也可以去访问序列中的下一个元素,并在所有元素都被迭代 后抛出StopIteration异常。
2. 不是所有可迭代对象都是迭代器:
可迭代对象只实现__iter()__方法,并不实现 __next()__方法。比如Python内置的列表、元组、字符串等虽然是可迭代对象, 但不是迭代器。
总结:
1. 凡是可作用于for循环的对象都是可迭代对象。
2. 凡是同时拥有__iter()__和__next()__方法的都是迭代器。
2.4、内置类型的迭代器
对于Python内置的可迭代对象来说,可以直接通过调用每一个对象已经定义好的 __iter()__方法来获取对应的迭代器
my_list = [1, 2, 3, 4, 5]
list_iterator = iter(my_list)
try:
while True:
item = list_iterator.__next__()
print(item)
except StopIteration:
print("Iteration is complete.")
'''
1
2
3
4
5
Iteration is complete.
'''
2.5、自定义迭代器
在Python中也可以自定义自己的迭代器,只需要在构造类时实现__iter()__和 __next()__两个方法
创建一个名为 EvenNumbers 的迭代器类,该迭代器能够生成从指定起始值(包含)开始的指定数量的偶数。例如,EvenNumbers(2, 5) 应该生成 2、4、6、8、10 这 5 个偶数。
class EvenNumbers: def __init__(self,a,b): self.a=a self.b=b self.count=0 if self.a%2==0: self.a=self.a-2 else: self.a=self.a-1 def __iter__(self): return self def __next__(self): if self.count<self.b: self.count+=1 print(f'===={self.count}====') self.a=self.a+2 return self.a else: raise StopIteration e=EvenNumbers(2,5) for i in e: print(i) ''' ====1==== 2 ====2==== 4 ====3==== 6 ====4==== 8 ====5==== 10 '''
使用迭代器去生成指定个数的斐波那契数列
class Fbnq: def __init__(self,n): self.n=n self.a,self.b=1,1 self.count=0 def __iter__(self): return self def __next__(self): if self.count<self.n: self.count+=1 print(f'===={self.count}====') value=self.a self.a,self.b=self.b,self.a+self.b return value else: raise StopIteration f=Fbnq(5) for i in f: print(i) ''' ====1==== 1 ====2==== 1 ====3==== 2 ====4==== 3 ====5==== 5 '''
三、生成器
3.1、生成器
生成器(Generator)是一种特殊的迭代器,它可以在需要时动态生成值,避免一次 性生成所有值所占用的大量内存。生成器使用 yield 语句来产生值,可以通过迭代器 协议来逐个获取生成器产生的值。生成器可以在循环中被逐个迭代,从而实现高效地 处理大量数据或者无限序列。在Python中,生成器可以通过函数定义和生成器表达 式来创建
3.2、生成器的特点
惰性求值:
生成器不会在创建时生成所有值,而是逐个生成值。这意味着生成器 可以在需要的时候再去生成值。
使用yield关键字:
只有yield被调用时,生成器才会返回一个值,并且程序暂停 执行,直到下一次迭代时才继续执行。
内存效率:
生成器可以处理大型数据集或生成大型数据结构,而不会占用太多内 存,因为它们只在需要时生成值。
迭代器协议:
生成器也是迭代器,所有实现了迭代器协议,因此可以用于for循环 和其他需要迭代器的代码中。
生成器是一个特殊的程序,可以被用作控制循环的迭代行为,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器。
3.3、yield的用法
生成器使用最多的地方是和函数结合,因此,拥有生成器的函数就叫做生成器函数, 也可以理解为函数中包含了yield关键字的函数就是一个生成器函数。生成器函数包 含如下特点:
使用yield关键字:
普通函数使用return返回值,而生成器函数使用yield关键字 返回值。
状态保持:
生成器函数在每次产生一个值之后会暂停执行,并保持当前的状态, 包括局部变量的状态和当前的执行位置。当再次调用生成器时,他会从上次yield 语句之后的地方继续执行。
惰性求值:
生成器只在需要时才计算并生成值,这意味着它可以高效处理大量数 据或无限流数据。
生成器函数在调用时,不会立即执行函数里面的代码,而是会先返回一个生成器的对 象,当调用生成器对象的__next()__方法时,才会进入函数中并执行函数里面的代 码,遇到yield关键字后,函数的执行将会暂停,并将yield后面的表达式作为当前迭 代的值返回。然后每次调用生成器的__next()__方法或使用for循环进行迭代时,函数 会从上次暂停的地方继续执行,直到再次遇到yield关键字,并再次返回一个值,直 到无法继续生成为止。
def test():
print("start")
i=0
while i<3:
temp = yield i #下次迭代时,代码从`yield`的下一条语句(不是下一行)开始执行
print(f"temp:{temp}")
i += 1
print("end")
return "done"
if __name__ == '__main__':
a = test()
print(type(a))
print(a.__next__())
print(a.__next__())
print(a.__next__()) # next(a) 一样
print(a.__next__()) # 抛出异常:StopIteration
'''
<class 'generator'>
start
0
temp:None
1
temp:None
2
temp:None
end
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
Cell In[553], line 16
14 print(a.__next__())
15 print(a.__next__()) # next(a) 一样
---> 16 print(a.__next__()) # 抛出异常:StopIteration
StopIteration: done
'''
3.4、send的用法
send的作用是唤醒并继续执行,发送一个信息到生成器内部
def foo():
print("start")
i = 0
while i<2:
temp = yield i
print(f"temp:{temp}")
i=i+1
print("end")
g = foo()
print(next(g))
print("*"*20)
print(g.send(100))
print(next(g))
'''
start
0
********************
temp:100
1
temp:None
end
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
Cell In[554], line 14
12 print("*"*20)
13 print(g.send(100))
---> 14 print(next(g))
StopIteration:
'''
3.5、自定义生成器函数
def count_up_to(max):
count = 1
while count <= max:
yield count
count += 1
for number in count_up_to(5):
print(number)
def fibonacci():
a, b = 1, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
for _ in range(10):
print(next(fib))
四、装饰器
4.1、装饰器
在Python中,装饰器(Decorator)本质上是一种特殊的嵌套函数,它接收一个函数 作为参数(该函数就是被装饰的函数),并返回一个新的函数(装饰之后的函数)。 装饰器最大的作用就是可以让我们在不改变被装饰函数的代码的情况下去给它添加新 的功能
def func1(func):
def inner():
print('b')
func()
print('c')
return inner
def func():
print('a')
func = func1(func)
func()
'''
b
a
c
'''
4.2、装饰器语法糖
语法糖是指一种编程语言提供的,可以让代码更加简洁、易于理解和书写的 语法特性,这些特性不会带来新的功能,也就是说,使用或不使用语法糖,最终程序 的行为是相同的,但是语法糖可以使代码更易于阅读和维护
Python中有许多语法糖,比如:
列表推导式
字典推导式
集合推导式
链式比较
f-string
解包
装饰器
装饰器的使用可通过在目标函数上方放置一个 @ 符号,随后跟上装饰 器函数的名字来实现。这样,我们就无需显式地将目标函数作为参数传递给装饰器函 数,而是可以直接调用目标函数。通过这种方式,装饰器能够为原有函数增添额外的 功能。
def func1(func):
def inner():
print('b')
func()
print('c')
return inner
@func1
def func():
print('a')
func()
'''
b
a
c
'''
4.3、装饰带参数的函数
当装饰器装饰带有参数的函数时,装饰器内部也需要去接收参数,从而保证在装饰器 里调用被装饰的函数时能够正常传递参数。
4.3.1、装饰只有一个参数的函数
import time
def timer(f):
def de(x):
start=time.time()
ret=f(x)
print(time.time()-start)
return ret
return de
@timer
def func(x):
time.sleep(2)
func(1)#2.0049710273742676
4.3.2、装饰未知参数数量的函数
import time
def timer(f):
def decorator(*args, **kwargs):
start = time.time()
ret = f(*args, **kwargs)
print(time.time() - start)
return ret
return decorator
@timer
def my_func(x):
time.sleep(x)
@timer
def add(x, y):
time.sleep(1)
print(x + y)
# my_func(1)#1.0033810138702393
add(2, 3)
# 5
# 1.000701904296875
4.4、带参数的装饰器
装饰器本身又要接收被装饰的函数名称,并且返回一个新的函 数,所以为了保证装饰器能够顺利接收参数及顺利接收被装饰的函数,需要在装饰器 内部多一层内嵌函数,最外层的函数用来获取装饰器参数,中间的函数用来获取被装 饰的函数名称,最内层的函数负责进行功能的添加。
def fun1(inp):
def fun2(f):
def fun3(*arg,**kwargs):
ret=f(*arg,**kwargs)
return f'{ret}======{inp}'
return fun3
return fun2
@fun1('hello World')
def fun(name):
return f'hello,就你叫{name}'
fun('张三')#'hello,就你叫张三======hello World'
4.5、装饰器嵌套
装饰器嵌套是指在Python中,一个函数可以被多个装饰器依次装饰。每个装饰器都 会对原始函数进行一层包装,从而在调用原始函数时,可以依次执行每个装饰器中的 逻辑,以达到为该函数添加多个不同功能的要求。
def decorator_one(func):
def wrapper_one(*args, **kwargs):
print("Decorator one - before call")
result = func(*args, **kwargs)
print("Decorator one - after call")
return result
return wrapper_one
def decorator_two(func):
def wrapper_two(*args, **kwargs):
print("Decorator two - before call")
result = func(*args, **kwargs)
print("Decorator two - after call")
return result
return wrapper_two
@decorator_one
@decorator_two
def say_hello(name):
print(f"Hello, {name}")
# say_hello = decorator_one(decorator_two(say_hello))
say_hello("Alice")
'''
Decorator one - before call
Decorator two - before call
Hello, Alice
Decorator two - after call
Decorator one - after call
'''
装饰器嵌套时,装饰器的执行顺序是从下到上,且最下面的装饰器的返回值会返回给 上一层的装饰器作为参数,然后层层传递直到最上面的装饰器,而当我们要运行被装 饰的函数时,相当于从最上层的装饰器开始向下运行直到运行完毕。
4.6、类装饰器
除了可以自定义一个新的函数用作装饰器之外,也可以将一个类作为装饰器,为被装 饰的函数添加新的功能。类装饰器通过实现类的__call__方法,使得类的实例可以被 当作函数来调用,从而实现对其他函数的装饰。
编写一个类装饰器 PrintInputOutput,用于装饰一个函数,使其在调用时打印输入的参数和输出的结果。
class PrintInputOutput(object): def __init__(self,func): self.func=func def __call__(self,*args,**kwargs): ret=self.func(*args,**kwargs) # print(f'{args}') return args,ret @PrintInputOutput def add(x,y): return x+y add(3,5)#((3, 5), 8)
4.7、wraps装饰器
一个函数不止有他的执行语句,还有着 __name__(函数名),__doc__ (说明文档)等属性。
functool.wraps可以将原函数对象的指定属性赋值给包装函数对象,默认有module、name、doc,或者通过参数选择。
from functools import wraps
def mylog(func):
@wraps(func)
def infunc(*args,**kwargs):
print("日志纪录...")
print("函数文档:",func.__doc__)
return func(*args,**kwargs)
return infunc
@mylog # fun2 = mylog(fun2)
def fun2():
"""强大的功能2"""
print("使用功能2")
fun2()
print("函数文档--->",fun2.__doc__)
"""
日志纪录...
函数文档: 强大的功能2
使用功能2
函数文档---> 强大的功能2
"""
4.8、 内置装饰器
4.8.1、property装饰器
property 装饰器用于类中的函数,使得我们可以像访问属性一样来获取一个函数的返回值。
class User:
def __init__(self,name,month_salary):
self.name = name
self.month_salary = month_salary
@property
def year_salary(self):
return int(self.month_salary)*12
u1 = User("张三","30000")
print(u1.year_salary)
4.8.2、staticmethod装饰器
staticmethod 装饰器同样是用于类中的方法,这表示这个方法将会是一个静态方法,意味着该方法可以直接被调用无需实例化,但同样意味着它没有 self 参数,也无法访问实例化后的对象。
class Person:
@staticmethod
def say_hello():
print("hello world!")
Person.say_hello()
4.8.3、classmethod装饰器
classmethod 这个方法是一个类方法。该方法无需实例化,没有 self 参数。相对于 staticmethod 的区别在于它会接收一个指向类本身的 cls 参数。
class Person:
@classmethod
def say_hello(cls):
print(f"我是{cls.__name__}")
print("hello world!")
Person.say_hello()
4.9、装饰器的常见应用
4.9.1、记录日志
装饰器可以用来记录函数调用的详细信息,包括调用时间、参数、返回值等。
import logging
def log_decorator(func):
def wrapper(*args, **kwargs):
logging.basicConfig(filename='./app.log', level=logging.INFO, filemode='a', format='%(name)s - %(levelname)s - %(asctime)s- %(message)s')
logging.warning(f"Calling function: {func.__name__} with args: {args} and kwargs: {kwargs}")
result = func(*args, **kwargs)
logging.warning(f"Function {func.__name__} returned: {result}")
return result
return wrapper
@log_decorator
def test():
print('123')
test()
4.9.2、性能检测
装饰器可以用来测量函数执行所需的时间,帮助识别性能瓶颈。
import time
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} took {end_time - start_time:.10f} seconds to run.")
return result
return wrapper
@timer
def process_list(my_list):
for i in my_list:
print(i)
my_list = list(range(100))
process_list(my_list)
4.9.3、权限验证
装饰器可以用来检查用户是否有权限执行特定的函数或方法。
class User:
def __init__(self, username, role):
self.username = username
self.role = role
# 装饰器:检查用户是否具有管理员权限
def admin_only(func):
def wrapper(user, *args, **kwargs):
if user.role != 'admin':
raise PermissionError(f"User {user.username} is not authorized to perform this action.")
return func(user, *args, **kwargs)
return wrapper
# 使用装饰器的函数
@admin_only
def delete_user(user, user_to_delete):
"""只有管理员可以删除用户"""
print(f"User {user_to_delete.username} has been deleted by {user.username}.")
# 测试代码
admin_user = User('Alice', 'admin')
normal_user = User('Bob', 'user')
# 尝试以管理员身份删除用户
delete_user(admin_user, normal_user) # 应该成功
# 尝试以普通用户身份删除用户
try:
delete_user(normal_user, admin_user) # 应该失败
except PermissionError as e:
print(e)