这篇文章主要介绍 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