系列文章:
文章目录
数据类型的底层实现
1、列表的复制
1.1、浅拷贝复制的奇怪现象
对拷贝前后的两个列表进行操作
- 对 list_2 进行操作
list_1 = [1, [22, 33, 44], (5, 6, 7), {"name": "Sarah"}]
# 浅拷贝
list_2 = list_1.copy()
list_2.append(55)
print("list_1: ", list_1)
#list_1: [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}]
print("list_2: ", list_2)
#list_2: [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 55]
- 对 list_2[1] 进行操作
list_1 = [1, [22, 33, 44], (5, 6, 7), {"name": "Sarah"}]
# 浅拷贝
list_2 = list_1.copy()
list_2[1].append(55)
print("list_1: ", list_1)
#list_1: [1, [22, 33, 44, 55], (5, 6, 7), {'name': 'Sarah'}]
print("list_2: ", list_2)
#llist_2: [1, [22, 33, 44, 55], (5, 6, 7), {'name': 'Sarah'}]
1.2、列表的底层实现
- 新增元素
list_1 = [1, [22, 33, 44], (5, 6, 7), {"name": "Sarah"}]
list_2 = list(list_1) # 浅拷贝 与list_1.copy()功能相同
list_1.append(100)
list_2.append("n")
print("list_1: ", list_1)
#list_1: [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 100]
print("list_2: ", list_2)
#list_2: [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 'n']
- 修改元素
list_1[0] = 10
list_2[0] = 20
print("list_1: ", list_1)
#list_1: [10, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 100]
print("list_2: ", list_2)
#list_2: [20, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 'n']
- 对列表型元素进行操作
list_1[1].remove(44)
list_2[1] += [55,66]
print("list_1: ", list_1)
# list_1: [10, [22, 33, 55, 66], (5, 6, 7), {'name': 'Sarah'}, 100]
print("list_2: ", list_2)
#list_2: [20, [22, 33, 55, 66], (5, 6, 7), {'name': 'Sarah'}, 'n']
结果表明:对列表型元素进行操作,两个表的列表型元素都同时改变
- 对元组型元素进行操作
list_2[2] += (8,9)
print("list_1: ", list_1)
#list_1: [10, [22, 33, 55, 66], (5, 6, 7), {'name': 'Sarah'}, 100]
print("list_2: ", list_2)
#list_2: [20, [22, 33, 55, 66], (5, 6, 7, 8, 9), {'name': 'Sarah'}, 'n']
元组是不可变的!
对元组类型元素进行操作,两个表是独立的,互不影响
5. 对字典型元素进行操作
list_1[-2]["age"] = 18
print("list_1: ", list_1)
#list_1: [10, [22, 33, 55, 66], (5, 6, 7), {'name': 'Sarah', 'age': 18}, 100]
print("list_2: ", list_2)
#list_2: [20, [22, 33, 55, 66], (5, 6, 7, 8, 9), {'name': 'Sarah', 'age': 18}, 'n']
结论同列表
1.3、引入深拷贝
浅拷贝后:
针对不可变元素(数字、字符串、元组)的操作,两个列表互不影响
针对可变元素(列表、集合、字典)的操作,列表中的元素是有影响的
引入深拷贝
深拷贝将所有层级的相关元素全部复制,完全分开,渭泾分明,避免了上述问题
import copy
list_1 = [1, [22, 33, 44], (5, 6, 7), {"name": "Sarah"}]
list_2 = copy.deepcopy(list_1)
list_1[-1]["age"] = 18
list_2[1].append(55)
print("list_1: ", list_1) #list_1: [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah', 'age': 18}]
print("list_2: ", list_2) #list_2: [1, [22, 33, 44, 55], (5, 6, 7), {'name': 'Sarah'}]
2、字典
2.1、快速的查找
import time
ls_1 = list(range(1000000))
# [-10]*500 是初始化值为-10的长度为500的list, 之后使用+ 拼接两个list
# 最终ls_2 长度是500 + 500
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, end-start))
#查找1000个元素,在ls_1列表中的有500个,共用时3.092348575592041秒
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)))
# 查找1000个元素,在ls_1列表中的有500个,共用时0秒
2.2、字典的底层实现
字典的创建过程
- 第一步:创建一个散列表(稀疏数组N >> n)
d = {} - 第二步:通过hash() 计算键的散列值
print(hash("python"))
print(hash(1024))
print(hash((1,2)))
-4771046564460599764
1024
3713081631934410656
d["age"] = 18 # 增加键值对的操作,首先会计算键的散列值hash("age")
print(hash("age"))
- 第三步根据计算的散列值确定其在散列表中的位置
极个别时候,散列值会发生冲突,则内部有相应的解决冲突的办法 - 在该位置上存入值
键值对的访问过程
d["age"]
- 第一步:计算要访问的键的散列值
- 第二步:根据计算的散列值,通过一定的规则,确定其在散列表中的位置
- 第三步:读取该位置上存储的值
如果存在,,则返回该值
如果不存在,则报错 KeyError
小结
- 字典数据类型,以空间换时间(空间利用率低),实现类快速地数据查找
- 因为散列值对应位置的顺序与键在字典中显示的顺序可能不同,因此表现出字典是无序的
如果N=n 会产生很多位置冲突
3、字符串
通过紧凑数组实现字符串的存储
- 数据在内存中是连续存放的,效率高
- 同为序列类型,列表采用引用数组,而字符串采用紧凑数组
3.1、可变类型与不可变类型
不可变类型:数字、元组、字符串
在生命周期中保持内容不变
当改变了时,会有新的id,实际上创建了一个新的对象
x= 1
y = "Python"
print("x id:", id(x)) #x id: 1447911844144
print("y id:", id(y)) #y id: 1447917164912
x += 2
y += "3.7"
print("x id:", id(x)) #x id: 1447911844208
print("y id:", id(y)) #y id: 1447917208304
元组并不是总是不可变的
t = (1,[2])
print(id(t)) #2524431430976
t[1].append(3)
print(id(t))#2524431430976
print(t) #(1, [2, 3])
可变类型:列表、字典、集合
id 保持不变,但里面的内容可以变
ls = [1, 2, 3]
d = {"Name": "Sarah", "Age": 18}
print("ls id:", id(ls)) #ls id: 2864434660288
print("d id:", id(d)) #d id: 2864434589056
ls += [4, 5]
d_2 = {"Sex": "female"}
d.update(d_2) # 把d_2中的元素更新到d中
print("ls id:", id(ls)) #ls id: 2864434660288
print("d id:", id(d)) #d id: 2864434589056
4、列表操作的几个例子
4.1、删除列表中特定的元素
- 法1 存在运算删除法
alist = ["d", "d", "d", "2", "2", "d" ,"d", "4"]
s = "d"
while True:
if s in alist:
alist.remove(s)
else:
break
- 法2 一次性遍历元素执行删除
alist = ["d", "d", "d", "2", "2", "d" ,"d", "4"]
s = "d"
for i in alist:
if i == s:
alist.remove(s) # remove(s) 删除列表中第一次出现的该元素
print(alist) #['2', '2', 'd', 'd', '4']
解决方法:使用负向索引
alist = ["d", "d", "d", "2", "2", "d" ,"d", "4"]
s = "d"
for i in range(-len(alist),0):
if alist[i] == s:
alist.remove(alist[i])
print(alist) #['2', '2', '4']
4.2、多维列表的创建
print([0]*10) #[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
print([[0]*10]*3)
# [[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]]
ls[0][0] = 1
print(ls)
# [[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
# [1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
# [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
ls = [[0]*10 for i in range(3)]
print(ls)
# [[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]]
ls[0][0]=1
print(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]]
4.4 列表推导
解析语法的基本结构—以列表解析为例(也称为列表推导)
[expression for value in iterable if conditihon]
三要素:表达式、可迭代对象、if条件(可选)
执行过程
- 从可迭代对象中拿出一个元素
- 通过if条件(如果有的话),对元素进行筛选。
若通过筛选,则把元素传递给表达式
若未通过,则进入步骤1,进入下一次迭代 - 将传递给表达式的元素,带入表达式进行处理,产生一个结构
- 将步骤3产生的结果作为列表的一个元素进行存储
- 重复步骤1-4直到迭代对象迭代结束,返回新创建的列表
等价于如下过程:
# 等价于如下代码
result = []
for value in iterale:
if condition:
result.append(expression)
【例】求20以内奇数的平方
squares = []
for i in range(1,21):
if i%2 == 1 :
squares.append(i**2)
print(squares) #[1, 9, 25, 49, 81, 121, 169, 225, 289, 361]
squares= [i**2 for i in range(1,21) if i%2==1]
print(squares) #[1, 9, 25, 49, 81, 121, 169, 225, 289, 361]
支持多变量
x = [1,2,3]
y = [1,2,3]
result = [i*j for i,j in zip(x,y)]
print(result)
支持嵌套循环
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']
4.5 字典推导
解析语法构造字典(字典推导)
squares = {i: i**2 for i in range(5)}
for k, v in squares.items():
print(k, ":", v,end=",")#0 : 0,1 : 1,2 : 4,3 : 9,4 : 16,
4.6集合推导
squares = {i**2 for i in range(5)}
print(squares)#{0, 1, 4, 9, 16}
4.7 生成器表达式
squares = (i**2 for i in range(10))
print(squares) #<generator object <genexpr> at 0x00000204790BF0B0>
colors = ["black", "white"]
sizes = ["S", "M", "L"]
tshirts = ("{} {}".format(color , size) for color in colors for size in sizes)
print(tshirts) #<generator object <genexpr> at 0x000002EF6451F0B0>
for tshirt in tshirts:
print(tshirt)
# black S
# black M
# black L
# white S
# white M
# white L
4.8条件表达式
expr1 if condition else expr2
【例】将变量n的绝对值赋值给变量x
n = -10
if n >= 0:
x = n
else: x = -n
print(x) #10
n = -10
x = n if n >= 0 else -n
print(x) #10
条件表达式和解析语法简单实用、运行速度相对更快一些
三大神器
1、生成器
ls = [i**2 for i in range(1,100001)]
for i in ls:
pass
缺点:占用大量内存
生成器
- List item采用惰性计算方式
- 无需一次性存储海量数据
- 一边执行一边计算,只计算每次需要的值
- 实际上一直在执行next()操作,直到无值可取
1.1生成器表达式
- 海量数据,不需存储
squares = (i**2 for i in range(100000))
for i in squares:
pass
- 求0~100的和
无需显示存储全部数据,节省内存
sum((i for i in range(101))) #5050
1.2 生成器函数—yield
- 生产斐波那契数列
数列前两个元素为1,1 之后的元素为其前两个元素之和
def fib(max):
n ,a, b = 0, 1, 1
while n < max:
print(a)
a, b = b, a + b
n += 1
fib(5)
1
1
2
3
5
构造生成器函数
在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句继续执行
def fib(max):
n ,a, b = 0, 1, 1
while n < max:
yield a
a, b = b, a + b
n += 1
print(fib(5)) #<generator object fib at 0x000001FEF92CF0B0>
for i in fib(5):
print(i)
1
1
2
3
5
2.迭代器
1、可迭代对象
可迭代对象:可直接作用于for循环的对象统称为可迭代对象:Iterable
(1) 列表、元组、字符串、字典、集合、文件
from collections.abc import Iterable
# Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.10 it will stop working
print(isinstance([1, 2, 3],Iterable)) #True
print(isinstance((1, 2, 3),Iterable)) #True
print(isinstance({1, 2, 3},Iterable)) #True
print(isinstance("123",Iterable)) #True
print(isinstance({1:1, "2":2},Iterable)) #True
(2) 生成器
from collections.abc import Iterable
squares = (i**2 for i in range(5))
print(isinstance(squares, Iterable)) #True
生成器不但可以用于for循环,还可以被next()函数调用
from collections.abc import Iterable
squares = (i**2 for i in range(5))
print(next(squares))
print(next(squares))
print(next(squares))
print(next(squares))
print(next(squares))
print(next(squares))
0
1
4
9
16
直到没有数据可取,抛出StopIteration
print(next(squares))
Traceback (most recent call last):
File "E:\code\deeplea\pytorchlearn\ForTest\test.py", line 8, in <module>
print(next(squares))
StopIteration
2、迭代器
可以被next()函数调用并不断返回下一个值,直到没有数据可取的对象称为迭代器: Iterator
可以使用 isinstance() 判断一个对象是否是Iterator对象
注意 可迭代对象:Iterable 用 isinstance([1, 2, 3],Iterable)判断
迭代器:Iterator 用 isinstance([1, 2, 3], Iterator)
(1)生成器都是迭代器
from collections.abc import Iterator
sequares = (i**2 for i in range(5))
print(isinstance(sequares, Iterator)) #True
(2)列表、元组、字符串、字典、集合不是迭代器Iterator,但它们是可迭代对象Iterable
from collections.abc import Iterator
print(isinstance([1, 2, 3], Iterator)) #False
可以通过 iter(Iterable) 创建迭代器
print(isinstance(iter([1, 2, 3]),Iterator)) #True
for item in Iterable 等价于:
先通过iter()函数获取可迭代对象Iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration异常后循环结束。
(3)zip enumetate 等itertools里的函数都是迭代器
from collections.abc import Iterator
x = [1, 2]
y = ["a", "b"]
print(zip(x ,y)) #<zip object at 0x0000019095162E00>
for i in zip(x, y):
print(i)
print(isinstance(zip(x, y),Iterator)) #True
from collections.abc import Iterator
numbers = [1, 2]
print(enumerate(numbers)) #<enumerate object at 0x000001AF415C2E00>
for i in enumerate(numbers):
print(i)
# (0, 1)
# (1, 2)
print(isinstance(enumerate(numbers),Iterator)) #True
(4)文件是迭代器
with open("测试文件.txt", "r", encoding = "utf-8") as f:
print(isinstance(f, Iterator))#True
(5)迭代器是可耗尽的
squares = (i**2 for i in range(3))
for square in squares:
print(square)
# 0
# 1
# 4
for square in squares:
print(square) #无输出
(6)range() 不是迭代器
from collections.abc import Iterator
numbers = range(10)
print(isinstance(numbers,Iterator)) #False
print(len(numbers)) ## 有长度
print(numbers[0]) # 可索引
print(9 in numbers) # 可存在计算
next(numbers) # 不可被next()调用 TypeError: 'range' object is not an iterator
不会被耗尽
for number in numbers:
print(number) #有输出
for number in numbers:
print(number) #有输出
可以称range() 为懒序列,它是一种序列,但是并不包含任何内存中的内容,而是通过计算来回答问题
3.装饰器
1、需求的提出
(1)需要对已开发上线的程序添加某些功能
(2)不能对程序中函数的源代码进行修改
(3)不能改变程序中函数的调用方式
2、函数对象
函数是Python中的第一类对象
(1)可以把函数赋值给变量
(2)对该变量进行调用,可实现原函数的功能
def square(x):
return x**2
print(type(square)) #<class 'function'>
pow_2 = square #可以理解给这个函数起了个别名
print(pow_2(5)) #25
print(square(5)) #25
可以将函数作为参数进行传递
3、高阶函数
(1)接受函数作为参数
(2)或者返回一个函数
满足上述条件之一的函数称为高阶函数
def square(x):
return x**2
def pow_2(fun):
return fun
f = pow_2(square)
print(f(8)) #64
print(f == square) #True
4、嵌套函数
在函数内部定义一个函数
def outer():
print("outer is running")
def inner():
print("inner is running")
inner()
outer()
# outer is running
# inner 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 0x000001BE11B1D730>
print(f.__closure__) # __closure__属性中包含了来自外部函数的信息
for i in f.__closure__:
print(i.cell_contents)
(<cell at 0x000001BE0FDE06D8: int object at 0x00007FF910D59340>, <cell at 0x000001BE0FDE0A98: int object at 0x00007FF910D59460>)
1
10
res = f()
print(res)
(101, 10)
闭包:延伸了作用域的函数
如果一个函数定义在另一个函数的作用域内,并且引用了外层函数的变量,则该函数称为闭包,闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数+引用环境)
- 一旦在内层函数重新定义了相同名字的变量,则变量称为局部变量
def outer():
x = 1
def inner():
# nonlocal x
x = x+100 #显示代码有误:不能解析x
return x
return inner
f = outer
f()
def outer():
x = 1
def inner():
nonlocal x
x = x+100
return x
return inner
f = outer
f()
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 包含inner() 和timer的环境,如传递过来的参数
f1 = timer(f1)
f1()
# inner run
# f1 run
# f1 函数运行用时1.01秒
语法糖
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.01秒
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
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
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、带参数的装饰器
装饰器本身要传递一些额外参数
- 需求:有时需要统计绝对时间,有时需要统计绝对时间的2倍