【Python】函数再了解(参数内存地址相关,迭代器,生成器、闭包、装饰器)

本文介绍了Python中关于变量内存地址的概念,解释了为什么相同值的变量id不同,以及如何通过_id_()方法检查对象的内存地址。接着,详细阐述了迭代器的工作原理,包括_iter_()和_next_()方法的作用,并提供了判断对象是否可迭代的两种方法。文章还讨论了列表不是迭代器的原因,并展示了如何通过iter()函数获取列表的迭代器。此外,讲解了生成器的特性和工作流程,以及闭包的定义和创建条件。最后,介绍了装饰器的概念,如何使用装饰器为函数添加新功能,并展示了装饰器的实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、参数内存地址相关【面试题】

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 num

        num -= 1


g = 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_func
        return pow(x, n)  # 注意这里引用了外部函数的变量n

    return 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_func

out_def(2)      # 相当于调用了inner_func函数,所以out_def(2)等价于inner_func(2)
print(out_def(2))
上面就是一个完整闭包的使用

六、装饰器

什么是装饰器?

就是在不修改源代码的基础上,给函数增加新的功能;

装饰器会将被装饰的函数当作参数传递给装饰器的函数;


【添加装饰器,条件:】

  1. 存在闭包;

  1. 存在需要被装饰的函数


# 装饰器
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()的地址值;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LKsTaRt~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值