这篇文章主要介绍 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
文章知识点与官方知识档案匹配,可进一步学习相关知识