一、迭代器

1.1 什么是迭代?

迭代就像是你在一家自助餐厅中不停地填满你的盘子,直到你吃饱为止。每一次迭代,你都会拿起一份食物,放进盘子里,然后继续下一次迭代,直到你满足了你的胃口。

在Python中,我们也可以通过迭代来处理相似的数据集。迭代是一个重复执行的过程,每次迭代都依赖于上一次的结果。就像是你在自助餐厅中不断填满你的盘子,每一次迭代都是在上一次的基础上继续添加食物。

常见的可迭代对象有

  • 集合数据类型,就像是自助餐厅中的各种美食,有各种各样的菜肴,比如list、tuple、dict、set、str等;

  • 生成器(generator),就像是自助餐厅中的厨师,他们会根据你的需求来制作食物。生成器可以是一个生成器对象,也可以是一个带有yield关键字的生成器函数。无论是哪种形式,它们都可以用来生成一系列的值,供你迭代使用。

在Python中,如果给定一个列表、元组、字符串…,我们可以通过for循环来遍历,这种遍历我们称为迭代(Iteration),如下所示:

# 遍历列表
for i in [1,2,3]:
    print(i)

print("----------这是一个分割线----------")

# 遍历字符串
for i in "Python":
    print(i)

输出结果:

1
2
3
----------这是一个分割线----------
P
y
t
h
o
n

1.2 如何判断迭代对象?

判断一个对象是否可迭代就像判断一只鸭子是否会游泳一样。我们可以使用 isinstance() 函数来进行判断,就像用一把望远镜观察鸭子一样。首先,我们需要导入 from collections import Iterable 模块,就像准备一把望远镜一样。

然后,我们可以使用 isinstance(变量, Iterable) 来判断一个变量是否为可迭代对象,就像用望远镜观察鸭子是否会游泳一样。

如果返回 True,那么这个变量就是可迭代的,就像看到鸭子在水中游泳一样。如果返回 False,那么这个变量就不是可迭代的,就像看到一只鸭子不会游泳一样。

所以,记住,要判断一个对象是否可迭代,就像观察一只鸭子是否会游泳一样,使用 isinstance() 函数来进行判断,就像用望远镜观察鸭子一样。

from collections import Iterable

print(isinstance([1,2,3,4,5], Iterable))
print(isinstance((1,2,3,4,5), Iterable))
print(isinstance("Python", Iterable))
print(isinstance(123456, Iterable))

输出结果:

True
True
True
False

1.3 创建迭代器

迭代器就像是一个超级记忆力的对象,它能够帮助我们记住遍历的位置。

迭代器对象从集合的第一个元素开始,一直遍历到最后一个元素。它可不会后退哦,只能往前走。

要把一个类变成一个迭代器,我们需要在类中实现两个特殊的方法:__iter__()__next__()

  • __iter__() 方法要返回对象本身,也就是 self

  • __next__() 方法要返回下一个数据,如果没有数据了,就得抛出一个叫做 StopIteration 的异常。

嗯,就像是一场冒险,我们的迭代器会帮助我们一步步地探索集合中的元素,直到最后一个。记住,迭代器是我们的超级向导!

创建一个返回数字的迭代器,初始值为 1,逐步递增 1,在 5 次迭代后停止执行:

class MyNumbers:
    def __iter__(self):
        self.a = 1
        return self

    def __next__(self):
        if self.a <= 5:
            x = self.a
            self.a += 1
            return x
        else:
            raise StopIteration


myclass = MyNumbers()
myiter = iter(myclass)

for x in myiter:
    print(x)

输出结果:

1
2
3
4
5

二、生成器

2.1 什么是生成器?

有时候我们只需要一部分数据,但是却要创建一个超级大的列表,这真是太浪费内存了!比如说,我们只需要前面几个元素,后面那一大堆元素就白白占用了空间。那有没有一种方式,可以在循环的过程中边计算边生成元素呢?

当然有啦!Python中有一种神奇的机制叫做生成器generator。它可以让我们在循环的同时,动态地生成元素,而不需要一次性创建整个列表。这样就能节省大量的空间啦!

生成器的工作原理其实很简单,就像你在走路的时候边走边数数一样。在函数内部,我们使用关键字yield来返回一个值,每次调用函数就相当于执行一次next()方法,生成器就会产生一个新的元素。所以,生成器其实就是一种特殊的迭代器。

嗯,就像是你边走边数数,每走一步就数一个数,生成器也是边循环边计算,每次循环就生成一个新的元素。这样,我们就可以灵活地处理无限个变量和有限内存之间的矛盾啦!而且,生成器还能提高内存使用效率呢!

所以,生成器就像是一个智能的数数机器,能够帮助我们节省空间,同时又能够动态地生成元素。

2.2 创建生成器

方式1

要创建一个生成器,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成()

# 创建一个列表
L = [x*2 for x in range(5)]
print(L)
print("----------这是一个分割线")

# 创建一个生成器
G = (x*2 for x in range(5))
print(G)
print(next(G))
print(next(G))
print(next(G))
print(next(G))
print(next(G))

输出结果:

[0, 2, 4, 6, 8]
----------这是一个分割线
<generator object <genexpr> at 0x000002AE7AAE45F0>
0
2
4
6
8

创建L和G的区别仅在于最外层的[](),L是一个列表,而G是一个生成器,我们可以直接打印出 L 的每一一个元素,但我们怎么打印出G的每一个元素呢?如果要-一个一个打印出来,可以通过next()函数获得生成器的下一个返回值。

方式2

generator就像是一位非常强大的魔术师,可以帮助我们解决各种复杂的问题。有时候,我们遇到的算法太复杂,用普通的for循环就像是用筷子夹西瓜,力不从心。这时候,我们可以借助函数的魔法来实现我们的目标。

就好像是在一场魔术表演中,魔术师手中的魔杖一挥,一串闪亮的魔法序列就呈现在我们面前。在Python中,我们可以使用函数来创造出这样的魔法序列。通过定义一个函数,我们可以根据自己的需要生成一系列的值,而不需要事先将它们全部存储在内存中。

这就像是在一场魔术秀中,魔术师从空气中凭空创造出了一串彩色的气球。而在Python中,我们可以通过生成器函数来实现类似的效果。生成器函数可以让我们按需生成值,而不需要一次性生成所有的值。

所以,无论是用类似列表生成式的for循环,还是借助函数的魔法,generator都能帮助我们解决各种复杂的问题。就像是一位强大的魔术师,它可以帮助我们实现那些看似不可能的任务。让我们在Python的世界中,释放想象力,创造出属于我们自己的魔法!

比如,著名的斐波拉契数列(Fibonacci) , 除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34...

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

def creatNum():
    a, b = 0, 1
    for i in range(5):
        print(b)
        a, b = b, a + b


creatNum()
# 输出结果
# 1
# 1
# 2
# 3
# 5

函数里有yield,就变为生成器:

def creatNum():
    print("---------start-----------")
    a, b = 0, 1
    for i in range(5):
        print("----1----")
        yield b
        print("----2----")
        a, b = b, a + b
        print("----3----")
    print("---------stop-----------")

a = creatNum()
print(a)
print(next(a))
print("----------这是一个分割线-----------")
print(next(a))

输出结果:

<generator object creatNum at 0x000002000CB30EB0>
---------start-----------
----1----
1
----------这是一个分割线-----------
----2----
----3----
----1----
1

如上所示,我们可以看出a是一个生成器对象,当第一次调用next(a)的时候,生成器从上往下执行,执行到yield b的时候停止并返回b的值;再次调用next(a)的时候,程序根据原来停止的地方接着往下执行,循环执行到yield b的时候又停止并返回b的值。

方式3

在上面的例子,我们在循环过程中不断调用yield, 就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。同样的,把函数改成generator后,我们基本上从来不会用 next() 来获取下一个返回值,而是直接使用for循环来迭代,并且不需要关心 StopIteration 的错误:

def creatNum():
    print("---------start-----------")
    a, b = 0, 1
    for i in range(5):
        yield b
        a, b = b, a + b
    print("---------stop-----------")

a = creatNum()

for num in a:
    print(num)

输出结果:

---------start-----------
1
1
2
3
5
---------stop-----------

当我们使用 for 循环调用 generator 时,有时候会发现无法获取到 generator 中 return 语句的返回值。

这时候,我们需要捕获 StopIteration 错误,并从错误的 value 属性中获取返回值。

就像是在玩捉迷藏,我们要抓住 StopIteration 错误,然后从中找到宝藏,也就是我们想要的返回值。

这个宝藏可能隐藏在 StopIteration 错误的 value 中,我们需要小心翼翼地打开它,才能得到我们想要的结果。

所以,记住,在使用 for 循环调用 generator 时,要时刻准备好捕获 StopIteration 错误,以获取隐藏在其中的宝藏!

def creatNum():
    print("---------start-----------")
    a, b = 0, 1
    for i in range(5):
        yield b
        a, b = b, a + b
    print("---------stop-----------")

a = creatNum()

while True:
    try:
        x = next(a)
        print("value:%d"%x)
    except StopIteration as e:
        print("生成器返回值:%s" %e.value)
        break

输出结果:

---------start-----------
value:1
value:1
value:2
value:3
value:5
---------stop-----------
生成器返回值:None

2.3 生成器使用案例

处理大量数据

import time

start = time.time()
print(sum([i for i in range(100000000)]))
end = time.time()
print("使用列表推导式的耗时", end - start)
print("------------这是一个分割线-----------")

start1 = time.time()
print(sum(i for i in range(100000000)))
end1 = time.time()
print("使用生成器的耗时", end1 - start1)

输出结果:

4999999950000000
使用列表推导式的耗时 9.606479167938232
------------这是一个分割线-----------
4999999950000000
使用生成器的耗时 7.590640544891357

当我们面对越来越大的数字时,使用生成器可谓是一种聪明的选择。你可以把生成器想象成一个智慧的小精灵,只在你需要的时候才会出现,而不会占据大量的内存空间。相比之下,列表推导式就像是贪吃的怪兽,一口气把所有的数字都吞下肚子,很可能会导致内存爆炸!

所以,如果你担心内存问题,生成器是一个更加稳健的选择。它们只会迭代一次,不会在内存中存储大量的数据。就像是你只需要一口一口地吃掉一块巧克力,而不是一下子把整个巧克力工厂都装进肚子里。

所以,让我们聪明地选择生成器吧!它们不仅能够节省时间,还能保护我们的内存健康。就像是一位智慧的管家,为我们提供高效的服务,而不是像一个贪婪的怪兽,让我们陷入内存的深渊。

读取大文件

你知道吗,处理大文件就像是吃巨无霸汉堡一样,一口咬下去可能会让你噎住。但是,我们可以用一种聪明的方式来解决这个问题,就像是小口小口地吃汉堡一样。

在Python中,我们可以利用生成器的神奇力量来实现这个目标。生成器就像是一个可以暂停和重新开始的超级英雄,它可以让我们按需读取指定大小的文件,避免一次性读取内容过多导致的内存溢出等问题。

想象一下,你有一个超级大的文件,里面装满了文字。你可以使用生成器来创建一个迭代器,每次只读取一小部分内容,就像是一口一口地吃汉堡一样。这样,你就可以避免一次性读取整个文件,而是按需读取,节省了宝贵的内存空间。

让我们来看看具体的代码:

def read_large_file(file_path, chunk_size):
    with open(file_path, 'r') as file:
        while True:
            chunk = file.read(chunk_size)
            if not chunk:
                break
            yield chunk

# 使用示例
file_path = 'path/to/your/file.txt'
chunk_size = 1024  # 每次读取的字节数
for chunk in read_large_file(file_path, chunk_size):
    # 在这里处理每个小块的内容
    print(chunk)

通过这种方式,你可以轻松地处理大文件,就像是吃汉堡一样,一口一口地享用,而不会让你噎住。记住,生成器是你的超级英雄,帮助你解决大文件读取的难题!

三、装饰器

3.1 什么是闭包?

闭包就像是一个间谍,能够偷偷地窥视其他函数内部的秘密变量。想象一下,在Python的世界里,只有函数内部的小伙伴们才能够偷窥到局部变量的内容,而这些小伙伴们就是闭包。闭包就像是一个桥梁,将函数内部和函数外部连接起来。

咱们来打个比方吧:假设我们有一个函数叫做"特工007",这个函数会返回一个"特工008"。这个"特工008"就是我们的闭包,它可以偷偷地获取"特工007"内部的情报,而且还能和外界进行联系。是不是有点豁然开朗了呢?

闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

通俗来讲:比如我们调用一个带有返回值的函数 x,此时函数 x 为我们返回一个函数 y,这个函数 y 就被称作闭包,这么一说是不是豁然开朗了

def test(number):

    print("--1--")

    def test_in(number2):
        print("--2--")
        print(number+number2)

    print("--3--")
    return test_in

ret = test(100)
print("-"*30)
ret(1)
ret(100)
ret(200)

输出结果:

--1--
--3--
------------------------------
--2--
101
--2--
200
--2--
300

可以看出来,我先调外面的函数传一个默认的 number 的值;用 ret 去指向返回内部函数的引用,接下来在用 ret 的时候就会在之前调用外面函数的基础上计算

3.2 装饰器

装饰器,就像给房子装修一样,不改变原有的功能,只是增添一些额外的装饰。比如,你想给房子增加隔音功能,你可以在墙上加一层隔音板,而不是拆掉墙重新建造。

在程序中也是一样,我们可以使用装饰器来给函数增加一些额外的功能,而不改变原有的函数。比如,我们想要记录函数的执行时间,我们可以使用装饰器来实现。

装饰器是程序开发中非常常用的工具,它可以大大提高开发效率,所以在Python面试中经常会被问到。

但是对于初次接触装饰器的人来说,可能会觉得有点绕,就像在装修房子时绕过了隔音这个环节一样。

但是装饰器是程序开发的基础知识,如果连这个都不会,那就别说自己会Python了。不过别担心,看完下面的内容,你一定能够掌握装饰器的使用。

装饰器可以基于函数实现,也可以基于类实现,但是使用方式基本是固定的。

下面是使用装饰器的基本步骤:

  1. 定义一个装饰函数(或类)

  2. 定义一个业务函数

  3. 在业务函数上添加 @装饰函数/类名,就像给房子贴上装饰一样

记住,装饰器不会改变原有函数的功能,只是为其增添一些额外的装饰。就像给房子增加隔音一样,墙还是原来的墙,只是多了一层隔音板。所以,使用装饰器可以让你的代码更加优雅和灵活,就像装修后的房子一样,美观又实用!

案例(需求在不动原函数的基础上添加新的功能):

def w1(func):
    """装饰器函数"""
    def inner():
        func()
        print("这是添加的新功能")
    return inner

@w1  # 等同于调用函数的时候上方加上:f1 = w1(f1)
def f1():
    """业务函数"""
    print("---f1---")


@w1
def f2():
    """业务器函数"""
    print("---f2---")


f1()
f2()

输出结果:

---f1---
这是添加的新功能
---f2---
这是添加的新功能

Python 中还支持多个装饰器同时使用,如下:

def w1(fn):
    """装饰器函数"""

    def inner():
        print("---1---")
        return "<b>" + fn() + "</b>"

    return inner


def w2(fn):
    """装饰器函数"""

    def inner():
        print("---2---")
        return "<i>" + fn() + "</i>"

    return inner


@w1
@w2
def f1():
    """业务函数"""
    print("---3---")
    return "hello python"


ret = f1()
print(ret)

输出结果:

---1---
---2---
---3---
<b><i>hello python</i></b>

思路图如下:

从上可以看出test3指向w1就先打印---1---@w1中的fn()指向w2;调w2就接着打印---2---,w2中的fn()指向test3…;所以@w1先等@w2调用完再调用,输出结果<b><i>hello python</i></b>

3.3 装饰器执行时间

案例1

def w1(fn):
    """装饰器函数"""
    print("---正在装饰---")
    def inner():
        print("---正在验证---")
        fn()

    return inner



@w1 # 只要Python解释器执行到这个代码,就开始自动进行装饰,而不是等到调用的时候才装饰
def f1():
    """业务函数"""
    print("---2---")
    return "hello python"

输出结果:

---正在装饰---

从上可以看出,代码执行到@w1 就开始装饰了,我们并没有调用f1()都输出了---正在装饰---,我们调用的是装饰完后的结果。

案例2

def w1(fn):
    """装饰器函数"""
    print("---正在装饰1---")

    def inner():
        print("---正在验证---")
        fn()

    return inner


def w2(fn):
    """装饰器函数"""
    print("---正在装饰2---")

    def inner():
        print("---正在验证---")
        fn()

    return inner


@w1
@w2
def f1():
    """业务函数"""
    print("---3---")
    return "hello python"

输出结果:

---正在装饰2---
---正在装饰1---

从上可以看出,@w1在最上面,下面需要是一个函数,可下面是@w2,必须先等@w2装饰完再装饰,所以先输出---正在装饰2---

3.4 装饰器传参

传递两个参数案例:

def w1(fn):
    """装饰器函数"""
    print("---正在装饰---")

    def inner(a, b):  # 如果a, b没有定义,那么会导致19行代码调用失败
        print("---正在验证---")
        fn(a, b)  # 如果没有把a, b当实参进行传递,那么会导致调用13行的函数失败

    return inner


@w1
def f1(a, b):
    """业务函数"""
    print(a + b)


f1(10, 20)

输出结果:

---正在装饰---
---正在验证---
30

不定长参数案例:

def w1(fn):
    """装饰器函数"""
    print("---正在装饰---")

    def inner(*args, **kwargs):  # 如果a, b没有定义,那么会导致19行代码调用失败
        print("---正在验证---")
        fn(*args, **kwargs)  # 如果没有把a, b当实参进行传递,那么会导致调用13行的函数失败

    return inner


@w1
def f1(a, b):
    """业务函数"""
    print(a + b)


@w1
def f2(a, b, c):
    """业务函数"""
    print(a + b + c)


f1(10, 20)
f2(10, 20, 30)

输出结果:

---正在装饰---
---正在装饰---
---正在验证---
30
---正在验证---
60

3.5 装饰器返回值

def w1(fn):
    """装饰器函数"""
    print("---正在装饰---")

    def inner():
        print("---正在验证---")
        ret = fn()  # 保存返回来的字符串
        return ret  # 把字符串返回到20行的调用处

    return inner


@w1
def test():
    """业务函数"""
    print("---test---")
    return "这是原函数返回值"


ret = test()  # 需要用参数来接收返回值
print(ret)

输出结果:

---正在装饰---
---正在验证---
---test---
这是原函数返回值

3.6 通用装饰器

def w1(fn):
    """装饰器函数"""

    def inner(*args, **kwargs):
        print("---记录日志---")
        ret = fn(*args, **kwargs)  # 保存返回来的字符串
        return ret  # 把字符串返回到20行的调用处

    return inner


@w1
def test1():
    """不带返回值"""
    print("---test1---")


@w1
def test2():
    """带返回值"""
    print("---test2---")
    return "这是原函数返回值"


@w1
def test3(a):
    """业务函数"""
    print("---test3中的数据:%d---" % a)


ret1 = test1()
print(ret1)
ret2 = test2()
print(ret2)
ret3 = test3(10)
print(ret3)

输出结果:

---记录日志---
---test1---
None
---记录日志---
---test2---
这是原函数返回值
---记录日志---
---test3中的数据:10---
None

3.7 装饰器带参数

def func_arg(arg):
    def func(funtionName):
        def func_in():
            print("输出给装饰器传入的参数:%s" % arg)
            if arg == "hello":
                funtionName()
                funtionName()
            else:
                funtionName()

        return func_in

    return func


@func_arg("hello")
def test():
    print("---test---")


@func_arg("haha")
def test2():
    print("---test2---")


test()
test2()

输出结果:

输出给装饰器传入的参数:hello
---test---
---test---
输出给装饰器传入的参数:haha
---test2---

步骤

  • 1、先执行func_arg("hello")函数,这个函数return的结果是func这个函数的引用

  • 2、@func

  • 3、使用@functest进行装饰

作用:传递的参数不同,可以用于判断做不同的事情