函数和lambda表达式(下)
5. 函数的高级用法
5.1 局部函数(嵌套函数)
python中允许在函数体内部定义函数,这种被放在函数体内定义的函数称为局部函数,又称为嵌套函数。
def outer_func():
name = "Meng"
def inner_func():
print(name)
inner_func()
outer_func()
5.2 高阶函数
一个函数可以作为参数传给另外一个函数,或者一个函数的返回值为另外一个函数,满足其一则为高阶函数。
# example
# 函数作为参数传递给另外一个函数
def get_two_sum(x, y):
return x + y
def print_two_sum(func, a, b):
result = func(a, b)
print("两个数的结果为:", result)
print_two_sum(get_two_sum, 10, 20)
#======output========
两个数的结果为: 30
# 函数作为返回值
def returned_func():
print("returned_func函数被调用")
def outer_func():
print("outer_func函数被调用")
return returned_func
ret = outer_func()
ret()
#=======output=======
outer_func函数被调用
returned_func函数被调用
常用的高阶函数包括map()、filter()、reduce()
,常常和lambda
表达式一起使用,使用方式如下:
map()函数
该函数接收的是两个参数,一个函数,一个序列,其功能是将序列中的值做出处理后依次返回给序列,它的返回值是一个迭代器对象。
list1 = [1, 2, 3, 4]
list2 = list(map(lambda x: x**2, list1)) # 将列表中的元素变成对应元素的平方,这里第二个参数传入一个元组、字符串也是可以的,如果传入的是字典类型,需要将其变成dict_items类型
print(list2)
#========output========
[1, 4, 9, 16]
filter()函数
该函数同样接收的是两个参数,一个函数和一个序列,其功能是过滤。其返回值也是一个迭代器对象。
list1 = [1, 2, 3, 4]
list2 = list(filter(lambda x: x % 2 == 0, list1)) # 过滤出的是函数返回值是True的元素
print(list2)
#=======output========
[2, 4]
reduce()函数
该函数也是接收三个参数,一个是函数,一个是序列,还有一个可选参数是一个初始值,其功能是对参数序列中的元素进行累积,其返回值是一个值。它不是内置函数,是functools
模块中的函数。
from functools import reduce
list1 = [1, 2, 3, 4]
result = reduce(lambda x, y: x + y, list1, 10)
print(result)
#=======output=======
20
# 其计算过程如下
# 10 + 1 = 11
# 11 + 2 = 13
# 13 + 3 = 16
# 16 + 4 = 20
5.3 函数的闭包
利用嵌套函数,在外部函数中定义一个内部函数,内部函数中运用了外部函数中的临时变量,并且外部函数的返回值是内部函数的引用,这样就形成了一个闭包。
def outer_func(a):
b = 10
def inner_func():
return a + b
return inner_func
ret = outer_func(3)
print(ret())
5.4 函数装饰器
装饰器本质上是一个Python
函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。
装饰器=高阶函数+函数嵌套+闭包
5.4.1 无参函数装饰器
首先是最简答的装饰器,要装饰的函数不需要额外的参数,例如测试一个函数执行的时间:
import time
def test_run_time(func): # 函数作为参数,形成高阶函数
def wrapper(): # 函数内部定义函数,形成函数嵌套
start_time = time.time()
func() # 使用外层函数变量,形成闭包
end_time = time.time()
print("函数运行时间为:", end_time - start_time)
return wrapper # 函数作为返回值,形成高阶函数
@test_run_time # 该语法就相当于 test = test_run_time(test) 简化了操作,省去了一个赋值语句。
def test():
time.sleep(1)
print("这里是要测试的函数")
test()
5.4.2 有参函数装饰器
import time
def test_run_time(func):
def wrapper(*args): # 如果要装饰的函数不需要传关键字参数,则只写*args就可以,如果需要关键字参数,需要加上**kwargs
start_time = time.time()
func(*args)
end_time = time.time()
print("函数运行时间为:", end_time - start_time)
return wrapper
@test_run_time
def print_two_sum(a, b):
time.sleep(1)
print("{}+{}={}".format(a, b, a+b))
print_two_sum(5, 7)
#=========output=========
5+7=12
函数运行时间为: 1.0009658336639404
5.4.3 有返回值函数装饰器
import time
def test_run_time(func):
def wrapper(*args):
start_time = time.time()
result = func(*args)
end_time = time.time()
print("函数运行时间为:", end_time - start_time)
return result
return wrapper
@test_run_time
def get_two_sum(a, b):
time.sleep(1)
return a + b
result = get_two_sum(5, 7)
print("两数之和为:", result)
#=======output=======
函数运行时间为: 1.0006444454193115
两数之和为: 12
根据上面三个例子我们就可以总结出一个完整的装饰器框架如下:
def wrap(func):
def wrapper(*args, **kwargs):
"""可增加功能"""
result = func(*args, **kwargs)
"""可增加功能"""
return result
return wrapper
在使用时只需在定义函数前面加上@wrap
即可。
5.4.4 带参数的装饰器
使用了两层嵌套函数,相当于对原有装饰器进行封装,并返回一个装饰器。
import time
def cal(mark):
def wrapper(func):
def deco(*args):
if mark == "+":
print("这是加法运算")
elif mark == "*":
print("这是乘法运算")
start_time = time.time()
result = func(*args)
end_time = time.time()
print("{}运算耗时:".format(mark), end_time - start_time)
return result
return deco
return wrapper
@cal("+") # 该语法就相当于 cal = cal("+") get_two_sum = cal(get_two_sum)
def get_two_sum(a, b):
time.sleep(1)
return a + b
@cal("*")
def get_two_multi(a, b):
time.sleep(2)
return a * b
result = get_two_sum(5, 7)
print("两数之和为:", result)
print('-' * 20)
result1 = get_two_multi(5, 7)
print("两数之积为:", result1)
#===========output=========
这是加法运算
+运算耗时: 1.0009543895721436
两数之和为: 12
--------------------
这是乘法运算
*运算耗时: 2.0008320808410645
两数之积为: 35
5.5 偏函数
当一个函数有很多参数时,调用者就需要提供多个参数。如果减少参数个数,就可以简化调用者的负担。使用偏函数可以将函数中的某个或某些参数固定,这样在需要传相同参数的情况下就可以少写一个参数,减轻负担。该功能通过调用functools
中的partial
来实现。
# example
# 例如要将多个二进制字符串转换成数字,我们可以进行如下操作,省去了在每次调用时都要传入base参数的麻烦
from functools import partial
int2 = partial(int, base=2)
print(int2("11001"))
5.6 生成器函数
生成器函数(Generator)
是一种特殊的函数,是用来创建生成器对象的工具,生成器函数使用yield
语句返回。其返回值是一个生成器对象。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行。
# 使用生成器生成斐波那契数列
def fib(num):
a, b = 0, 1
for i in range(num):
yield b
a, b = b, a+b
for i in fib(10):
print(i, end=" ")
#========output=======
1 1 2 3 5 8 13 21 34 55
生成器只能遍历一次
生成器表达式
类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表。
# 列表推导式
list1 = [i for i in range(0, 10, 2)]
for i in list1:
print(i)
# 在使用for循环遍历后再对其进行访问,仍然可以访问
print(list1[2])
# 生成器表达式
gen = (i for i in range(0, 10, 2))
for i in gen:
print(i)
# 在使用for循环遍历完后对其中单个元素进行访问,不能访问
print(gen[2]) # TypeError: 'generator' object is not subscriptable
# 这说明,生成器表达式在调用完一次之后就被销毁了
生成器与迭代器虽然在使用方式上类似,但是内部的原理却完全不相同。
迭代器是所有的内容都在内存上, 使用next函数往下遍历
生成器不会把内容放在内存里,每次调用next函数时,返回的都是计算出来的那个元素,用完后立即销毁。
所以使用生成器与迭代器存在时间上与空间上的衡量,如果是数据量较大的程序,那么使用生成器,但是如果是时间至上,则可以优先考虑迭代器。
参考自博客https://blog.csdn.net/qq_20880939/article/details/100899942
6. Python中的内置函数
点击对应函数名查看相应用法。
7. 其它
7.1 对函数的参数和返回值类型的指定和检查
Python
在3.5版本之后加入了对参数和返回值类型的指定和检查,在新建变量时也可以指定类型。
7.1.1 基本类型的指定
def test(a: str) -> list:
return a
test([1, 2, 2])
例如,我们在上面函数中指定a
的类型为字符串,返回值类型为list
,在代码中直接将a
作为返回值返回,在pycharm
中使用的时候它会给出相应的颜色提示,但是在实际运行的时候是不会报错的,因为Python
的本质是动态语言。
7.1.2 复杂的指定(集合相关类型的指定)
除了最基本的数据类型指定,还可以进行集合相关类型的指定。
这里需要用到typing
模块中的一些类和方法
from typing import List
Vector = List[float] # 这样就指定了List中的元素需为float类型,如果里面指定每个元素的类型如:Tuple[float, int], 那么它的长度就只能是2,并且,第一个元素为float类型,第二个元素为int类型,当然也是只会在pycharm中给出提示,实际运行并不会因为检查类型不匹配报错。
def scale(scalar: int, vector: Vector) -> Vector:
return [scalar * num for num in vector]
new_vector = scale(2, [1.0, 2.6, 5.4])
print(new_vector)
7.1.3 泛型的指定
from typing import Sequence, TypeVar, Union
T = TypeVar('T') # 声明一个类型变量,如果后面什么都不加,那么就可以是任意类型
def first(l: Sequence[T]) -> T:
return l[0]
如果在声明类型变量的时候,后面指定可以表示哪些类型,那么在检查的时候会进行匹配,如果不匹配会给出相应提示。
T = TypeVar('T') # T可以表示任何类型
A = TypeVar('A', str, bytes) # A必须是字符串或字节
7.1.4 创建变量时的类型指定
name:str = "Meng" # 如果写成 name:str = 123,pycharm将会给出提示
print(name)
其它更多的类型指定可以参考Python
中typing
的说明文档。
该部分内容参考自博客https://blog.csdn.net/sgyuanshi/article/details/96192887