Python学习笔记(十) 迭代器、生成器、装饰器

这篇文章主要介绍 Python 中几个常用的高级特性,用好这几个特性可以让自己的代码更加 Pythonic 哦


1、迭代器

什么是迭代呢?简单来说,迭代是访问元素集合的一种方式,关于迭代有两个概念需要我们理解:

  • 可迭代对象 (Iterable):一个实现了 __iter__() 方法的对象

  • 迭代器对象 (Iterator):一个实现了 __iter__()__next__() 方法的对象

由此我们知道,迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象,例如:

列表类型、集合类型是可迭代对象 (有实现 __iter__()),但不是迭代器对象 (没实现 __next__())

而生成器、文件对象是可迭代对象 (有实现 __iter__()),且也是迭代器对象 (有实现 __next__())

from collections.abc import Iterable # 可迭代对象
from collections.abc import Iterator # 迭代器对象

li = [i for i in range(2)]
print(isinstance(li, Iterable)) # True
print(isinstance(li, Iterator)) # False
di = dict()
print(isinstance(di, Iterable)) # True
print(isinstance(di, Iterator)) # False

ge = (i for i in range(2))
print(isinstance(ge, Iterable)) # True
print(isinstance(ge, Iterator)) # True
fi = open('File', 'wt')
print(isinstance(fi, Iterable)) # True
print(isinstance(fi, Iterator)) # True

一般来说,人们会使用 for 循环和 next() 方法遍历可迭代对象和迭代器对象

for 循环可作用于可迭代对象和迭代器对象,而 next() 方法只能作用于迭代器对象

注意,迭代器对象只能迭代一次,并且在迭代结束时,会抛出 StopIteration 异常以表示迭代终止

# 使用 for 循环
li = [i for i in range(2)]
ge = (i for i in range(2))

for i in li:
    print(i, end = ' ') # 0 1 

# for 能自动捕获并处理 StopIteration 异常,以判断迭代终止
for i in ge:
    print(i, end = ' ') # 0 1 
# 使用 next 方法
li = [i for i in range(2)]
ge = (i for i in range(2))

print(next(li)) # TypeError: 'list' object is not an iterator

# next 每次往前读取一个数据,直至到达末尾时抛出 StopIteration 异常
print(next(ge)) # 0
print(next(ge)) # 1
print(next(ge)) # StopIteration

2、生成器

在上面我们可以知道,使用列表生成式可以快速创建一个列表:

li = [i for i in range(100000)]
sys.getsizeof(li) # 412236

但是这样有一个缺点,那就是它会直接生成列表中的所有元素,占用大量内存空间

而使用生成器就能解决这个问题,生成器并不会直接生成全部元素,而是保存一个生成元素的算法

当需要返回结果的时候,生成器才会去计算该元素的值并返回,这种特性也被称为惰性计算或延迟计算

ge = (i for i in range(100000))
sys.getsizeof(ge) # 64

(1)创建生成器

一是使用生成器表达式,它类似于列表生成式,只需将列表生成式中的中括号替换成小括号即可

ge = (i for i in range(5))
print(type(ge)) # <class 'generator'>

二是使用生成器函数,它类似于普通函数,只需将普通函数中的 return 语句替换成 yield 语句即可

def counter(max_num):
    count = 0
    while count < max_num:
        yield count
        count += 1
            
ge = counter(5)
print(type(ge)) # <class 'generator'>

我们需要将生成器函数的调用赋值给一个变量,那么该变量就是一个生成器

每次调用生成器获取一个元素时,都会执行一次生成器函数中的代码,直至遇到 yield 或者 return 语句

  • yield   语句会返回一个值并挂起函数执行,下一次再调用生成器时会从当前位置继续执行
  • return 语句会马上终止生成器的执行,抛出 StopIteration 异常

(2)调用生成器

一是使用 next() 全局方法,每次调用 next() 方法都会返回一个数据,直至调用结束

ge = (i for i in range(2))

print(next(ge)) # 0
print(next(ge)) # 1
print(next(ge)) # StopIteration

二是使用 send() 内置方法,每次调用 send() 方法都会返回一个数据,并允许向生成器内部发送信息

值得注意的是,第一次调用时需要使用 next() 或者 send(None),不能使用 send() 发送一个其它值

def counter(max_num):
    count = 0
    while count < max_num:
        msg = yield count
        print(msg)
        count += 1
            
ge = counter(3)

print(ge.send(None))
# 0
print(ge.send('Hello'))
# Hello
# 1
print(ge.send('World'))
# World
# 2
print(ge.send('!'))
# StopIteration

三是使用 for 循环,实际上生成器也是一种特殊的迭代器

ge = (i for i in range(2))
for i in ge:
    print(i)

# 0
# 1

3、装饰器

在讲解装饰器前,我们先来解释一下什么是闭包?先看闭包的定义:

如果在一个内部函数中对外部作用域(但不是全局作用域)的变量进行引用,那么该内部函数称为闭包

def external(x):
    def internal(y):
        return x + y
    return internal

func1 = external(5)
print(func1(10)) # 15

func2 = external(10)
print(func2(10)) # 20

# 以上面的代码为例,internal 是内部函数,external 是外部函数
# 在内部函数 internal 中对外部作用域(但不是全局作用域)的变量 x 进行引用
# 那么这时我们可以称内部函数 internal 为闭包

装饰器实际上就是一个闭包,它接收一个函数作为参数,返回一个经过装饰的函数

def decorator(func):
    def wrapper(*args, **kwargs):
        print('装饰器 前处理')
        func()
        print('装饰器 后处理')
    return wrapper

@decorator
def func():
    print('原有操作')

func()

# 装饰器 前处理
# 原有操作
# 装饰器 后处理

实际上,我们还可以为一个函数添加多个装饰器,注意观察它们之间的执行顺序:

def decorator1(func):
    def wrapper(*args, **kwargs):
        print('装饰器1 前处理')
        func()
        print('装饰器1 后处理')
    return wrapper

def decorator2(func):
    def wrapper(*args, **kwargs):
        print('装饰器2 前处理')
        func()
        print('装饰器2 后处理')
    return wrapper

@decorator1
@decorator2
def func():
    print('原有操作')

func()

# 装饰器1 前处理
# 装饰器2 前处理
# 原有操作
# 装饰器2 后处理
# 装饰器1 后处理

装饰器常常用于日志功能,下面是一个例子:

import time
from functools import wraps

def logger(func):
    @wraps(func) # 添加 functools.wraps 可以防止原有函数自身的信息丢失
    def wrapper(*args, **kwargs):
        currTime = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
        print(
            '[%s] %s is called, with parameters %s, %s' %
            (currTime, func.__name__, args, kwargs)
        )
        return func(*args, **kwargs)
    return wrapper

@logger
def func(x, y):
   return x + y

res = func(3, 4)

# [2021-03-12 11:50:33] func is called, with parameters (3, 4), {}

装饰器还常用于计时功能,下面是一个例子:

import time
from functools import wraps

def timer(func):
    @wraps(func) # 添加 functools.wraps 可以防止原有函数自身的信息丢失
    def wrapper(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        print(end_time - start_time)
    return wrapper

@timer
def func():
    time.sleep(1)

func()

# 1.0003883838653564
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值