Table of Contents
第九章 有益的探索
数据类型的底层实现
奇怪的列表
1.错综复杂的复制
list_1 = [1,[22,33,44],(5,6,7),{"name":"peter","sex":"male"}]
- 浅拷贝
list_2 = list_1.copy() #浅拷贝
list_2[1].append(55)
print("list_1: ", list_1)
print("list_2: ", list_2)
list_1: [1, [22, 33, 44, 55], (5, 6, 7), {'name': 'peter', 'sex': 'male'}]
list_2: [1, [22, 33, 44, 55], (5, 6, 7), {'name': 'peter', 'sex': 'male'}]
2.列表的底层实现
引用数组的概念
引用数组的概念
列表内的元素可以分散的存储在内存中
列表存储的,实际上是这些元素的地址!!!——地址的存储在内存中是连续的
list_1 = [1,[22,33,44],(5,6,7),{"name":"peter","sex":"male"}]
list_2 = list(list_1) #浅拷贝 与list_1.copy()功能一样
(1) 新增元素
list_1.append(100)
list_2.append("n")
print("list_1: ", list_1)
print("list_2: ", list_2)
list_1: [1, [22, 33, 44], (5, 6, 7), {'name': 'peter', 'sex': 'male'}, 100]
list_2: [1, [22, 33, 44], (5, 6, 7), {'name': 'peter', 'sex': 'male'}, 'n']
list_1.append(100)
list_2.append("n")
print("list_1: ", list_1)
print("list_2: ", list_2)
list_1: [1, [22, 33, 44], (5, 6, 7), {'name': 'peter', 'sex': 'male'}, 100, 100]
list_2: [1, [22, 33, 44], (5, 6, 7), {'name': 'peter', 'sex': 'male'}, 'n', 'n']
list_1 和list_2
后面增加不同的元素
(2) 修改元素
list_1[0] = 111
list_2[0]= 222
print("list_1: ", list_1)
print("list_2: ", list_2)
list_1: [111, [22, 33, 44], (5, 6, 7), {'name': 'peter', 'sex': 'male'}, 100, 100]
list_2: [222, [22, 33, 44], (5, 6, 7), {'name': 'peter', 'sex': 'male'}, 'n', 'n']
(3) 对列表型元素进行操作
list_1[1] .remove(44)
list_2[1] += [55,66]
print("list_1: ", list_1)
print("list_2: ", list_2)
list_1: [111, [22, 33, 55, 66], (5, 6, 7), {'name': 'peter', 'sex': 'male'}, 100, 100]
list_2: [222, [22, 33, 55, 66], (5, 6, 7), {'name': 'peter', 'sex': 'male'}, 'n', 'n']
(4) 对元组型元素进行操作
元组是不可变的 当执行+操作 元组是一个新的元组
list_2[2] += (8,9)
print("list_1: ", list_1)
print("list_2: ", list_2)
list_1: [111, [22, 33, 55, 66], (5, 6, 7), {'name': 'peter', 'sex': 'male'}, 100, 100]
list_2: [222, [22, 33, 55, 66], (5, 6, 7, 8, 9), {'name': 'peter', 'sex': 'male'}, 'n', 'n']
(5) 对字典型元素进行操作
list_1[-3]["age"] = 20
print("list_1: ", list_1)
print("list_2: ", list_2)
list_1: [111, [22, 33, 55, 66], (5, 6, 7), {'name': 'peter', 'sex': 'male', 'age': 20}, 100, 100]
list_2: [222, [22, 33, 55, 66], (5, 6, 7, 8, 9), {'name': 'peter', 'sex': 'male', 'age': 20}, 'n', 'n']
- 针对不可变元素(数字、字符串、元组)的操作,都各自生效了
- 针对不可变元素(列表、集合)的操作,发生了一些混淆
3. 引入深拷贝
- 深拷贝将所有层级的相关元素全部复制,完全分开,泾渭分明,避免了上述问题
import copy
list_1 = [1, [22, 33, 44], (5, 6, 7), {"name": "peter", "sex": "male"}]
list_2 = copy.deepcopy(list_1)
list_1[-1]["age"] = 18
list_2[1].append(55)
print("list_1: ", list_1)
print("list_2: ", list_2)
list_1: [1, [22, 33, 44], (5, 6, 7), {'name': 'peter', 'sex': 'male', 'age': 18}]
list_2: [1, [22, 33, 44, 55], (5, 6, 7), {'name': 'peter', 'sex': 'male'}]
神秘的字典
1.快速的查找
import time
ls_1 = list(range(1000000))
ls_2 = list(range(500)) + [-10] * 500
start = time.time()
count = 0
for n in ls_2:
if n in ls_1:
count += 1
end = time.time()
print("查找{}个元素,在ls_1中的有{}个,共用时{}秒".\
format(len(ls_2),count,round(end-start),2))
查找1000个元素,在ls_1中的有500个,共用时6秒
import time
d = {i: i for i in range(1000000)}
ls_2 = list(range(500)) + [-10] * 500
start = time.time()
count = 0
for n in ls_2:
try:
d[n]
except:
pass
else:
count+=1
end = time.time()
print("查找{}个元素,在ls_1中的有{}个,共用时{}秒".\
format(len(ls_2),count,round(end-start),2))
查找1000个元素,在ls_1中的有500个,共用时0秒
字典的实现
通过稀疏数组来实现值的存储与访问
字典的创建过程
- 第一步: 创建一个散列表(稀疏数组 N>>n)
d = {}
- 第二步: 通过hash()计算键的散列值
print(hash("python"))
print(hash(1012))
print(hash((1,2)))
7824177075760137180
1012
3713081631934410656
d["age"] = 18 # 增加键值对的操作,首先会计算键的散列值hash("age")
print(hash("age"))
-7854890815213985751
- 第三步:根据计算的散列值确定其在散列表中的位置
极个别时候,散列值会发生冲突,则内部有相应的解决冲突的办法
- 第四步:在该位置上存入值
键值对的访问过程
d["age"]
18
-
第一步:计算要访问的键的散列值
-
第二步:根据计算的散列值,通过一定的规则,确定其在散列表中的位置
-
第三步:读取该位置上存储的值
如果存在,则返回该值 如果不存在,则报错 Key Error
3.小结
(1)宇典数据类型,通过空间換时间,实现了快速的数据查找
·也就注定了字典的空间利用效率低下
(2)因为散列值对应位置的顺序与键在字典中显示的顺序可能不同,因此表现出来字典是无序的
紧凑的字符串
通过紧凑数组实现字符串的存储
-
数据在内存中是连续存放的,效率更高,节省空间
-
思考一下,同为序列类型,为什么列表采用引用数组,而字符串采用紧凑数组
是否可变
1、不可变类型:数字、字符串、元组
在生命周期中保持内容不变
换句话说,改变了就不是它自己了(id变了)
- 不可变对象的+=操作实际上创建了一个新的对象
x = 1
y = "python"
print("x id: ", id(x))
print("y id: ", id(y))
x id: 140722067513744
y id: 2488682840624
x += 2
y += "123"
print("x id: ", id(x))
print("y id: ", id(y))
x id: 140722067513808
y id: 2487398868016
元组并不是总是不可变的
t = (1,[2])
print(t)
print(id(t))
t[1].append(3)
print(t)
print(id(t))
(1, [2])
2488716027720
(1, [2, 3])
2488716027720
2、可变类型:列表、字典、集合
-
id保持不变,但是里面的内容可以变
-
可变对象的+=操作实际在原对象的基础上就地修改
ls = [1, 2, 3]
d = {"name": "peter", "age": 18}
print("ls id: ", id(ls))
print("d id: ", id(d))
ls id: 2487399022984
d id: 2487025142264
ls += [4,5]
d_2 = {"sex": "male"}
d.update(d_2)
print("ls id: ", id(ls))
print("d id: ", id(d))
ls id: 2487399022984
d id: 2487025142264
列表操作的几个小例子
[例1] 删除列表内的特定元素
- 方法1 存在运算删除法
确定 每次运算都要从头进行变量查找 效率低下
alist = ["d","d","d","0","0","d","d","1"]
s = "d"
while True:
if s in alist:
alist.remove(s)
else:
break
print(alist)
['2', '2', '2', '4']
- 方法2 一次性遍历元素执行删除
alist = ["d","d","d","0","0","d","d","1"]
count = 0
for s in alist:
if s == "d":
alist.remove(s)
count+=1
print(alist,count)
['0', '0', 'd', 'd', '1'] 3
解决方法: 使用负向索引
alist = ["d","d","d","0","0","d","d","1"]
for i in range(-len(alist),0):
if alist[i] == "d":
alist.remove(alist[i])
print(alist)
['0', '0', '1']
[例2] 多维列表的创建
[[0]*10]
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
更简洁的语法
解析语法
ls = [[i]*10 for i in range(4)]
ls
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3]]
ls[0][0] = 1
ls
[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
1. 解析语法的基本操作— 以列表解析为例(也称列表推导)
[expression for value in iterable if conditihon]
- 三要素: 表达式,可迭代对象, if条件(可选)
ls = [[i]*10 for i in range(10) if i % 2 == 0]
ls
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
[4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
[6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
[8, 8, 8, 8, 8, 8, 8, 8, 8, 8]]
执行过程
(1) 从可迭代对象中拿出一个元素
(2) 通过 if条件(有的话),对元素进行筛选
通过筛选;传递给表达式
未通过筛选;进入(1) 进行下一次迭代
(3) 将传递给表达式的元素,带入表达式进行处理,产生一个结果
(4) 将(3)步产生的结果作为列表的一个元素进行存储
(5) 重复(1)-(4),知道迭代对象迭代结束,返回新创建的列表
等价与以下代码
result = []
for value in iterale:
if condition:
result.append(expression)
# 例 求20以内奇数的平方
square = [i**2 for i in range(1,21) if i % 2 == 1]
print(square)
[1, 9, 25, 49, 81, 121, 169, 225, 289, 361]
支持多变量
x = [1,2,3]
y = [1,2,3]
res = [i*j for i,j in zip(x,y)]
print(res)
[1, 4, 9]
支持循环嵌套
colors = ["black","white"]
sizes = ["S","M","L"]
tshirts = ["{} {}".format(color,size) for color in colors for size in sizes]
print(tshirts)
['black S', 'black M', 'black L', 'white S', 'white M', 'white L']
2.其他解析语法的例子
- 解析语法构造字典(字典推导)
squares = {i: i**2 for i in range(10)}
for k, v in squares.items():
print(k,":",v)
0 : 0
1 : 1
2 : 4
3 : 9
4 : 16
5 : 25
6 : 36
7 : 49
8 : 64
9 : 81
- 解析语法构造集合(集合推导)
squares = {i**2 for i in range(10)}
squares
{0, 1, 4, 9, 16, 25, 36, 49, 64, 81}
- 生成器表达式
squares = (i**2 for i in range(10))
squares
<generator object <genexpr> at 0x0000024301DD7148>
colors = ["black","white"]
sizes = ["S","M","L"]
tshirts = ["{} {}".format(color,size) for color in colors for size in sizes]
for tshirt in tshirts:
print(tshirt)
black S
black M
black L
white S
white M
white L
条件表达式
expr_1 if condition else expr_2
# 将变量n 的绝对值赋值给x
n = -10
x = n if n >=0 else -n
print(x)
10
条件表达式和解析语法简单使用,运行速度相对更快一些
三大神器
生成器
ls = [i**2 for i in range(1000000)]
for i in ls:
pass
缺点: 占用大量内存
生成器
(1)采用惰性计算的方式
(2)无需一次性存储海量数据
(3)一边执行一边计算,只计算每次需要的值
(4)实际上一直在执行next操作,直到无值可取
1. 生成器表达式
- 海量数据,不需存储
squares = (i**2 for i in range(1000000))
for i in squares:
pass
- 求0-100的和
无需显示存储全部数据,节省内存
sum(i for i in range(101))
5050
2.生成器函数–yidld
- 生成斐波那契数列
def fib(max):
ls = []
n, a, b = 0, 1, 1
while n < max:
ls.append(a)
a, b = b, a + b
n = n + 1
return ls
fib(10)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
中间尝试
def fib(max):
n,a,b = 0,1,1
while n < max:
print(a)
a,b=b,a+b
n=n+1
fib(10)
1
1
2
3
5
8
13
21
34
55
def fib(max):
n,a,b = 0,1,1
while n < max:
yield a
a,b=b,a+b
n=n+1
构造生成器函数
每次调用next()的时候执行,遇到yield语句返回,再次执行上次返回的yield语句处继续执行
for i in fib(10):
print(i)
1
1
2
3
5
8
13
21
34
55
迭代器
1.可迭代对象
课直接用域for循环的对象统称为可迭代对象:Iterable
(1) 列表,元组,字符串,字典,集合,文件
可以使用isistance()判断一个对象是否是Iterable对象
from collections import Iterable
print(isinstance([1,2,3],Iterable))
True
print(isinstance({"name":"peter"},Iterable))
True
print(isinstance("Python",Iterable))
True
(2)生成器
squares = (i**2 for i in range(5))
print(isinstance(squares,Iterable))
True
print(next(squares))
print(next(squares))
print(next(squares))
print(next(squares))
print(next(squares))
0
1
4
9
16
直到没有数据可取,抛出StopIteration
print(next(squares))
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-139-f5163ac9e49b> in <module>
----> 1 print(next(squares))
StopIteration:
可以被next(函数调用并不断返回下一个值,直至没有数据可取的对象称为迭代器: Iterator
2.迭代器
可以使用 instance()判断一个对象是否是 Iterate对象
(1) 生成器都是迭代器
from collections import Iterator
squares = (i**2 for i in range(10) if i%2==0)
print(isinstance(squares,Iterator))
True
E:\Anaconda\lib\site-packages\ipykernel_launcher.py:1: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3,and in 3.9 it will stop working
"""Entry point for launching an IPython kernel.
(2) 列表,元组,字符串,字典,集合,文件不是迭代器
print(isinstance([1,2,3],Iterator))
False
可以通过iter(iterable)创建迭代器
isinstance(iter([1,2,3]),Iterator)
True
for item in Iterable等价于:
先通过 iter函数获取可迭代对象 Iterable的迭代器
然后对获取到的迭代器不断调用 next(方法来获取下一个值并将其赋值给item
当遇到 StopIteration的异常后循环结束
(3) zip enumerate等 itertools里的函数都是迭代器
x = [1, 2]
y = ['a', 'b']
zip(x, y)
<zip at 0x243053336c8>
for i in zip(x, y):
print(i)
isinstance(zip(x, y), Iterator)
(1, 'a')
(2, 'b')
True
numbers = [1,2,3,4,5]
enumerate(numbers)
<enumerate at 0x24306dab0e8>
for i in enumerate(numbers):
print(i)
isinstance(enumerate(numbers), Iterator)
(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, 5)
True
(4) 文件是迭代器
with open("测试文件.txt","r",encoding = "utf-8") as f:
print(isinstance(f, Iterator))
True
(5)迭代器是可以耗尽的
squares = (i**2 for i in range(5))
for square in squares:
print(square)
0
1
4
9
16
for square in squares:
print(square)
(6)range()不是迭代器
numbers = range(10)
isinstance(numbers, Iterator)
False
print(len(numbers)) #有长度
print(numbers[0]) #可索引
print(9 in numbers) #可存在运算
# 不能被next进行遍历
10
0
True
range() 可称为懒序列
装饰器
比如 要统计每个函数的运行时间
def f1():
pass
def f2():
pass
def f3():
pass
f1()
f2()
f3()
2. 函数对象
函数是Python中的第一类对象
(1) 可以吧函数赋值给变量
(2) 对改变了进行调用,可实现原函数的功能
def square(x):
return x**2
print(type(square))
<class 'function'>
pow_2 = square
print(pow_2(5))
print(square(5))
25
25
可以将函数作为参数进行传递
3.高阶函数
(1)接收函数作为参数
(2)或者返回一个函数
满足上述条件之一的函数称之为高阶函数
def square(x):
return x**2
def pow_2(fun):
return fun
f = pow_2(square)
f(8)
64
print(f == square)
True
4. 嵌套函数
def outer():
print("outer fun is running")
def inner():
print("inner fun is running")
inner()
outer()
outer fun is running
inner fun is running
5.闭包
def outer():
x = 1
z = 10
def inner():
y = x + 100
return y, z
return inner
f = outer() # 实际上f包含了 inner函数本身+ outer函数的环境
print(f)
<function outer.<locals>.inner at 0x0000027AF41728B8>
print(f.__closure__) # closure属性中包含了来自外部函数的信息
for i in f.__closure__:
print(i.cell_contents)
(<cell at 0x0000027AF41DB378: int object at 0x00007FFC549EA190>, <cell at 0x0000027AF41DB2E8: int object at 0x00007FFC549EA2B0>)
1
10
res = f()
print(res)
(101, 10)
闭包:延伸了作用域的函数
如果一个函数定义在另一个函数的作用域内,并且引用了外层函数的变量,则该函数称为闭包
闭包是由函数及其相关的引用环境组合而成的实体即:闭包=函数+引用环境)
- 一旦在内层函数重新定义了相同名字的变量,则变量成为局部变量
def outer():
x = 1
def inner():
nonlocal x # 使用nonlocal 表示x不是一个局部变量
x = x + 100
return x
return inner
f = outer()
f()
101
6.一个简单的装饰器
嵌套函数实现
import time
def timer(func):
def inner():
print("inner run")
start = time.time()
func()
end = time.time()
print("{}函数运行用时{:.2f}秒".format(func.__name__,(end-start)))
return inner
def f1():
print("f1 run")
time.sleep(1)
f1 = timer(f1) # 包含inner() 和timer的环境,如传递过来的参数func
f1()
inner run
f1 run
f1函数运行用时1.00秒
语法糖
import time
def timer(func):
def inner():
print("inner run")
start = time.time()
func()
end = time.time()
print("{}函数运行用时{:.2f}秒".format(func.__name__,(end-start)))
return inner
@timer #相当于实现了 f1 = timer(f1)
def f1():
print("f1 run")
time.sleep(1)
f1()
inner run
f1 run
f1函数运行用时1.00秒
7.装饰有参函数
import time
def timer(func):
def inner(*args, **kwargs):
print("inner run")
start = time.time()
func(*args, **kwargs)
end = time.time()
print("{}函数运行用时{:.2f}秒".format(func.__name__, (end - start)))
return inner
@timer #相当于实现了 f1 = timer(f1)
def f1(n):
print("f1 run")
time.sleep(n)
f1(2)
inner run
f1 run
f1函数运行用时2.00秒
被装饰函数有返回值的情况
import time
def timer(func):
def inner(*args, **kwargs):
print("inner run")
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print("{}函数运行用时{:.2f}秒".format(func.__name__, (end - start)))
return res
return inner
@timer #相当于实现了 f1 = timer(f1)
def f1(n):
print("f1 run")
time.sleep(n)
return "wake up"
res = f1(2)
print(res)
inner run
f1 run
f1函数运行用时2.00秒
wake up
8.带参数的装饰器
def timer(method):
def outer(func):
def inner(*args, **kwargs):
print("inner run")
if method == "origin":
print("origin inner run")
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print("{}函数运行用时{:.2f}秒".format(func.__name__, (end - start)))
elif method == "double":
print("double inner run")
start = time.time()
res = func(*args, **kwargs)
end = time.time()
print("{}函数运行双倍用时{:.2f}秒".format(func.__name__, 2*(end - start)))
return res
return inner
return outer
@timer(method="origin") # 相当于 timer = timer(method = "origin") f1 = timer(f1)
def f1():
print("f1 run")
time.sleep(2)
@timer(method="double") # 相当于实现了 f1 = timer(f1)
def f2():
print("f2 run")
time.sleep(2)
f1()
f2()
inner run
origin inner run
f1 run
f1函数运行用时2.00秒
inner run
double inner run
f2 run
f2函数运行双倍用时4.00秒
9.何时执行装饰器
- 一装饰就执行,不必等调用
func_name = []
def find_function(func):
print("run")
func_name.append(func)
return func
@find_function
def f1():
print("f1 run")
@find_function
def f2():
print("f2 run")
@find_function
def f3():
print("f3 run")
f1()
f2()
f3()
print(func_name)
run
run
run
f1 run
f2 run
f3 run
[<function f1 at 0x0000027AF4270288>, <function f2 at 0x0000027AF4270828>, <function f3 at 0x0000027AF4270168>]
for func in func_name:
print(func.__name__)
func()
print()
f1
f1 run
f2
f2 run
f3
f3 run
10. 回归本源
import time
def timer(func):
def inner():
print("inner run")
start = time.time()
func()
end = time.time()
print("{}函数运行用时{:.2f}秒".format(func.__name__,(end-start)))
return inner
@timer #相当于实现了 f1 = timer(f1)
def f1():
print("f1 run")
time.sleep(1)
print(f1.__name__)
inner
- 回来
import time
from functools import wraps
def timer(func):
@wraps(func)
def inner():
print("inner run")
start = time.time()
func()
end = time.time()
print("{}函数运行用时{:.2f}秒".format(func.__name__,(end-start)))
return inner
@timer #相当于实现了 f1 = timer(f1)
def f1():
print("f1 run")
time.sleep(1)
print(f1.__name__)
f1()
f1
inner run
f1 run
f1函数运行用时1.00秒