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

1. 迭代器

Python的迭代器和生成器是一个非常好的能够构建数据管道的方式,试想一下,如果全部的数据都一股脑的读入内存,那么当数据量极大的时候内存绝对会爆掉的,这个时候迭代器和生成器的优势就极大地体现出来了,因为迭代器和生成器一个函数,每次都利用函数记住取到了哪个位置的数据,下次取得时候直接从上次取出的地方再取数据即可。

构建迭代器时最重要的就是需要重写两个方法,__iter____next__StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 next() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。下面我构建一个迭代器当作示例:

class MyNumbers:
    def __init__(self, list):
    	# 获取传入的列表
        self.list = list
        # 当前的索引
        self.index = 0
        # 列表的长度,即索引的界限
        self.n = len(list)


    def __iter__(self):
    	# 返回迭代器本身
        return self


    def __next__(self):
        if self.index < self.n:
            num = self.list[self.index]
            self.index += 1
            return num
        else:
            raise StopIteration


itera_list = MyNumbers([1,4,6,9])
for i in itera_list:
    print(i)

以上迭代器输出为:

1
4
6
9

2. 生成器

生成器与迭代器其实差不多,可以理解为生成器是迭代器的子类,是用 yield 关键字来定义的。一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。当函数执行结束时,generator 自动抛出 StopIteration 异常,表示迭代完成。在 for 循环里,无需处理 StopIteration 异常,循环会正常结束。

虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。

下面我来示例一下用生成器生成一个1-10数字的平方的函数:

def gengerate():
    for i in range(1, 11):
        yield i **2

for i in gengerate():
    print(i)

可以看到,生成器本质上就是一个迭代器的再封装。

3. 装饰器

在了解装饰器前,我们先来讲一讲Python中的闭包,因为装饰器本来就是闭包的一个应用。

3.1 闭包

闭包的概念如下:

在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。

简单来说,闭包就是外部函数中定义一个内部函数,内部函数引用外部函数中的变量,外部函数的返回值是内部函数,格式如下:

def fun1(arg1):
	def fun2(arg2):
		# some function with arg1
		return value
	return fun2

闭包有以下的特征:

  1. 嵌套函数
  2. 内层函数引用了外层函数的变量
  3. 内层函数作为返回值返回给外层函数

接下来我们使用一个加法函数来实现一个简单的闭包进行示例。

def fun1(arg1):
    def fun2(arg2):
        return arg1 + arg2
    return fun2

add_3 = fun1(3)
r1 = add_3(10)
r2 = add_3(12)
print(r1) # 13
print(r2) # 15

执行结果为 r1=13,r2=15 ,可以看到,其中的 add_3 相当于就是内层函数 fun2 的一个实例化,使用的时候直接往实例化后的函数传入值即可得到最后的结果。

从上面的结果可以看到,单纯的闭包使用其实作用以及必要性都不大,最多看上去有点帅而已,但是,没必要。但是,接下来我们要将的装饰器就是闭包的一个应用,能够简化很多的代码。

3.2 python参数传递

在讲装饰器之前再介绍一个,Python中的参数传递。

很多情况下我们写的代码都是针对的知道有多少个参数的情况下,但是,还是有一些情况,即不知道参数有多少的情况会出现,这种情况我们一般采用的是数组的方式来进行参数的传递,但是,Python中有更为简单的方式来解决这一类的参数传递问题。

  1. 打包成元组
    当你传入的参数个数不知道多少的时候,可以将参数一股脑的全部都塞到函数里面去,然后在函数里面使用 *args 来进行接收,这样,Python会自动将传递的参数全部打包到元组 args 中,想要得到参数只需要访问元组就行,示例如下:

    def add(*args):
        for i in args:
            print(i)
    
    add(1, 2, 3, 4, 5, 6, 9, 78)
    

    该示例会将传入的参数 1, 2, 3, 4, 5, 6, 9, 78 挨个打印。

  2. 打包成字典
    当你想不知道多少个参数,且想要为每一个参数命名时,可以使用这种类型的参数传递,传递参数时使用 head='round' 形式,接收时用 **kwargs 进行接收。

    def add(**kwargs):
        for key, value in kwargs.items():
            print('{}={}'.format(key, value))
    
    add(one=12, tow='amgds', three='drogon')
    
  3. 综合传递
    元组传参和字典传参已经能够满足大部分传参场景。综合传参指的是这两种的组合,在很多源码中我们都经常看到这样的组合。

    def add(*args, **kwargs):
    	for i in args:
            print(i)
        for key, value in kwargs.items():
            print('{}={}'.format(key, value))
    
    add(1, 2, 3, 4, 5, 6, 9, 78, one=12, tow='amgds', three='drogon')
    

    有人问万一是 add(1,2,one=12,5,6,tow='amgds') 这种结构Python怎么解决呢。这个问题大可不必担心,会报错的。因为Python规定了如果写了这种 ne=12 字典传参的形式,那么这种形式必须放在最后,即后面如果还有参数那也必须是这种形式,否则会报错。

3.3 一般的装饰器

装饰器有点类似于JAVA中的注解,主要用于插入日志、性能测试、事务处理、缓存、权限校验等场景。

首先我们来看看一般的装饰器是怎么写的,下面我们写一个统计运行时间的装饰器。

import time

def fun1(func):
    def fun2(*args, **kwargs):
        t1 = time.time()
        func(*args, **kwargs)
        t2 = time.time()
        print(t2-t1)
    return fun2


@fun1
def add(a, b):
    time.sleep(5)
    return a + b

if __name__ == '__main__':
    add(1,2)

其中,上面的 @fun1 就相当于 add=fun1(add) ,如果看了上面的闭包,是不会想到这就是闭包的样式,所以,装饰器其实就是闭包的一种使用。这里的 @fun1 实际是将整个函数作为参数传进去,再将其返回而已。

3.4 带参数的装饰器

既然装饰器相当于java中的注解,那么装饰器能不能像注解 @RequestMapping("/hello2") 一样传值呢?

其实是可以的,虽然装饰器只能接收一个参数,并且还是函数类型,但是这种实现的话需要在装饰器外面再进行一次函数嵌套,即三层函数嵌套。示例代码如下:

def operate(flag):
    def decorator(fn):
        def inner(num1, num2):
            if flag == "+":
                print("加法结果如下")
            elif flag == "-":
                print("减法结果如下")
            result = fn(num1, num2)
            return result
        return inner
    # 返回装饰器
    return decorator


# 使用装饰器装饰函数
@operate("+")
def add(a, b):
    result = a + b
    return result

print(add(1, 3))

这种实现其实有些偏麻烦了,下节讲了类装饰器后这种形式其实可以改为类装饰器的形式。

3.5 类装饰器

类装饰器,顾名思义,就是一个将装饰器写成一个类的形式。我们来看下面的例子进行讲解。

import time

class operate():
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        start = time.time()
        result = self.func(*args, **kwargs)
        end = time.time()
        print('执行用时: {}'.format(end - start))
        return result


@operate
def add(a, b):
    time.sleep(2)
    result = a + b
    return result

print(add(1, 3))

先别急,我们来仔细看看代码的。

上述装饰器类的装饰代码 @operate 实际上就相当于 add=operate(add) 相当于就是将类 operate 实例化了,实例化后,问题就来了,既然 add 已经成为了一个类的实例化,那么我们肯定是不能使用 add(1,3) 来运行一个实例化的,但是我们却需要使用 add(1,3) 这种形式来运行怎么办呢?

这种情况就需要使用 __call__ 方法了。只要在创建类型的时候定义了 __call__ 方法,这个类型就是可调用的,即使用 add(1,3) 调用实例化后,实例化的参数会传递到 __call__ 方法中,然后运行 __call__ 方法中的内容。

3.6 带参数的类装饰器

前面我讲过,可以用类装饰器实现带参数的装饰器。事实上,当遇到装饰器看不懂时,我们不妨将其拆开来看,即拆解成为其等价的形式。带参数的类装饰也是如此,假如我们实现的是例子与前面3.4节的例子一样,在函数 add 前面写一个类装饰器 @operate('+') ,这不就等价于 add=operate('+')(add) 吗,这样一来,思路瞬间就清晰了。

class operate():
    def __init__(self, operation):
        self.operation = operation

    def __call__(self, func):
        def inner(*args, **kwargs):
            if self.operation == '+':
                print('正在运行加法')
            elif self.operation == '-':
                print('正在运行减法')
            result = func(*args, **kwargs)
            return result
        return inner


@operate("+")
def add(a, b):
    result = a + b
    return result
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值