Python迭代器、生成器和装饰器

一、迭代器

1、迭代器简介

迭代操作是访问集合元素的一种方式,是 Python最强大的功能之一。

迭代器是用来迭代取值的工具,是一个可以记住遍历的位置的对象。

迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

1.1 可迭代对象(Iterable)

通过索引的方式进行迭代取值,实现简单,但仅适用于序列类型:字符串,列表,元组。

对于没有索引的字典、集合等非序列类型,必须找到一种不依赖索引来进行迭代取值的方式。

迭代器提供了一种通用的且不依赖于索引的迭代取值方式的功能。

字符串、列表、元组、字典、集合、打开的文件都是可迭代对象。

1.2 迭代器对象(Iterator)

迭代器对象是内置有 iter和 next方法:

  • iter():返回迭代器对象,即得到的仍然是迭代器本身。
  • next():返回迭代器中的下一个元素值。

2、创建迭代器

2.1 创建迭代器

list = [1, 2, 3, 4]
# 创建迭代器对象
# it = iter(list)
it = list.__iter__()

print(type(it)) # 

# 输出迭代器的下一个元素
print(next(it))  # 1
print(next(it))  # 2
print(it.__next__())  # 3

了解了迭代器,就可以不依赖索引迭代取值了。

使用while循环的实现遍历:

import sys  # 引入 sys 模块

list = [1, 2, 3, 4]
it = iter(list)  # 创建迭代器对象

while True:
    try:
        print(next(it))
    except StopIteration:
        # sys.exit()
        break

上述 while循环可以使用 for语句遍历简写:in后可以跟任意可迭代对象

list = [1, 2, 3, 4]
it = iter(list)  # 创建迭代器对象

for x in it:
    print(x, end=" ")

2.2 创建类迭代器

把一个类作为一个迭代器使用时,需要在类中实现两个方法:__iter__() __next__()

  • __iter__() 方法:返回一个特殊的迭代器对象, 这个迭代器对象实现了__next__()方法并通过 StopIteration 异常标识迭代的完成。
  • __next__() 方法:返回下一个迭代器对象。
    Python 的构造函数为 __init__(),它会在对象初始化的时候执行。

**示例代码如下:**创建一个返回数字的迭代器,初始值为 1,逐步递增 1。

class MyClass:
    def __iter__(self):
        self.a = 1 # 初始化一个变量
        return self

    def __next__(self):
        x = self.a
        self.a += 1  # 每次迭代加1
        return x


obj = MyClass()
myIter = iter(obj)

print(next(myIter))
print(next(myIter))
print(next(myIter))
print(next(myIter))

**StopIteration:**

StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况。

__next__()方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。

示例代码如下:在第 4次迭代后停止执行:

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

    def __next__(self):
        if self.a <= 4:
            x = self.a
            self.a += 1
            return x
        else:
            raise StopIteration # 结束迭代


obj = MyClass2()
myIter = iter(obj)

for x in myIter:
    print(x)

Python迭代器、生成器和装饰器_第1张图片

二、生成器

1、生成器简介

在 Python 中,使用了 yield 关键字的函数被称为生成器(generator)。

生成器是一个返回迭代器的函数,只能用于迭代操作,可理解为生成器就是一个自定义迭代器。

yield 能够临时挂起当前函数,记下其上下文(包括局部变量、待决的 try catch 等),将控制权返回给函数调用者。当下一次再调用其所在生成器时,会恢复保存的上下文,继续执行剩下的语句,直到再遇到 yield 或者退出为止。

生成器有一个基本的方法:

  • next() :返回迭代器中的下一个元素值。

生成器调用过程:

在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值,并在下一次执行 next() 方法时从当前位置继续运行。

1.2 yield 和 return 区别

  • 函数一旦遇到 return就结束了,销毁上下文(弹出栈帧),将控制权返回给调用者。
  • yield只能在函数内使用
  • yield可以保存函数的运行状态,挂起函数,用来返回多次值。值的类型没有限制。

2、创建生成器

示例代码如下:使用 yield 实现斐波那契数列。

# 创建生成器函数实现 - 斐波那契
def fibonacci(n):
    # n 代表数列的个数
    a, b, counter = 0, 1, 0
    while counter <= n:
        yield a # 返回
        a, b = b, a + b
        counter += 1 # 个数加1

# f 是一个迭代器,由生成器返回生成
f = fibonacci(10)

print()
print(type(f)) # 
print('=====for遍历====')
for value in f:
    print(value, end=" ")

print()
f = fibonacci(10)
print('=====while遍历====')
while True:
    try:
        # 获取迭代器中的下一个元素值
        value = next(f)
        print(value, end=" ")
    except StopIteration:
        break

Python迭代器、生成器和装饰器_第2张图片

三、装饰器

1、装饰器简介

装饰器是给现有的模块增添新的小功能,可以对原函数进行功能扩展,而且还不需要修改原函数的内容,也不需要修改原函数的调用。即装饰器,就是可以让我们拓展一些原有函数没有的功能。

装饰器的使用符合了面向对象编程的开放封闭原则

  • 对扩展开放:意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
  • 对修改封闭:意味着类一旦设计完成,就可以独立工作,而不要对类进行任何修改。

2、创建无参装饰器

2.1 原函数是无参数

假设原函数(被装饰的函数)是无参数的,对其创建一个不带参数的无参装饰器函数。

示例代码如下:

import time

def say1():
    print("我是say1, Hello Python")
    time.sleep(2)

def say2():
    print("我是say2, Hello Python")
    time.sleep(2)

# 计时装饰器
def count_time(func):

    # 装饰函数名:可以自定义
    def wrapper():
        t1 = time.time()
        func()
        print("执行时间为:", time.time() - t1)

    return wrapper


if __name__ == '__main__':
    say1 = count_time(say1)  # 因为装饰器 count_time(say1) 返回的是装饰函数对象 wrapper,
    say1()  # 执行 say1()就相当于执行wrapper()

    print("---------")
    say2 = count_time(say2)
    say2()

注意: 这里的wrapper装饰函数名是可以自定义的,只要你定义的函数名,跟return的函数名是相同即可。

Python迭代器、生成器和装饰器_第3张图片

2.2 原函数有参数

假设原函数(被装饰的函数)是有参数的,对其创建一个带参数的有参装饰器函数。

示例代码如下:

import time


def say1(name):
    print("我是%s, Hello Python" % name)
    time.sleep(2)


def say2(name):
    print("我是%s, Hello Python" % name)
    time.sleep(2)


# 计时装饰器
def count_time(func):
    # 装饰的函数名:可以自定义
    def wrapper(*args, **kwargs):
        t1 = time.time()
        func(*args, **kwargs)
        print("执行时间为:", time.time() - t1)

    return wrapper


if __name__ == '__main__':
    say1 = count_time(say1)  # 因为装饰器 count_time(say1) 返回的是装饰函数对象 wrapper,
    say1("say111")  # 执行 say1()就相当于执行wrapper()

    print("---------")
    say2 = count_time(say2)
    say2("say222")

注意:上面装饰器函数wrapper的参数为 *args和 **kwargs,表示可以接受任意参数。

Python迭代器、生成器和装饰器_第4张图片

2.2 装饰器的语法糖@

在 Python项目中,难免会看到@符号的代码,这个 @符号就是装饰器的语法糖。

如果装饰器的语法糖@来实现定义装饰器,我们就可以直接调用这个原函数名。其实底层实现也是创建装饰器函数。本质上没有变化。方便了我们的使用。

(1)原函数是无参数,使用语法糖创建装饰器

import time

# 计时装饰器
def count_time(func):

    # 装饰的函数名:可以自定义
    def wrapper():
        t1 = time.time()
        func()
        print("执行时间为:", time.time() - t1)

    return wrapper

# count_time必须定义在前面
@count_time
def say1():
    print("我是say1, Hello Python")
    time.sleep(2)

@count_time
def say2():
    print("我是say2, Hello Python")
    time.sleep(2)

if __name__ == '__main__':
    say1()  # 使用用语法糖之后,就可以直接调用该函数来执行扩展的装饰功能。
    
    print("---------")
    say2()

(2)原函数是有参数,使用语法糖创建装饰器

import time

# 计时装饰器
def count_time(func):
    # 装饰的函数名:可以自定义
    def wrapper(*args, **kwargs):
        t1 = time.time()
        func(*args, **kwargs)
        print("执行时间为:", time.time() - t1)

    return wrapper

# count_time必须定义在前面
@count_time
def say1(name):
    print("我是%s, Hello Python" % name)
    time.sleep(2)

@count_time
def say2(name):
    print("我是%s, Hello Python" % name)
    time.sleep(2)


if __name__ == '__main__':
    say1("say111")  # 使用用语法糖之后,就可以直接调用该函数来执行扩展的装饰功能。

    print("---------")
    say2("say222")

2、创建有参装饰器

装饰器也是函数,既然是函数,那么就可以进行参数传递。

示例代码如下:假设我们在使用装饰器的时候,传入一些备注的msg信息。

import time

# 计时装饰器
# 参数自行定义,这里使用的是默认值参数。
def count_time_args(msg=None):
    def count_time(func):
        def wrapper(*args, **kwargs):
            t1 = time.time()
            func(*args, **kwargs)
            print(f"[{msg}]执行时间为:", time.time() - t1)

        return wrapper

    return count_time


@count_time_args("say1")
def say1(name):
    print("我是%s, Hello Python" % name)
    time.sleep(2)


@count_time_args("say2")
def say2(name):
    print("我是%s, Hello Python" % name)
    time.sleep(2)


if __name__ == '__main__':
    say1("say111")

    print("---------")
    say2("say222")

基于原来的 count_time函数外部再包一层用于接收参数的 count_time_args函数,接收回来的参数就可以直接在内部的函数里面调用了。

执行结果如下:

Python迭代器、生成器和装饰器_第5张图片

3、类装饰器

在 Python中,其实也可以用类来实现装饰器的功能,称之为类装饰器。

类装饰器的实现是调用了类里面的 __call__函数。

当我们将类作为一个装饰器,工作流程:

  • 通过__init__()方法初始化类
  • 通过__call__()方法调用真正的装饰方法

类装饰器的写法比我们写装饰器函数更加简单。

3.1 无参类装饰器

示例代码如下:

import time

# 定义无参数的类装饰器
class MyClassCountTimeDecorator:
    def __init__(self, func):
        self.func = func
        print("执行类的__init__方法")

    def __call__(self, *args, **kwargs):
        print('进入__call__函数')
        t1 = time.time()
        self.func(*args, **kwargs)
        print("执行时间为:", time.time() - t1)


@MyClassCountTimeDecorator
def say1():
    print("我是say1, Hello Python")
    time.sleep(2)


def say2():
    print("我是say2, Hello Python")
    time.sleep(2)


if __name__ == '__main__':
    say1()
    print('--------------')
    say2()

Python迭代器、生成器和装饰器_第6张图片

3.2 有参类装饰器

当装饰器有参数的时候,__init__() 函数就不能传入func(func代表要装饰的函数)了,而 func是在__call__函数调用的时候传入的。

import time

# 定义带参数的类装饰器
class MyClassCountTimeDecorator:
    def __init__(self, arg1, arg2):  # init()方法里面的参数都是装饰器的参数
        print('执行类Decorator的__init__()方法')
        self.arg1 = arg1
        self.arg2 = arg2

    def __call__(self, func):  # 因为装饰器带了参数,所以接收传入函数变量的位置是这里
        print('执行类Decorator的__call__()方法')

        def wrapper(*args):  # 装饰器的函数名字可以自定义,只要跟return的函数名相同即可
            print('执行wrap()')
            print('装饰器参数:', self.arg1, self.arg2)
            print('执行' + func.__name__ + '()')
            func(*args)
            print(func.__name__ + '()执行完毕')

        return wrapper


@MyClassCountTimeDecorator('say', '1')
def say1(a1, a2, a3):
    print("我是say1, 传入的参数:", a1, a2, a3)
    time.sleep(2)


@MyClassCountTimeDecorator('say', '2')
def say2(a1, a2):
    print("我是say2, 传入的参数:", a1, a2)
    time.sleep(2)


if __name__ == '__main__':
    say1("鲁班", "后裔", "妲己")
    print('--------------')
    say2("赵云", "赵子龙")

Python迭代器、生成器和装饰器_第7张图片

注意:多理解无参与有参数的装饰器/类装饰器区别,加深理解代码。

四、装饰器的顺序

一个函数可以被多个装饰器进行装饰,那么装饰器的执行顺序是怎么调用的?

答案是:在装饰器修饰完的函数,在执行的时候先执行原函数的功能,然后再由里到外依次执行装饰器的内容。

示例代码如下:

def Decorator_1(func):
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
        print('我是装饰器1')

    return wrapper


def Decorator_2(func):
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
        print('我是装饰器2')

    return wrapper


def Decorator_3(func):
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
        print('我是装饰器3')

    return wrapper


@Decorator_1
@Decorator_2
@Decorator_3
def say1():
    print("我是say1, Hello Python")


if __name__ == '__main__':
    say1()

Python迭代器、生成器和装饰器_第8张图片

– 求知若饥,虚心若愚。

你可能感兴趣的:(Python,Python装饰器)