【Python】函数再了解(参数内存地址相关,迭代器,生成器、闭包、装饰器)
一、参数内存地址相关【面试题】
id() 查看所在内存中的地址
v1 = "小胖"
addr = id(v1)
print(addr) # 140691049514160
v1 = [11, 22, 33]
v2 = [11, 22, 33]
v3 = v1
print("v1的id值:", id(v1))
print("v2的id值:", id(v2))
print("v3的id值:", id(v3))

看到这可能有小伙伴有疑惑,为什么v1和v2的值是一样的为什么他们的id不相同?
这是因为它们的值虽然一样,但是是赋给了不同的变量,所以它们的id值不一样;
也可以这么理解:你叫张三,他也叫张三,你们是同一个人吗?当然不是,而v1,v3为什么一样?
因为他只是将v1传递给了v3,所以id值相等;
【记住一句话:函数执行传参时,传递的是内存地址。 】
二、迭代器
首先我们可以使用for语句和while语句遍历任何可迭代的数据类型,而for语句之所以能够遍历是因为幕后有迭代器在默默支持,for语句在执行的时候会悄咪咪执行两件事:
1、调用这个位置的对象的_iter_()方法;

a = 2233 # a没有_inter_()方法,所以它是不可迭代的
for i in a: # 强行将它放入for循环中会报错,TypeError: 'int' object is not iterable,# a是一个int类型的对象不是可迭代的;print(a)

除了这样的“笨办法”还有另一种比较“优雅”的办法来判断它是不是一个可以迭代的对象:
判断是否为可迭代的对象
方法一:
from collections.abc import Iterable # 导入Iterable这个类a = 2233
b = [1,2,3]
print(isinstance(a, Iterable)) # 返回False,说明他不是一个可迭代的对象
print(isinstance(b, Iterable)) # 返回True,说明他是一个可迭代的对象

方法二:
我们可以使用 Python 内置的 hasattr() 函数来判断一个对象是不是可迭代的:
c = 123
d = [1, 2, 3]
print(hasattr(c, '__iter__')) # 返回False,说明他不是一个可迭代的对象
print(hasattr(d, '__iter__')) # 返回True,说明他是一个可迭代的对象

2、对于可迭代的对象,继续做第二件事,把_iter_()方法的返回值当成一个对象,并调用这个对象的_next_()方法;这个对象必须要有_next_()方法,否则程序会崩溃;

【可以总结为:
1、如果一个可迭代对象具有_next_()方法,那么它就是一个迭代器;
2、只要这个对象具有_iter_()fang方法和_next_()方法,它就是一个迭代器

】
判断是否为迭代器
from collections.abc import Iterator # 导入Iterator这个类b = [1, 2, 3]
print(isinstance(b, Iterator)) # 判断b是否是一个迭代器;返回False,他不是一个迭代器

可得,列表不是一个迭代器?到这可能有小伙伴有疑惑,列表是可迭代的,可它居然不是迭代器?
这是因为列表没有_next_()方法;
列表作为“大哥”迭代是“不屑”于亲历亲为的,会交给“小弟”去做,这个“小弟“才是迭代器,
接下来有请“小弟”出场:
python中有个内置函数iter(),用来得到可迭代对象的迭代器(如果它有的话);
from collections.abc import Iterator # 导入Iterator这个类b = [1, 2, 3]
print(isinstance(b, Iterator)) # 判断b是否是一个迭代器;返回False,他不是一个迭代器
xd = iter(b) # 返回列表这个可迭代对象的迭代器<list_iterator object at 0x000002280734B220>
print(xd)
print(isinstance(xd, Iterator)) # 返回True,说明xd是一个迭代器

顺便说一句,对于迭代器可以直接调用_next_()方法,来查看每一次的返回值,在没有后续元素时抛出 StopIteration 异常。
from collections.abc import Iterator # 导入Iterator这个类b = [1, 2, 3]
print(isinstance(b, Iterator)) # 判断b是否是一个迭代器;返回False,他不是一个迭代器
xd = iter(b) # 返回列表这个可迭代对象的迭代器<list_iterator object at 0x000002280734B220>
print(xd)
print(isinstance(xd, Iterator)) # 返回True,说明xd是一个迭代器
print('---------------------------------------------------')
print(xd.__next__()) # 返回结果为1 调用_next_()方法,来查看每一次的返回值,
print(next(xd)) # # 返回结果为2 调用内置函数next()方法,来查看每一次的返回值,
print(xd.__next__()) # 3

没有后续元素抛出异常

让自己的类支持迭代
【看过迭代器协议的幕后机制,让我们自己的类支持迭代就很容易,

】


四、生成器
生成器是一个特殊的迭代器,因为他也满足迭代器协议;
调用生成器函数会生成一个 生成器对象;
yield 除了可以返回相应的值,还有一个更重要的功能,即每当程序执行完该语句时,程序就会暂停执行。不仅如此,即便调用生成器函数,Python 解释器也不会执行函数中的代码,它只会返回一个生成器(对象)。
【带有 yield 的函数执行过程比较特别: 】
调用该函数的时候不会立即执行代码,而是返回了一个生成器对象;
当使用 next() (在 for 循环中会自动调用 next() ) 作用于返回的生成器对象时,函数
开始执行,在遇到 yield 的时候会『暂停』,并返回当前的迭代值;
当再次使用 next() 的时候,函数会从原来『暂停』的地方继续执行,直到遇到 yield 语
句,如果没有 yield 语句,则抛出异常;
整个过程看起来就是不断地 执行->中断->执行->中断 的过程。一开始,调用生成器函数的时候,函
数不会立即执行,而是返回一个生成器对象;然后,当我们使用 next() 作用于它的时候,它开始
执行,遇到 yield 语句的时候,执行被中断,并返回当前的迭代值,要注意的是,此刻会记住中
断的位置和所有的变量值,也就是执行时的上下文环境被保留起来;当再次使用 next() 的时候,
从原来中断的地方继续执行,直至遇到 yield ,如果没有 yield ,则抛出异常。
简而言之,就是 next 使函数执行, yield 使函数暂停。
def gen(num): # gen()生成器函数while num > 0:yield numnum -= 1g = gen(5) # g是生成器对象# 当你执行这个gen(5)的时候,它不会返回值,而会返回一个生成器对象保存到g里# 里面的yield,return都不是它的返回值first = next(g) # 只要当你对这个生成器对象使用next()函数的时候# 它才开始真正运行他的函数本体gen()for i in g:print(i)
# 在你做g=gen(5)的时候,你把num已经赋值好了为5,但是没有运行这个函数,而是生成了一个生成器对象;
当你运行next(g)的时候才开始运行gen()函数,他先判断5是不是大于0,如果大于0,把5先返回,然后这个函数暂停在这块(停在 yield num),所以这个时候first是5,然后执行下面for i in g;
而g会调用gen()这个函数体,函数继续从上次暂停的地方执行,直至遇到 yield,也就是先执行 num -= 1 ,这时num=4,再执行while num > 0,再执行 yield num,这时执行暂停,并将当前迭代值4返回给num,遇到yield,这一次的函数调用执行完成,所以i等于4;for继续循环遍历g,从上一次的暂停的位置开始执行,直到遇到yield;


五、闭包
闭包可以理解为“定义在一个函数内部的函数”。
在一个函数中嵌套定义了一个函数,内函数运用了外函数的临时变量,并且外部函数的返回值是内部函数的变量名,这样九构成了一个闭包。
【条件:】
存在函数的嵌套关系
内层函数引用了外部函数的变量
外部函数返回内层函数的地址值
【举例】
from math import pow# pow()函数,pow(a,b)表示a的二次幂def make_pow(n):def inner_func(x): # 嵌套定义了 inner_funcreturn pow(x, n) # 注意这里引用了外部函数的变量nreturn inner_func # 返回 inner_func的地址值out_def = make_pow(2)
print(out_def) # <function make_pow.<locals>.inner_func at 0x000001213F687280># 为什么明明输出的是make_pow(2),出来的却是inner_func at 0x000001213F687280># 因为外层函数make_pow()返回的是内层函数inner_func()的地址值,所以这里out_def = make_pow(2)等价于out_def = inner_funcout_def(2) # 相当于调用了inner_func函数,所以out_def(2)等价于inner_func(2)
print(out_def(2))

上面就是一个完整闭包的使用
六、装饰器
什么是装饰器?
就是在不修改源代码的基础上,给函数增加新的功能;
装饰器会将被装饰的函数当作参数传递给装饰器的函数;
【添加装饰器,条件:】
-
存在闭包;
-
存在需要被装饰的函数
# 装饰器
def welcome(func):def in_func():print("欢迎光临")func()print("in_func地址值:", in_func)print("func地址值:", func)return in_func# 添加装饰器
# 添加装饰器语法:@闭包外层函数名
@welcome
# 装饰器会把被装饰函数(login()函数)自动传递给装饰器函数(welcome())
# 所以welcome()函数中的参数func就是被装饰的函数login()
def login():print("登录成功")# 运行login()
login()
print("login地址值:", login)

因为login()在装饰器函数welcome()的内层函数in_func()里执行,所以他的地址值是in_func()函数的地址值;
func是装饰器将login()函数传递给了func,所以他的地址值是login()的地址值;