Python面经【11】- Python可迭代对象一网打尽专题
- 可迭代对象
- Python的迭代器、生成器
- 1) 迭代器
- 2) 生成器
- 可迭代对象、迭代器的区别
- 12. 生成器、迭代器的区别
- 什么是装饰器?
- 函数装饰器有什么作用
- 一句话解释什么样的语言能够使用装饰器
- Python中的作用域?
- 什么是闭包
可迭代对象
- 可迭代对象是迭代器和生成器的基础,简单来说,可以使用for循环遍历的对象就是可迭代对象,比如常见的list、set和dict。
- 在python中,可迭代对象是指实现了**iter(**)方法的对象,当我们使用for循环遍历一个可迭代对象时,实际上是调用该对象的__iter__()方法获取一个迭代器,并使用该迭代器迭代访问对象的元素。
- 常见的类型:
- (1) 所有序列类型:如:list、str、tuple、range
- (2) 非序列类型:dict、set、文件对象:f = open(xx, )
Python的迭代器、生成器
1) 迭代器
- 迭代器是python中实现迭代协议的一个对象,本质上是一种python数据结构,它实现了两个魔法方法:iter()和__next__()方法,统称为迭代器协议。其中,iter()方法返回迭代器自身,next()方法返回序列中的下一个值。
- 迭代器能够支持for循环以及next()函数的调用。在每次迭代时,迭代器都会产生一个值,直到没有下一个值了,就会抛出StopIteration异常。
- 如何判断对象是否是迭代器,和判断是否是可迭代对象的方法差不多,只要把Iterable换成Iterator。
2) 生成器
1. def spam():
2. yield”first”
3. yield”second”
4. yield”third”
5. for x in spam:
6. Print(x)
7. -----结果如下-----
8. first
9. second
10. third
1 我们创建列表的时候,受到内存限制,容量肯定是有限的,而且不可能全部给他一次枚举出来。Python常用的列表生成式有一个致命的缺点就是定义即生成,非常的浪费空间和效率。
2 若列表元素可以按照某种算法推算出来,那么在循环的过程中不断推算中后续的元素,这样就不必创建完整的list,从而节省大量的空间。在python中,这种一边循环一边计算的机制,称为生成器:generator。与字典和集合相反,生成器是典型的时间换空间的例子。
3 创建一个generator,最简单的方法就是改造列表生成式,还有就是生成器函数,通过def定义,然后使用yield来支持迭代器协议,比迭代器写起来更简单。
4 进行函数调用的时候,返回一个生成器对象。在使用next()调用的时候,遇到yield就返回,记录此时的函数调用位置,下次调用next()时,从断点处开始。
5 我们完全可以像使用迭代器一样使用generator,当然除了定义,定义一个迭代器,需要分别实现iter()和next()方法,但generator只需要yield就可以。
可迭代对象、迭代器的区别
- 定义不同:
- Iterable:可迭代对象是指实现了iter()方法的函数,该方法返回了一个可迭代器对象。
- Iterator:迭代器是指实现了iter()和next()方法的对象,其中iter()方法返回迭代器自身,而next方法返回了迭代器的下一个元素,在结尾时引发StopIteration异常。
- 使用方式不同:
- Iterable:可迭代对象可以使用 for 循环和 list() 等函数进行迭代访问。在 for 循环中,会自动调用迭代器的 next() 方法,直到抛出 StopIteration 异常为止。使用 list() 函数可以将可迭代对象转换成列表,从而进行访问。
- Iterator:迭代器需要使用 next() 函数来获取下一个元素,当迭代器遍历完所有元素时,会自动抛出 StopIteration 异常。此外,迭代器只能单向遍历,不能反向遍历。
- 记忆状态:
- Iterable:可迭代对象在进行迭代访问时,不会记忆之前的访问状态,每次都从头开始遍历。
- Iterator:迭代器在进行迭代访问时,会记忆之前的访问状态,每次调用 next() 方法时,会从上一次停止的位置开始遍历。
12. 生成器、迭代器的区别
虽然生成器和迭代都可以用于for循环的遍历,但它们之间有明显的不同之处:
- 实现方式不同
生成器使用了yield语句来实现,而迭代器使用了魔法函数__iter__()和__next__()来实现。 - 生成方式不同
生成器可以逐个生成序列中的值,而迭代器一次性生成整个序列,将其存储在内存中。 - 执行方式不同
生成器可以像函数一样调用,可以在每次迭代的时产生和恢复值,而迭代器则按照序列的下一个元素依次执行。 - 功能不同
生成器主要用于生成序列,而迭代器主要用于遍历序列。
什么是装饰器?
装饰器本质上是还是一个函数,它可以让已有函数不做任何改动的情况下增加功能。
装饰器允许通过将现有函数传递给装饰器,从而向现有函数添加一些额外的功能,该装饰器将执行现有函数的功能和添加的额外功能。
装饰器两大特性:
- 能把被装饰的函数替换成其他函数
- 装饰器在加载模块时立即执行
Eg:写一个记录函数执行时间的装饰器【面试常考】:
1. from time import time
2.
3. def record_time(func):
4. def inner(*args, **kwargs): # 这里的*args和**kwargs用来接收被装饰的函数的参数
5. """函数注释:用来修饰被装饰函数的内嵌函数"""
6. start = time()
7. result = func(*args, **kwargs) # result用来接受被装饰的函数的返回值
8. end = time()
9. print(f"被装饰的函数{func.__name__}的执行时间为:{end - start}")
10. return result + '~~~~~~' # 对被装饰的函数的返回值进行修饰
11.
12. return inner
上面定义的record_time函数即为一个装饰器,内嵌函数inner+外部函数参数fun即构成了闭包。
- 使用方法1:
1. def countdown(n):
2. """函数注解:这是一个倒计时函数"""
3. print("开始倒计时")
4. while n > 0:
5. n -= 1
6. print("倒计时结束")
7. return "我执行完啦"
8.
9. # 使用方法1
10. countdown = record_time(countdown) # countdown是 record_time 函数中返回的 inner 函数
11. res = countdown(1000000) # 执行 inner 函数,将 inner 函数的返回值给 res
12. print(res)
- 使用方法2:
1. @record_time # 对人类更友好的语法糖写法
2. def countdown(n):
3. """函数注解:这是一个倒计时函数"""
4. print("开始倒计时")
5. while n > 0:
6. n -= 1
7. print("倒计时结束")
8. return "我执行完啦"
9.
10. res = countdown(10000000) # 被装饰完的 countdown 虽然看起来和调用方式都是 countdown,其实是它的本质是一个 inner 函数
11. print(res)
语法1和语法2的原理是一样的,写法2是语法糖的写法,语法糖是指那些没有给计算机语言添加新功能或特性的语法结构。
- 语法糖的书写规范:紧贴在被装饰对象的上方书写,语法糖和被装饰对象之间不可以有代码
- 语法糖的内部原理:语法糖会自动把装饰对象的名字当成参数传给装饰器函数调用
函数装饰器有什么作用
装饰器本质上是一个python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能。装饰器的返回值也是一个函数对象,它适用于有切面需要的场景。比如:插入日志、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
1. def first(fun):
2. def second():
3. print('start')
4. fun()
5. print('end')
6. print fun.__name__
7. return second
8.
9. def man():
10. print('i am a man()')
11.
12. f = first(man)
13. f()
等价于
1. 1. def first(func):
2. 2. def second():
3. 3. print('start')
4. 4. func()
5. 5. print('end')
6. 6. print (func.__name__)
7. 7. return second
8. 8.
9. 9. @first
10. 10. def man():
11. 11. print('i am a man()')
12. 12.
13. 13. man()
14. ------ 结果 ------
15. Start
16. I am a man()
17. End
18. man
上面的这段代码和之前的代码的作用一模一样。区别在于之前的代码直接“明目张胆”的使用first函数去封装函数,而上面这个用了【语法糖】来封装man函数。在上述代码中【@first】在man函数的上面,表示对man函数使用first装饰器。【@】是装饰器的语法,【first】是装饰器的名称。
一句话解释什么样的语言能够使用装饰器
函数可以作为参数传递的语言,可以使用装饰器。
Python中的作用域?
语言区分作用域,是为了复用变量名。引入作用域,相当于给变量划分了各自的‘隔离区’,在不同的‘隔离区’里,查找变量变得容易。Python中,变量的作用域取决于其定义的位置。当python遇到一个变量的话它会按照这个顺序进行搜索(由内向外):
局部作用域(Local)–>嵌套作用域(Enclosing locals)–>全局作用域(Global)–>内置作用域(Built-in)
- 局部作用域:它的范围是一个函数/方法的内部。函数的参数,函数内定义的各种变量或对象都属于局部作用域
- 嵌套作用域:主要用在有嵌套函数的场景,用来实现闭包,定义在函数中,嵌套函数外,且被嵌套函数引用的变量
- 全局作用域:它的范围是当前模块的任何位置。模块中的顶层区域导入的其它模块、定义的全局变量、函数和类,都属于全局作用域
- 内置作用域:它的范围是所有模块的任何位置。python内置的数据类型、内置函数、标准异常等都属于内置作用域。
什么是闭包
闭包指的是在函数内部定义了另一个函数,并返回了这个作为函数的对象,同时还保存了外层函数的状态信息。这个嵌套函数可以引用外层函数的变量和参数,而且外层函数返回的是这个嵌套函数的引用。这种在函数内部定义函数并返回的方式称为闭包。闭包延伸了嵌套函数的作用域,其中主要指在函数定义中引用但不在函数定义体中定义的非全局变量。
- 创建闭包必须满足以下条件:
- 必须有一个嵌套函数的前提下(内嵌函数:函数里面定义的函数)
- 内嵌函数必须引用外部函数中的变量【包括外部函数中的参数】
- 外部函数的返回值必须是内嵌函数【函数作为一等对象的体现:把函数像普通变量一样返回】