内存管理与数据运算
1、内存管理
1.1、内存中的优化机制
1.1.1 小整数池
小整数池是 Python 解释器的一项内存优化机制:提前创建好一部分常用的整数对象,放在全局缓存池里,程序运行时直接复用,避免频繁创建和销毁对象,既省内存又提升速度。包含[-5, 256],也就是说,Python 在启动时就一次性创建好从 -5到 256的所有整数对象,并且整个解释器生命周期内都不会销毁。
小整数池的好处:
1、-5 ~ 256覆盖了绝大多数循环索引、计数器、标志位等高频使用场景
2、范围再大会显著增加启动内存,收益递减
3、该定义在 CPython 源码中是硬编码的
注意:小整数池是全局唯一的,即使是不同类型构造方式,只要落在 [-5, 256],都指向同一个缓存对象。
x = -5
y = int("-5")
print(x is y) # True
对于字符串,python解释器也会做小整数池类似的事情,碰到定义的多个变量拥有同一个id并不奇怪。但需要知道的时,python解释器不可能把所有的值都为你提前准备好。
1.1.2 两种判断方式
- is:身份运算符,判断是不是同一个对象,即两个值的id是否相等。
- ==:相等性判断:判断值是否相等
a = 257
b = 257
print(a == b) # True(永远成立)
print(a is b) # 不要依赖这个结果
永远不要用 is比较整数值大小。
1.1.3 常量折叠
交互式环境(REPL):
>>> a = 257
>>> b = 257
>>> a is b
False
写在 .py文件中运行:
a = 257
b = 257
print(a is b) # True
原因:脚本文件是一次编译、统一规划;交互环境是一句一执行、各管各的。 脚本文件中会做常量折叠,就是 Python 在“统一规划”时,把相同的数字合并成一个对象来用的优化手段。
1.2、垃圾回收机制
1.2.1 什么是垃圾
当变量值没有任何引用时就变成了垃圾
x = 10
x = 20 # 内存区域中存储的10变成了垃圾
del x # 内存区域中存储的20变成了垃圾
1.2.2 垃圾回收机制
python解释器提供自动的垃圾回收机制用于清理那些没有任何引用的值, 让内存区域空置下来重复使用,垃圾回收机制可以让程序员不必再困扰于内存释放的问题,降低程序员处理内存中触发的风险,从而提升开发效率。
1.2.3 堆区与栈区
内存空间里被分了很多区域,其中跟变量定义直接相关的有栈区和堆区这两种区域。
- 栈区 stack:存放变量名及其引用的堆区地址。
- 堆区 heap:存储所有的python对象和对象内部的数据,比如数值、字符串、列表、字典、类实例等。
def func():
a = 10 # a 是栈上的变量,指向堆中的整数对象
b = [1, 2] # b 是栈上的变量,指向堆中的列表对象
每个线程都会独立开辟一块连续的内存区域作为栈区,由解释器自动分配与释放。而堆区是在定义对象时申请的,具有手动申请、自动回收的特点,它在内存中不连续,并且被所有线程共享。
数值与字符在内存中的结构
列表与字典在内存中的结构
注意:只能直接调用桟区的变量名,从而间接获取到堆区的对象和数据结构。
1.2.4 直接引用与间接引用
直接引用:从栈区出发直接指向堆区的对象
间接引用:来自于堆区对象之间的互相引用
# 示例
l1 = [666,777]
l2 = [888,999]
l1.append(l2)
1.3 垃圾回收机制三大核心
1.3.1 引用计数
引用计数是gc机制最核心的工作原理,python解释器会对变量值的引用次数进行统计,当变量值的引用次数为0时,会将变量对象回收掉。
1、引用计数增加
x = 10 # 值10的引用计数为1
y = x # 值10的引用计数为2
l3 = []
l3.append(x) # 值10的引用计数为3
2、引用计数减少:一旦减少为0,就会成为gc机制回收的目标
x = 20 # 值10的引用计数为2
del y # 值10的引用计数为1
del l3[0] # 值10的引用计数为0
1.3.2 标记清除
引用计数存在一个问题:引用循环导致的计数永不为0。
l1 = [666,777]
l2 = [888,999]
l1.append(l2)
print(l1[2][0])
print(l2[0])
l2.append(l1)
print(l1[1])
print(l2[2][1])
del l1
del l2
此时两个列表均没有来自桟区的列表名引用,但python中数据结构的访问必须从栈区为入口,所以这两个列表对象的堆区无法再被引用。按常理应该成为垃圾,但是因为循环引用,导致引用计数统计不为0,于是这块堆内存不会被回收。
标记清除规定:一个变量必须有一条从桟区出发的链路作为访问入口,如果没有则将该堆区标记为垃圾,直接清除。标记清楚是用来解决引用计数机制在发生循环引用问题时如何正确回收堆区的。
1.3.3 分代回收
当数据量大了之后,对所有的数据进行实时的引用计数统计会显著增大计算量,带来很大的cpu资源消耗。于是python将这些变量对象按存活时间划分为新生代、青春代、老年代,越老的对象对应引用计数的动作越少,这种机制提升了引用计数的检查效率。。
- 新生代中检查多次都没有变成垃圾的对象,提升到青春代,青春代扫描频率会被降低;
- 青春代中检查多次都没有变成垃圾的对象,提升到老年代,老年代扫描频率再降低。
不过还是有一些问题:如果有一个对象刚好升级到老年代,一会儿它的引用计数变成了0,因为老年代扫描的频率低,导致了应该被回收的垃圾没有得到及时地清理,这个对象在老年代滞留了一整个扫描周期, 就形成了垃圾积累。
没有十全十美的方案:毫无疑问,如果没有分代回收,即引用计数机制一直不停地对所有变量进行全体扫描,可以更及时地清理掉垃圾占用的内存,但这种一直不停地对所有变量进行全体扫描的方式效率极低,所以我们只能将二者中和。
综上
垃圾回收机制是在清理垃圾&释放内存的大背景下,允许分代回收以极小部分垃圾不会被及时释放为代价,以此换取引用计数整体扫描频率的降低,从而提升其性能,这是一种以空间换时间的解决方案
2、与用户交互
2.1 输入内容
python中提供 input 指令来输入内容。
1、将输入的值传给变量
username = input("请输入您的账号:") # username = "egon"
password = input("请输入您的密码:") # password = "123"
# 判断用户输入的用户名与密码是否正确,如果正确
print(username,password)
print("登录成功")
2、input默认传递为字符类型
在python3中,input会把用户输入的内容统一都存成str类型
x = input(">>>: ") # x = "1"
print(x,type(x))
# print(x + 10) # "1" + 10
如果想要输出数字类型,需要强转换。
print(int(x) + 10) # int("1") + 10
2.2 输出内容
1、直接输出
print(111)
print(111,222,333)
2、替换行尾换行符(默认为回车)
print("第一行内容",end="***")
print("第二行内容",end="&&&")
3、格式化输出 :%
%s,接受字符, 表示提前占位,取后面%()中的值
msg = "my name is %s,my age is %s" % ("egon", "18")
msg = "my name is %s,my age is %s" % ("18", "egon")
print(msg)
username = input("请输入您的用户名:")
age = input("请输入您的年龄:")
print("my name is %s,my age is %s" % (username,age))
当然也可以用+拼接实现这个效果,但不推荐
print("my name is " + username + " my age is" + age) # 不推荐
%d同理,但只能接收数字
print("my age is %d" % 1000)
print("my age is %d" % "1000")
print("my age is %s" % 1000)
print("my age is %s" % [111,222,333])
3、基本运算符
3.1 算数运算
主要用于数字类型之间进行
有加减乘除余等,更复杂的如平方,立方。
print(10 + 1)
print(10 - 1)
print(10 * 3)
print(10 / 3)
print(10 // 3)
print(10 % 3)
print(10 ** 2)
print(10 + 3.1)
print(10 / 0)
了解
print("aaa" + "bbb")
print("aaa" * 3)
l1 = [111,222]
l2 = [333,444]
print(l1 + l2)
print(l1 * 3)
注意:整数、小数、复数是可以一起计算的。python在这一块比较人性化。
3.2 比较运算
比较的是值是否相等,所有类型都可以用==来进行相等性判断,但必须是同种类型才有可能相等
符号是:== != > < >= <=
print(10 == "10")
print(10 == "10")
print(10 == [1,2,3])
!=: 不等于
print(10 != "10")
> >= < <=: 判断大小,主要用于数字之间比大小
print(10 > 3)
print(10 > 10)
print(10 >= 10)
print(10 > 3.1)
了解:字符、列表都可以比大小,此时就按照同位的a-z顺序来比。
msg1 = "abcdefg"
msg2 = "abz"
print(msg1 > msg2)
l1 = [111,222,"abcdefgg"]
l2 = [111,222,"abz"]
print(l1 > l2)
注意,比较运算符可以被连接在一起,形成一个复合判断条件。
print(1 < x < 10) # 是不是像极了数学课本里的
而不必写成:1<x and x < 10
3.3 赋值运算
变量的赋值,没有类型限制,都可用。
1 增量赋值 :做加减乘除
符号有:+= -= *= %=
age = 18
print(age)
# age += 1 # age = age + 1
# age -= 1 # age = age - 1
age *= 2 # age = age * 2
print(age)
x = 10
x %= 3 # x=x%3
print(x)
2 链式赋值:多个变量传值
x=10
y=x
z=y
x = y = z = 10
print(id(x),id(y),id(z))
3 交叉赋值:交换值
m = 666
n = 777
print(m, n)
# temp=m
# m=n
# n=temp
m, n = n, m
print(m, n)
4 解压赋值: 主要针对有多个内容的类型,比如列表、字典、元组、集合、字符串,可以将其中每一个元素单独取出来。
1、一般方案
salary = [11, 22, 33, 44, 55, 66, 77]
解压赋值
mon0 = salary[0]
mon1 = salary[1]
mon2 = salary[2]
mon3 = salary[3]
mon4 = salary[4]
mon5 = salary[5]
mon6 = salary[6]
2、解压赋值
mon0, mon1, mon2, mon3, mon4, mon5, mon6 = salary
mon0, mon1, mon2, mon3, mon4, mon5 = salary # 少一个不行
mon0, mon1, mon2, mon3, mon4, mon5,mon6,mon7 = salary # 多一个不行
print(mon0)
print(mon1)
print(mon2)
print(mon3)
print(mon4)
print(mon5)
print(mon6)
3、取某一部分
* 表示所有,_是一个字符,这里定义为变量表示后面不会使用它,但变量_是有效的。
*—表示匹配到的所有内容都传递给_这个变量。
列表解压赋值
salary = [11, 22, 33, 44, 55, 66, 77]
mon0,mon1,mon2,*_ = salary # 后面的丢掉
print(mon0,mon1,mon2)
print(_)
mon0,mon1,*_,mon_last=salary # 中间的丢掉
print(mon0)
print(mon1)
print(mon_last)
x,y,z,*_="abcdefg" # 字符串解压赋值
print(x,y,z,_)
# 字典解压赋值
dic = {'k1': 111, 'k2': 2222, 'k3': 333}
a,b,c=dic
print(a,b,c)
3.4 逻辑运算
什么是条件:只要结果为布尔值的,都可以当做判断条件
逻辑运算包括 and or not 三种。通过逻辑运算将多个判断条件组合在一起,判断整体是真是假。
短路运算,逻辑运算的多个条件,只要判断到某一个条件可以确定最终结果了,就立即返回当前位置计算的结果,不必往后继续运算。
1、not运算
not 表示取反
print(not 10 > 3)
print(not True)
print(not 111)
print(not 0)
2、and
链接的多个条件必须同时成立,最终结果才为True
print(10 > 3 and 3 == 3)
print(10 > 3 and 3 != 3)
print(10 <= 12 and True and 3 > 2 and 1 == 1)
print(10 <= 12 and False and 3 > 2 and 1 == 1)
3、or
链接的多个条件只要有一个成立,最终结果就成立True
print(10 > 3 or 10 < 2)
print(10 < 2 or 10 > 3)
print(10 <= 12 or False or 3 > 2 or 1 == 1)
4、复杂逻辑运算
同时使用多个逻辑判断,组成更加复杂的逻辑运算组合。
在同时使用了多个逻辑运算时,它们之前有一个优先级。
- 优先级:not > and > or
# 有一个复杂逻辑运算如下:
print(10 > 3 and not 1 == 1 or 3 > 2 and True or 1 < 3)
# 运算步骤分析
先计算比较:10 > 3 为 True,1 == 1 为 True,3 > 2 为 True,1 < 3 为 True。
所以表达式变为:True and not True or True and True or True
然后计算 not:not True 为 False。
所以表达式变为:True and False or True and True or True
然后 and 比 or 优先级高,所以先计算所有 and:
True and False -> False
True and True -> True
所以表达式变为:False or True or True
然后从左到右计算 or:False or True -> True,然后 True or True -> True。
所以最终结果是 True。
我们也可以通过添加()来自己决定执行的顺序,括号内的先匀运算
比如:print((10 > 3) and (not 1 == 1 or 3 > 2) and (True or 1 < 3))
其他练习:
print(111 and "abc")
print(1 and 2 or 3 and 4 or 5)
print(1 and 2 or 3 and 4 or 5)
print(2 or 3 and 4 or 5)
print(2 or 4 or 5)
print(2 or 4 or 5)
print(2)
注意:复杂的逻辑运算是有优先级的:not>and>or;而复杂的比较运算是链式的:print(1 < x < 10)
3.5 成员运算
成员运算用于判断是不是某个对象的其中一个成员。关键字是 in /
- 字符串:成员为字符串对象里的单个/多个字符
- 列表:成员为列表里的元素
- 字典:成员为列表里的key, 需要注意value不属于字典的成员。
msg = "hello egon hahahaha"
print("egon" in msg)
print("bigegon" in msg)
print(not "bigegon" in msg)
print("bigegon" not in msg)
l1 = ['egon','tom','jack','小周周']
print('小周周' in l1)
print('小周周666' not in l1)
dic = {'k1':111,'k2':222,'k3':3333}
print(222 in dic)
print('k2' in dic)
3.6 身份运算
身份运算:用于判断两个变量是否指向同一个内存id。关键字:is / is not
x = 10
y = x
m = 20
print(x is y)
print(x is m)
print(x is not m)
注意: 身份运算这里其实判断的是两个变量值的id,因为python中通常会进行常量折叠,所以一般指向同一个内存区域的就是同一个身份,跟变量名无关。

浙公网安备 33010602011771号