首页Python【Python基础】15....

【Python基础】15.函数基础3——生成器、递归、装饰器


本系列文章配套代码获取有以下三种途径:

  • 可以在以下网站查看,该网站是使用JupyterLite搭建的web端Jupyter环境,因此无需在本地安装运行环境即可使用,首次运行浏览器需要下载一些配置文件(大约20M):

https://returu.github.io/Python_Basic/lab/index.html
链接:https://pan.baidu.com/s/1x2Ynh_VibPY2u-HWTaJo8w?pwd=mnsj 提取码:mnsj
  • 前往GitHub详情页面,单击 code 按钮,选择Download ZIP选项:
https://github.com/returu/Python_Basic

—————————————————

1.生成器:

生成器是一个Python对象序列生成对象。通过它可以对可能会很长的序列进行迭代,而无须一次性在内存中创建并保存整个序列。生成器通常是迭代器的数据源。

1.1 生成器与迭代器:

在for循环中介绍了迭代器的相关内容,生成器与迭代器的使用方法较为相似,但是两者的内部原理却是完全不同的。

迭代器是将所有的内容都放在内存中,然后依次往下遍历;而生成器则不会把内容放在内存中,每次遍历返回的都是本次计算出来的元素,用完之后立即销毁。

因此,当整个序列占用内存特别大时,使用生成器对象会节约内存;当系统运算资源不足时,使用迭代器对象会节约运算资源。

1.2 生成器函数(generator):

生成器函数也是一种普通函数,但是是通过yield而非return语句返回函数值,返回值是一个生成器对象。

 1# 定义一个函数实现与内置range()函数一样的功能
2>>def my_range(start=0 , end=10 , step=1):
3...     num = start
4...     while num < end:
5...         yield num
6...         num += step
7
8# 这是一个普通函数
9>>> my_range
10<function __main__.my_range(start=0end=10, step=1)>
11
12# 函数会返回一个迭代器你对象
13>>> my_range = my_range(1,5)
14>>> my_range
15<generator object my_range at 0x0000015BE8C5ADD0>
16
17# 迭代返回的生成器对象
18>>> for i in my_range:
19...     print(i)
20
211
222
233
244

上述代码中,自定义了一个函数,其功能与内置range()函数一样,并返回生成器对象。在调用过程中,使用for循环对生成器对象进行遍历并输出。

在for循环的内部实现里,每次循环生成器都会返回一个元素,等全部循环结束后,生成器也随之结束,不会在内存中有存留。

1# 尝试再次迭代该生成器,可以看到什么都没有输出
2for j in my_range:
3    print(j)

2.递归:

如果一个函数调用其自身,就叫做递归。

 1>>> def flatten(lol):
2...     for item in lol:
3...         if isinstance(item , list):
4...             for subitem in flatten(item):
5...                 yield subitem
6...         else:
7...             yield item
8
9>>> lol = [1,[2,3],4,[[5]],[[6,[7,8]]],9]
10>>> flatten(lol)
11<generator object flatten at 0x000002952CB970B0>
12>>> list(flatten(lol))
13[123456789]

Python3.3 加入了yield from表达式,该表达式可以让一个生成器把部分工作交给另一个生成器,用于简化函数。

1# 使用yield from表达式简化代码
2>>> def flatten_2(lol):
3...     for item in lol:
4...         if isinstance(item , list):
5...             yield from flatten(item)
6...         else:
7...             yield item

3.装饰器:

有时候需要在不改变源代码的情况下修改已有的函数(例如增加调试语句,查看传入的参数等),此时就需要使用到装饰器。

3.1 装饰器的实现

装饰器是一种函数,接受一个函数作为输入并返回另一个函数。装饰器的主要作用就是在不改变已有代码的基础上,扩展原有功能。

装饰器就是在原有的函数外面再包装一层函数,使新函数在返回原有函数之前实现一些新功能。

 1# 定义一个装饰器,用于检查输入参数是否是int类型
2>>def check_int(func)# 装饰器函数,参数为要被装饰的函数
3...     def new_function(*args)# 定义一个检查参数类型的函数
4...         print("运行的函数为:" , func.__name__)
5...         print("参数为:" , args)
6...         
7...         if isinstance(args[0] , int) and isinstance(args[1] , int):
8...             return func(*args)
9...         print("输入参数不为int类型!")
10...         
11...     return new_function # 将装饰后的函数返回
12
13>>def add_ints(a , b):
14...     return a + b
15
16
17# 使用装饰器——手动应用
18>>> F = check_int(add_ints) # 对add_ints()函数进行装饰
19>>> F(2,3)
20运行的函数为:add_ints
21参数为: (23)
225
23>>> F(2,'w')
24运行的函数为:add_ints
25参数为: (2'w')
26输入参数不为int类型!

3.2 @修饰符:

还可以在需要装饰的函数前添加@decoratoe_name来代替手动应用装饰器。

使用@修饰符会使得装饰器与被装饰函数之间的关系更加明显,降低了编码出错的可能性。

使用@修饰符是最常用的写法。

 1>>def check_int(func):
2...     def new_function(*args):
3...         print("运行的函数为:" , func.__name__)
4...         print("参数为:" , args)
5...         
6...         if isinstance(args[0] , int) and isinstance(args[1] , int):
7...             return func(*args)
8...         print("输入参数不为int类型!")
9...         
10...     return new_function
11
12# 使用装饰器——@修饰符
13>>> @check_int
14... def add_ints(a , b):
15...     return a + b
16
17>>> add_ints(2 , 3)
18运行的函数为:add_ints
19参数为: (23)
205
21>>> add_ints(2,'w')
22运行的函数为:add_ints
23参数为: (2'w')
24输入参数不为int类型!

3.3 多个装饰器联合使用:

一个函数可以有多个装饰器。当有多个装饰器时,最接近函数的装饰器(就在def之上)先运行,然后再运行其上的装饰器。

下面先定义两个装饰器,然后应用。

 1# 定义第一个装饰器
2def outter_1(func):
3    print("第一个装饰器")
4    def inner_1(*args):
5        print("执行inner_1")
6        result = func(*args)*2
7        return result
8
9    return inner_1
10
11# 定义第二个装饰器
12def outter_2(func):
13    print("第二个装饰器")
14    def inner_2(*args):
15        print("执行inner_2")
16        result = func(*args)+'-'
17        return result
18
19    return inner_2
20
21# 使用装饰器
22@outter_2
23@outter_1
24def demo(a):
25    print("执行demo函数")
26    return a+'+'

此时系统会输出如下结果:

由此可以看出,装饰器在代码载入时就开始执行,会按照从下到上的顺序加载装饰器函数。

1第一个装饰器
2第二个装饰器

然后执行调用语句:

1demo('XXX')

输出如下结果:

1执行inner_2
2执行inner_1
3执行demo函数
4'XXX+XXX+-'

加载装饰器函数之后变为outter_2(outter_1(demo())),然后执行调用语句,首先执行outter_2(),其参数为outter_1(demo()),因此在执行outter_1(),其参数为demo(),执行demo()函数,其返回值为XXX+,然后执行inner_1(),得到XXX+XXX+,再执行inner_2()得到XXX+XXX+-
最终效果就是,先运行最接近函数的装饰器(将结果*2),然后再运行其上的装饰器(将结果加-)。

下面将装饰器函数位置进行互换:

1# 将装饰器位置互换
2
3@outter_1
4@outter_2
5def demo(a):
6    print("执行demo函数")
7    return a+'+'
8
9demo('XXX')

输出结果如下:

先运行最接近函数的装饰器(将结果加-),然后再运行其上的装饰器(将结果*2)。

1第二个装饰器
2第一个装饰器
3执行inner_1
4执行inner_2
5执行demo函数
6'XXX+-XXX+-'


本篇文章来源于微信公众号: 码农设计师

RELATED ARTICLES

欢迎留下您的宝贵建议

Please enter your comment!
Please enter your name here

- Advertisment -

Most Popular

Recent Comments