“Life is short, You need Python” – Bruce Eckel
Environment
- OS: macOS Mojave
- Python version: 3.7
- IDE: Jupyter Notebook
文章目录
0. 写在前面
Python 基础系列学习笔记最后一篇,记录一些必要的问题,包括深浅拷贝问题、迭代器与可迭代对象、生成器、装饰器。
至此,Python 基础内容就搞定了,能够应付基本的需求。
1. 拷贝
Python 中的拷贝分为浅拷贝和深拷贝,首先还要知晓一下可变类型和不可变类型。
1.1 不可变与可变
- 不可变的数据类型包括数字、字符串、元组,在它们的生命周期,本身且其中元素的id保持不变
例 字符串
string = '12'
ori_id = id(string)
string += '3'
cur_id = id(string)
print(ori_id == cur_id) # False
例 元组
tp = (1, 2)
ori_id = id(tp)
tp += (3,)
cur_id = id(tp)
print(ori_id == cur_id) # False
- 可变的类型包括列表、元组、集合,在它们的生命周期中,本身的id不变的情况下,其中的元素id可以发送变动。
例 列表
ls = [1, 2]
ori_id = id(ls)
ls.append(3)
cur_id = id(ls)
print(ori_id == cur_id) # True
例 字典
dc = {0: 1, 1: 2}
ori_id = id(dc)
dc[2] = 3
cur_id = id(dc)
print(ori_id == cur_id) # True
接着,以这个列表为例,了解直接赋值、浅拷贝和深拷贝的机制
ls = [0, ['python', 'java'], (6, 66, 666), {1: 'one', 2: 'two'}, 'string']
1.2 直接赋值
赋值创建一个新变量指向原变量引用的对象
1.3 浅拷贝
浅拷贝仅复制对象的第一层变量
1.4 深拷贝
深拷贝将复制对象的所有层变量,直到引用了不可变类型的变量为止
2. 迭代器
迭代器作为 Python 三器(迭代器、生成器、装饰器)之一,要理解这个东西,先得搞清楚迭代的概念,再分清可迭代对象和迭代器的区别。
2.1 迭代的概念
迭代(iteration) 是为了接近某个目标而不断进行重复的过程,每次重复被称为一次迭代,每次迭代的结果作为下一次迭代的初始。
2.2 可迭代对象
能够被 for
结构进行迭代的对象称为可迭代对象,如字符串、列表、元组、字典、集合。这些对象含有定义了 __iter__
魔法方法。
可以使用 collections.abc
中的 Iterable
类判断对象是否为可迭代的
from collections.abc import Iterable
ls = [1, 2, 3]
print(ls.__iter__)
# <method-wrapper '__iter__' of list object at 0x138157d48>
print(isinstance(ls, Iterable)) # True
作用于可迭代对象的常用函数
max(iterable, *[, default=obj, key=func])
和min(iterable, *[, default=obj, key=func])
求最值
ls = [0, 1, 2, 3, 4]
tp = (0, 1, 2, 3, 4)
dc = {0: 'zero', 1: 'one', 2: 'two', 3: 'three', 4: 'four'}
st = {0, 1, 2, 3, 4}
print(max(ls), max(tp), max(dc), max(st)) # 4 4 4 4
还可以获得字典最大的值对应的键
dc = {0: 9, 1: 5, 2: 2, 3: 7}
print(max(dc)) # 3
print(max(dc, key=dc.get)) # 0
sum(iterable, start=0, /)
求和,这里传入的对象只能是以整数或浮点数为元素的组合数据类型
ls = [0, 1, 2, 3, 4]
tp = (0, 1, 2, 3, 4)
dc = {0: 'zero', 1: 'one', 2: 'two', 3: 'three', 4: 'four'}
st = {0, 1, 2, 3, 4}
print(sum(ls), sum(tp), sum(dc), sum(st)) # 10 10 10 10
sorted(iterable, /, *, key=None, reverse=False)
对序列类型进行排序,返回一个新的排序后的列表对象
ls = [0, 1, 2, 3, 4]
tp = (0, 1, 2, 3, 4)
dc = {0: 'zero', 1: 'one', 2: 'two', 3: 'three', 4: 'four'}
st = {0, 1, 2, 3, 4}
print(sorted(ls)) # [0, 1, 2, 3, 4]
print(sorted(tp)) # [0, 1, 2, 3, 4]
print(sorted(dc)) # [0, 1, 2, 3, 4]
print(sorted(st)) # [0, 1, 2, 3, 4]
reversed(sequence)
,只能传入有序的数据类型,返回一个以倒序排列后的迭代器
reversed('string') # <reversed at 0x12ebcd9b0>
2.3 迭代器
迭代器一定是可迭代对象,而可迭代对象不一定是迭代器。
两者的相同之处在于都了定义了 __iter__
魔法方法;不同之处在于,
- 迭代器还需要定义
__next__
魔法方法,使之能够被传入next
函数; - 迭代器是惰性计算的,无法得到具体的长度,其没有
__len__
属性。
可以通过 iter
函数由可迭代对象得到迭代器
from collections.abc import Iterable, Iterator
ls = [1, 2, 3]
ls_iter = iter(ls)
print(isinstance(ls_iter, Iterable)) # True
print(isinstance(ls_iter, Iterator)) # True
print(ls_iter.__next__)
# <method-wrapper '__next__' of list_iterator object at 0x137e32cc0>
可以使用 next
函数得到迭代器的下一个返回值,事实上这就是 for
迭代的机制
# for 迭代的机制
ls_iter = iter(ls)
while True:
try:
elem = next(ls_iter)
except StopIteration:
break
pass # 执行体
尝试获得迭代器的长度,将报错
ls = [1, 2, 3]
ls_iter = iter(ls)
len(ls_iter)
# TypeError: object of type 'list_iterator' has no len()
2.3.1 迭代器的意义
ls = [i for i in range(100000)]
print(sys.getsizeof(ls)) # 824464
ls_iter = iter(range(100000))
print(sys.getsizeof(ls_iter)) # 48
迭代器的目的是将对迭代对象更好地进行迭代。
2.3.2 文件对象是迭代器
在Python基础(七)文件中提到,可以对文件对象进行迭代,这里再次证实文件对象是一个迭代器
from collections.abc import Iterator
# 打开同目录下的 zen.txt 文件
with open('zen.txt', 'r', encoding='utf-8') as f:
print(isinstance(f, Iterator))
# True
2.3.3 range 函数返回的对象不是迭代器
range
函数用于生产等差数字序列,注意,它返回的对象是可迭代的,但并不是迭代器。
print(isinstance(range(10), Iterable)) # True
print(isinstance(range(10), Iterator)) # False
可以称 range
函数的返回对象为一个懒序列,它是一种序列,但不包括任何内存中的内容,而是通过计算来回答问题。
range 函数返回的对象具有以下几个区别于迭代器的性质
numbers = range(3)
- 有长度
len(numbers) # 3
- 可索引
numbers[1] # 1
- 不可进行 next 操作
next(numbers) # 报错
# TypeError: 'range' object is not an iterator
- 当元素取完后会从头开始
for i in numbers:
print(i)
print()
for i in numbers: # 依然可以继续从头开始迭代到结尾
print(i)
2.3.4 创建迭代器
相较于 iter
函数外,以下函数更常被用作得到迭代器
enumerate(iterable, start=0)
返回一个以二元组(原序列每个元素的索引,元素)为元素的迭代器
ls = [1, 2, 3]
ls_enumerated = enumerate(ls)
print(isinstance(ls_enumerated, Iterator)) # True
while True:
try:
print(next(ls_enumerated))
except StopIteration:
break
# (0, 1)
# (1, 2)
# (2, 3)
zip(self, /, *args, **kwargs)
返回一个 n 元组的迭代器,元组以传入的 n 个可迭代对象的相同索引位置对应的元素
# 当传入的可迭代对象长度不同时,以最短者为准
ls_zipped = zip([1, 2, 3, 4], [5, 6, 7], [8, 9])
print(isinstance(ls_zipped, Iterator)) # True
while True:
try:
print(next(ls_zipped))
except StopIteration:
break
# (1, 5, 8)
# (2, 6, 9)
3. 生成器
3.1 生成器的概念
生成器与迭代器的区别:
- 迭代器是从一系列元素中依次获取;
- 生成器强调按规则生成值的序列,是“无中生有”的意思。
生成器对象同样有 __iter__
和 __next__
方法,说明
- 生成器是一种特殊的迭代器
from collections.abc import Iterator
gen = (i for i in range(10))
print(type(gen)) # <class 'generator'>
print(isinstance(gen, Iterator)) # True
- 可以被
for
迭代作用
gen = (i for i in range(5))
for elem in gen:
print(elem)
# 0
# 1
# 2
# 3
# 4
- 可以被传入
next
获取下一个元素
gen = (i for i in range(5))
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 2
3.2 创建生成器
生成器可以通过生成器解析式和生成器函数创建。生成器解析式已在Python基础(三)组合数据类型 Part 5.4中介绍。这里主要介绍生成器函数的使用。
用 yield
代替普通函数中的 return
即可
例 生成斐波那契数列
def fib():
'''一个生成斐波那契数列的生成器'''
a, b = 0, 1
while True:
a, b = b, a + b
print("Just before 'yield'")
yield a
print("Just after 'yield'")
gen = fib()
print(type(gen)) # <class 'generator'>
yield
关键字的两个作用
-
返回生成器函数的执行结果
-
保存一个生成器函数的运行状态(每次运行完
yield
语句,挂起)
例 观察斐波那契数列生成器函数的运行流程
# 第一次调用
res = next(gen)
# Just before 'yield'
print(res) # 1
# 第二次调用
res = next(gen)
# Just after 'yield'
# Just before 'yield'
print(res) # 1
# 第三次调用
res = next(gen)
# Just after 'yield'
# Just before 'yield'
print(res) # 2
# 之后同理
在生成器函数中的 yield
关键字位置定义一个赋值操作,允许调用使用生成器的 send
方法传入参数
例 定义一个素数生成器
import math
def is_prime(number):
"""判断数字是否为素数"""
if number <= 1:
return False
if number == 2:
return True
if number % 2 == 0:
return False
for divisor in range(3, int(math.sqrt(number) + 1), 2):
if number % divisor == 0:
return False
return True
def generatePrime(start=1):
"""生成素数的生成器,若传入了某一整数,则从该整数开始"""
number = start
while True:
if is_prime(number):
print("Just before 'yield'")
start = yield number # 定义一个这样的赋值操作
print("Jest after 'yield'")
if start is not None:
number = start
start = None
number += 1
prime_generator = generatePrime(5)
对于定义了 = yield
的生成器函数,next
函数相当于运行 send
方法,传入 None
。也就是说,send
方法除了完成了 next
函数的任务以外,还将参数传入给生成器。
# 第一次调用使用 next 函数
res = next(prime_generator)
# Just before 'yield'
print(res) # 5
# 第二次使用 send 方法,传入 16,就会从 16 开始向后生成
res = prime_generator.send(16)
# Jest after 'yield'
# Just before 'yield'
print(res) # 17
# 再次调用 send 方法,传入 9,又从 9 开始向后生成
res = prime_generator.send(9)
# Jest after 'yield'
# Just before 'yield'
print(res) # 11
注意,生成器的运行流程为先运行至 yield
处,再发生可能的参数接收。因此,第一次调用生成器函数时,其并未在 yield
处挂起,无法进行变量接收。也就是说,第一次调用时,必须使用 next
函数或对 send
方法不传入参数。
prime_generator = generatePrime()
# 第一次调用
prime_generator.send(6) # 报错
# TypeError: can't send non-None value to a just-started generator
4. 装饰器
4.1 定义装饰器
装饰器能够在不改动程序中函数的调用方式的前提下,添加某些功能。
例 为函数的运行计时
import time
def timer(func):
def inner():
print('inner is running...')
since = time.time()
func()
print('time taken:', time.time() - since)
print('inner done.')
return inner
def foo():
print('foo is being called.')
time.sleep(3)
foo = timer(foo)
foo()
# inner is running...
# foo is being called.
# time taken: 3.0018310546875
# inner done.
根据闭包(参考Python基础(五)函数 Part 5)可知,foo 包含了 inner 函数和 timer 的环境,如接收的参数 func。
foo = timer(foo)
之后,调用 foo 函数除了能够实现功能还达到了计时的效果。foo = timer(foo)
这个操作就像是使用 timer 对 foo 进行了装饰。
4.2 装饰器语法糖
装饰器语法糖语法
@decorator
def func(*args):
pass
例1 等价于上例
import time
def timer(func):
def inner():
print('inner is running...')
since = time.time()
func()
print('time taken:', time.time() - since)
print('inner done.')
return inner
@timer # 相当于执行了 foo = timer(foo)
def foo():
print('foo is being called.')
time.sleep(3)
foo()
# inner is running...
# foo is being called.
# time taken: 3.0018310546875
# inner done.
例2 被装饰的函数接收参数
import time
def timer(func):
def inner(*args, **kwargs):
print('inner is running...')
since = time.time()
func(*args, **kwargs)
print('{} function done. Time taken: {}'.format(func.__name__, time.time() - since))
return inner
@timer
def foo(n):
print('foo is being called.')
time.sleep(n)
foo(5)
# inner is running...
# foo is being called.
# foo function done. Time taken: 5.003183126449585
例3 被装饰的函数返回值不为 None
import time
def timer(func):
def inner(*args, **kwargs):
print('inner is running...')
since = time.time()
ret = func(*args, **kwargs)
print('{} function done. Time taken: {}'.format(func.__name__, time.time() - since))
return ret
return inner
@timer
def foo(n):
print('foo is being called.')
time.sleep(n)
return "I'm awake."
ret = foo(5)
# inner is running...
# foo is being called.
# foo function done. Time taken: 5.004913091659546
print(ret) # I'm awake.
例4 装饰器接收参数传入,实现按需求记录函数运行的时间或打印出当前日期
import time
import datetime
def timer(manner):
def outer(func):
def inner(*args, **kwargs):
print('inner is running...')
since = time.time()
ret = func(*args, **kwargs)
if manner == 'timer':
print('{} function done. Time taken: {}'.format(
func.__name__, time.time() - since))
elif manner == 'datetime':
print('{} function done.\nToday is: {}'.format(
func.__name__, datetime.date.today()))
return ret
return inner
return outer
@timer(manner='datetime')
def foo(n):
print('foo is being called.')
time.sleep(n)
return "I'm awake."
ret = foo(3)
# inner is running...
# foo is being called.
# foo function done.
# Today is: 2020-03-10
print(ret) # I'm awake.
4.3 保持本真
虽然 timer 对 foo 进行了装饰,但实际上,调用装饰器后返回的 foo 与原本的 foo 并不相同
import time
def timer(func):
def inner():
since = time.time()
func()
print('time taken:', time.time() - since)
return inner
def foo():
print('foo is being called.')
time.sleep(3)
foo = timer(foo)
print(foo.__name__) # inner
使用在 functools 中的 @wraps
可以让 foo 被装饰后名字不变
import time
from functools import wraps
def timer(func):
@wraps(func)
def inner():
since = time.time()
func()
print('time taken:', time.time() - since)
return inner
def foo():
print('foo is being called.')
time.sleep(3)
foo = timer(foo)
print(foo.__name__) # foo
4.4 用类作装饰器
import time
class Timer:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
since = time.time()
self.func(*args, **kwargs)
print('Time taken:', time.time() - since)
@Timer # 等价于 foo = Timer(foo)
def foo():
print('foo is being called.')
time.sleep(3)
foo()
# foo is being called.
# Time taken: 3.00045108795166
4.5 内置装饰器
记录一下 Python 中常用的内置装饰器:property、staticmethod 和 classmethod。
4.5.1 property 装饰器
property 函数见Python基础(六)类和对象 Part 3.3。
例 使用 property 对访问、设置、删除实例属性的函数进行装饰
class Human:
def __init__(self):
self._skin_colour = 'Yellow'
@property
def skin_colour(self):
"""Skin colour"""
print('Getting the attribute...')
return self._skin_colour
@skin_colour.setter
def skin_colour(self, colour):
print('Setting the attribute...')
self._skin_colour = colour
@skin_colour.deleter
def skin_colour(self):
print('Removing the attribute.')
del self._skin_colour
human = Human()
print(Human.skin_colour.__doc__)
# Skin colour
print(human.skin_colour)
# Getting the attribute...
# Yellow
human.skin_colour = 'Brown'
# Setting the attribute...
del human.skin_colour
# Removing the attribute
注意,不要让被装饰的方法和属性重名
class Human:
def __init__(self):
self._skin_colour = 'Yellow'
@property
def skin_colour(self):
"""Skin colour"""
print('Getting the attribute...')
return self.skin_colour
@skin_colour.setter
def skin_colour(self, colour):
print('Setting the attribute...')
self.skin_colour = colour
@skin_colour.deleter
def skin_colour(self):
print('Removing the attribute.')
del self.skin_colour
human = Human()
print(human.skin_colour)
# Getting the attribute...
# Getting the attribute...
# Getting the attribute...
# .
# .
# .
# 报错 RecursionError
4.5.2 staticmethod 装饰器
该装饰器使类方法变为静态,不会绑定到实例对象上,以节省内存开销。
class Human:
def walk(self):
print("{} is walking..".format(self))
@staticmethod
def run(): # 不传入 self
print('running...')
human = Human()
Human.run() # running...
human.run() # running...
4.5.3 classmethod 装饰器
该装饰器将类中定义的实例方法变为类方法
class Human:
def walk(self):
print("{} is walking..".format(self))
@classmethod
def run(cls): # 传入 cls
print('{} is running...'.format(cls))
human = Human()
human.run()
# <class '__main__.Human'> is running...
Human.run()
# <class '__main__.Human'> is running...