一. 数组类型的底层实现
1.1 列表–地址引用
1.1.1 浅拷贝的探究
列表内的元素可以分散存储在内存中
列表存储的,实际是元素的地址
a = [1,2,3,[4,5,6]]
b = a #这种情况只是给列表a起了一个别名b
b[3].append(7)
print(a) #[1, 2, 3, [4, 5, 6, 7]]
print(b) #[1, 2, 3, [4, 5, 6, 7]]
a = [1,2,3,[4,5,6],(1,2,3),{'a':1,'b':2},{1,2,3}]
b = a.copy() #这是一个浅拷贝,复制的是元素的地址
b = list(a) #另一种浅拷贝实现
1.1.2 修改元素
a = [1,2,3,[4,5,6],(1,2,3),{'a':1,'b':2},{1,2,3}]
b = a.copy() #这是一个浅拷贝,复制的是元素的地址
b[0] = 2
print(a)
print(b)
#[1, 2, 3, [4, 5, 6], (1, 2, 3), {'a': 1, 'b': 2}, {1, 2, 3}]
#[2, 2, 3, [4, 5, 6], (1, 2, 3), {'a': 1, 'b': 2}, {1, 2, 3}]
浅拷贝情况下,对元素值的修改不会影响到原列表
#元素修改之前的首元素地址
print(id(a[0]))
print(id(b[0]))
#2358182701360
#2358182701360
b[0] = 10
##元素修改之后的首元素地址
print(id(a[0]))
print(id(b[0]))
#2358182701360
#2358182701648
#可知b[0]元素值修改之后,本质上是指向了另一个地址
1.1.3 对列表元素进行操作
b[3].remove(4) #a也会改变
- 对列表内的列表元素的改变会影响原列表
- 因为操作是对列表元素直接进行的
- 浅拷贝指向的同一个内存地址
1.1.4 对元组类型进行操作
b[4] += (4,5) #此时对a没有影响
因为元组是不可变的,对元组操作后,b[4]指向的就不是原来的元组地址了
1.1.5 对字典型元素进行操作
会影响原列表,对字典元素操作是在字典上直接进行的
1.1.6 小结
浅拷贝情况下,对拷贝后的列表进行的操作不会影响原列表的数据类型有
- 数值
- 元组元素
- 字符串元素
底层原因是对元素的修改会指向一个新的地址
浅拷贝情况下,对拷贝后的列表进行的操作会影响原列表的数据类型有
- 列表元素
- 字典元素
- 集合元素
底层原因是对元素的修改是就地进行的,依然指向的是原地址
1.1.7 深拷贝
将所有层级元素全部复制,完全分开
import copy
a_1 = [1,[1,2,3],(4,5,6),{'a':1},{1,2}]
b_1 = copy.deepcopy(a_1)
b_1[1][0] = 0
print(a_1)
1.2 字典–稀疏数组
通过稀疏数组实现值的存储和访问
1.2.1 字典的创建过程
- 创建一个散列表
- 通过hash()计算键的散列值(冲突则会寻找解决方法)
- 根据计算的散列值确定其在散列表中的位置
字典利用空间换时间,实现了数据的快速查找
散列对应位置的顺序与键在字典显示的顺序可能不同,因此字典是无序的
1.3 字符串–紧凑数组
数据在内存中是连续存放的,效率高,节省空间
1.4 讨论
1.4.1 不可变类型:数字,字符串,元组
- 在生命周期中保持内容不变
- 改变了就不是自己了(id变了)
- 不可变对象+=操作实际上是创建了一个新的的对象
1.4.2 可变类型:列表,字典,集合
id不变,但是里面内容可以变
可变对象+=操作,实际在原对象上就地修改
1.4.3 列表操作的例子
负向索引的妙用
a = ["d", "d", "d", "2", "2", "d" ,"d", "4"]
for i in range(-len(alist), 0):
if alist[i] == "d":
alist.remove(alist[i]) # remove(s) 删除列表中第一次出现的该元素
print(alist)
1.4.4 多维列表的创建及列表推导式
二. 简洁的语法
2.1 解析语法–列表推导式
2.1.1 生成多维数组
n = 3
nums = [[i]*n for i in range(n) if i > 0]
[expression for value in iterable if condition]
三要素:表达式,可迭代对象,if条件
执行过程:
1. 从可迭代对象中拿出一个元素
2. 通过if条件进行筛选
3. 传递给表达式的元素,代入进行处理产生一个结果
4. 产生的结果进行存储
5. 重复1-4步骤
支持多变量
x = [1, 2, 3]
y = [1, 2, 3]
results = [i*j for i,j in zip(x, y)]
results
支持嵌套循环
a_l = ["a", "b"]
b_l = ["1", "2", "3"]
c = ["{0}{1}".format(a, b) for a in a_l for b in b_l]
c
2.1.2 其他解析语法的例子
解析语法构造字典
s_2 = {i:i**2 for i in range(10)}
for k,v in s_2.items():
print(k,':',v)
解析语法构造集合
s_2 = {i**2 for i in range(10)}
2.1.3 生成器表达式
可以作为可迭代对象
s = (i**2 for i in range(5))
s
2.2 条件表达式
expr1 if condition else expr2
n = -10
x = n if n>=0 else -n #x=10
条件表达式和解析语法简单实用,运行速度相对快一些
三. python三大神器
3.1 生成器
ls = [i**2 for i in range(1,100000)]
for i in ls:
do
缺点:会占用大量内存
生成器
1. 采用惰性计算
2. 无需一次存储海量数据
3. 一边执行一边计算
4. 实际上一直在执行next()操作,直到无值可以取
3.1.1 生成器表达式
海量数据,不许存储
无需显示存储全部数据,节约内存
sum(i for i in range(101))
3.1.2 生成器函数yield
生产斐波那契数列
数列前两个元素为1,之后的元素为前两个元素之和
内次调用next()时候执行,遇到yeild语句返回;
再次执行时从上次返回的yeild语句处继续执行
def fib(max):
n,a,b = 0,1,1
while n<max:
yield a
a,b = b,a+b
n += 1
for i in fib(10):
print(i)
3.2 迭代器
3.2.1 可迭代对象
可直接作用于for循环的对象称为可迭代对象:iterable
(1)列表,元组,字符串,字典,集合,文件
可以用instance()判断一个对象是不是iterable对象
from collections import Iterable
isinstance([1,2,3],Iterable)
(2)生成器
生成器不但可以for循环,还可以被next()函数调用取出值
直到没有数据可取,抛出异常
可以被next()函数调用并不断返回下一个值,直至没有数据可取的对象称为迭代器
3.2.2 迭代器的类型
可以用instance()判断一个对象是否是Iterator对象
(1)生成器都是迭代器
from collections import Iterator
s = (i**2 for i in range(5))
isinstance(s,Iterator)
(2)列表,元组,字符串,字典,集合不是迭代器
可以通过iter(Iterable)创建迭代器
for item in Iterablw:
等价于:
先通过iter()函数获取可迭代对象Iterable的迭代器
然后对获取到的迭代器不断调用next()方法获取下一个值并赋值给iter
遇到StopIteration异常后循环结束
*(3)zip enumerate等itertools里的函数是迭代器
x = [1,2]
y = ['a','b']
for i in zip(x,y):
print(i)
isinstance(zip(x,y),Iterator) #True
nums = [1,2,3,4,5]
for i in enumerate(nums):
print(i)
isinstance(enumerate(nums),Iterator)
(4)文件是迭代器
可以对文件对象进行迭代操作
见文件部分笔记 文件
(5)迭代器是可耗尽的
迭代器里的元素迭代过一次后再迭代不会产生任何内容
(6)range()不是迭代器
不能nect()操作
是不会穷尽的
3.3 装饰器
3.3.1 装饰器的作用
- 需要对已经开发上线的程序添加某些功能
- 不能对程序中的函数源代码进行修改
- 不能改变程序中函数的调用方式
3.3.2 装饰器相关基本概念
(1)函数对象
函数是Python中的第一类对象
1. 可以把函数赋值给变量
2. 对该变量进行调用,可以实现原函数的功能
def pow_2(x):
return x**2
print(type(pow_2)) #<class 'function'>
a = pow_2
print(a(3))#9
可以把函数作为参数进行传递
(2)高阶函数
1. 接受函数作为参数
2. 或者返回一个函数
满足上述条件之一称之为高阶函数
def pow_2(x):
return x**2
def pow_decoration(func):
return func #返回一个函数
f = pow_decoration(pow_2) #接受函数作为参数
f(3)
(3)嵌套函数
在函数的内部定义一个函数
可以共享外部函数的形参和外部变量
def outer():
print('outer')
def inner():
print('inner')
inner()
outer() # outer inner
(4)闭包
def outer():
x = 1
z = 10
def inner():
y = x+10
return y,x
return inner
f = outer()
print(type(f))
f() # 11 1
闭包:延伸了作用域的函数
如果一个函数定义在另一个函数作用域内,并且引用了外层函数的变量,则该函数称为闭包
闭包是由函数及其相关的引用环境组合而成的实体(闭包 = 函数+引用环境)
一旦在内层函数重新定义了相同名字的变量,则变量成为局部变量
解决方法:nonlocal允许内嵌的函数来修改闭包变量
内部函数变量与外部函数变量重名,内部函数变量会被认为是局部变量,如果修改外部变量,则需要nonlocal进行声明
3.3.3 简单的装饰器
#为了测出f1运行的时间,但又不想改变f1源代码
#版本一
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(5)
f = timer(f1)
f()
语法糖
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(5)
f1()
如果f1函数有参数
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)
思考
为什么要装饰器?
1. 因为想要f1()运行时,可以实现f1()不一样的功能
所以用f1 = outer(f1)修饰,而outer作用是转换,所以返回的应该是实现功能的函数
2. 实现功能的函数如果定义在outer外,就无法获知函数f1,所以要定义在outer内部
inner是实现功能函数,outer是进行转换的函数
outer参数是要修饰的f1
inner参数应该与f1参数一致
注意执行顺序
import time
def outer(func):
print('outer执行') #1
def inner():
print('inner执行') #3
start = time.time()
func() #此处的func执行的是修饰之前的函数
end = time.time()
print('%s运行时间:%d秒'%(func.__name__,(end-start))) #5
print('inner执行结束')#6
print('outer执行完') #2
return inner
def f_1():
print('f_1 run')#4
time.sleep(2)
f_1 = outer(f_1)
f_1()
练习:多重修饰
import time
def outer1(func):
print('outer1执行') #3
def inner1():
print('inner1执行') #5
start = time.time()
func()#6
end = time.time()
print('%s运行时间:%d秒'%(func.__name__,(end-start))) #12
print('inner1执行结束')#13
print('outer1执行完') #4
return inner1
def outer2(func):
print('outer2执行') #1
def inner2():
print('inner2执行') #7
start = time.time()
func() #8
end = time.time()
print('%s运行时间:%d秒'%(func.__name__,(end-start))) #10
print('inner2执行结束')#11
print('outer2执行完') #2
return inner2
@outer1
@outer2
def f_1():
print('f_1 run')#9
time.sleep(2)
f_1()
"""
outer2执行
outer2执行完
outer1执行
outer1执行完
inner1执行
inner2执行
f_1 run
f_1运行时间:2秒
inner2执行结束
inner2运行时间:2秒
inner1执行结束
"""
一步步解释
temp = outer2(f_1) #执行1 2
f_1 = outer1(temp) #执行3 4
f_1() #先是执行inner1代码 执行5 6 执行到6时,因为func执行的是装饰前的函数,outer装饰的是temp,所以会执行temp
#也就是执行inner2 执行7 8执行8时,func是修饰前的代码,就是原本的f_1执行f_1语句 9 。
#之后执行10 11 12 13
假如装饰器带返回值
res = f1()
接收即可
3.3.4 带参数的装饰器
原有基础上再套一层
def decoration(method)
def outer(func):
def inner():
if method == '':
pass
elif method == '':
pass
return inner
return outer
return outer
@decoration(method='')#想当于decoration = decoration(method='') f1 = outer(f1)
def f1():
pass
@decoration(method='')
def f2():
pass
装饰器一装饰就执行,执行的外层函数,不会执行功能函数
3.3.5 如果然f1函数依然是自己的属性
from functools import wraps
def outer(func):
@wraps(func) #回归本源
def inner():
pass
return inner
@outer
def f1():
pass