5 垃圾回收机制详解 与用户交互 基本运算符

垃圾回收机制详解 与用户交互 基本运算符

1. 垃圾回收机制

1.1 内存中的堆和栈

解释器在执行变量定义的语句时,为该变量在内存的堆区申请空间来存储变量值,然后将被分配的内存空间的地址交给变量名。
之后可以通过变量名去访问变量值。
变量名实际存储的是变量值在内存中的地址。
在这里插入图片描述
栈区存放变量名与变量值内存地址的映射关系,可以简单理解为变量名存储内存地址。
堆区存储变量值。

python是引用传递。
栈区存储的数据是变量名与内存地址的映射关系,即对值的引用。
变量名的赋值与传参过程所传递的都是引用,即栈区的数据。

1.2 垃圾回收机制

垃圾是如何产生的?
上面的例子,假如执行 y = 1002 或者 del y,都将会解除y与1001内存空间之间的绑定关系。此时没有任何变量存储指向1001的地址,这意味着无法访问该内存空间,无法再次使用它,失去了价值。
过多的内存垃圾可能导致内存溢出问题。

1.2.1 内存溢出

内存溢出,可以理解为内存不够用,存储的数据超出了指定空间的大小,这时数据就会越界,即多出来的数据覆盖了内存空间其他位置的数据,造成程序运行错误。

1.2.2 垃圾回收机制

当一个变量值无法被访问却依旧占用内存空间时,应该将这段内存空间回收以供其他变量使用,这就涉及垃圾回收的问题。
垃圾回收机制,简称GC,Garbage Collection,是解释器专门用来回收不可用的变量值所占用的内存空间的一种处理方法。

1.2.3 引用计数

引用计数(reference counting),直观的理解是记录内存中堆区中的变量值上关联的引用次数,包括直接引用和间接引用。
只要变量值上关联的引用次数不为0,它就有被访问到的可能性,有利用价值。
一旦变量值被引用的次数为0,即没有变量名能够访问该内存地址,可以判断该内存区域没有价值,会被视为垃圾并被回收。

引用计数技术存在弊端,无法识别循环引用产生的内存垃圾。

1.2.4 循环引用
1.2.4.1 直接引用与间接引用
  1. 直接引用
    从栈区出发直接到达存储值的内存地址,如上图。
  2. 间接引用
    从栈区出发引用到堆区后,需要经过一次或多次进一步引用才能到达存储值的内存空间。
    对于容器类型,变量名首先指向一片存储内存地址的内存空间,通过其中存储的内存地址可以间接找到变量值。
l1 = [11, 22, [333, 444]]

在这里插入图片描述

l2 = [11, 22, [33, {'name': 'egon', 'age': 18}]]

在这里插入图片描述

  1. 对比

直接引用

x = 1001  # 变量名x直接指向的内存中存储了整数1001
y = x  # x将存储的内存地址复制交给了y,此时x和y同时指向整数1001
x = 1000  # 解除了x与1001之间的绑定关系,重新指向一片新开辟的内存空间,存储整数1000

上面过程x已经指向了新的内存空间,但对y没有影响,此时y依旧存储整数1001的内存地址。

间接引用

l1 = [0, 1, 2]
l2 = l1  # l1和l2存储的内存地址相同
l1[0] = 3

上面过程中l1和l2指向了同一个内存空间,列表的元素发生变化,l1和l2都会感知到变化。
l1的第1个元素存储的内存地址发生了变化,l2的第一个元素也随之发生变化。

1.2.4.2 循环引用
l1 = [1, l2]
l2 = [2, l1]
del l1
del l2

del函数清理的是栈区的内容,将变量名存储的内存地址清除了,不影响堆区。垃圾回收机制管理的是堆区的内容
l1与列表1之间的绑定关系被解除了,l2与列表2之间的绑定关系被解除了,此时列表1和列表2应该被视为垃圾,因为无法通过变量名访问它们。
但是列表1与列表2之间的间接引用依然存在,此时的现象是列表1和列表2身上的引用计数均为1,不会被引用计数技术识别为垃圾。此时存在垃圾无法被识别回收的现象,造成内存泄漏。

1.2.4.3 内存泄漏

内存泄露指申请的内存空间无法被释放,导致程序运行过程中该内存空间被一直占用,而且指向这块内存空间的变量不存在,这块内存空间也就永远不可访问,可以利用的内存空间会变小。

1.2.5 标记-清除

为了解决容器对象的循环引用造成的内存泄露问题,解释器使用标记-清除技术。
标记清除就是用来清除循环引用情况下引用计数无法清除的垃圾。
当应用程序可用的内存空间被耗尽的时,临时暂停程序运行,执行“标记”和“清除”两项工作。
标记:
循环遍历栈区中的变量名,根据变量名找到堆区中所有指向的内存空间并打上标记,即找到所有有效的可以被访问的内存空间。
可以理解为,从栈区的每一个变量名出发,可以直接或间接访问达到的内存空间均为有效的可以被使用的,打上标记。
清除:
对堆区中未被打标记的且被占用的内存空间进行回收。

1.2.6 分代回收

分代回收是建立在标记清除技术基础之上的,是一种以空间换时间的方式,用来降低引用计数的扫描频率,提升垃圾回收的效率

思想:
活得越长的变量成为垃圾的可能性就越低,就应该减少对这些变量的扫描频率。

解释器根据变量已经经历的扫描次数来预估该变量的存活时间。
根据变量值的存活时间将内存划分为不同的级别,称为“代”,分别为新生代,青春代和老年代。

新创建的变量被划分为新生代,经历扫描和垃圾回收后依旧存活的变量被视为存活时间稍长的变量,其权重加1。随着扫描次数的增加,当变量权重超过一定阈值后,可以认为该变量存活时间相当长,将其移入青春代,
处于青春代的变量被扫描的频率比较低,最后是老年代,老年代中的变量是存活时间最长的,被扫描的频率最低。
随着所处的“代”的程度增加,变量需要被再次扫描的需求和频率会变少。

缺陷:
分代回收会浪费空间。
假设变量不分代,每次都经历扫描,则垃圾能够得到及时清除。引入分代回收后,假设变量刚刚被分入青春代就成为垃圾,但青春代的扫描频率比新生代低,可能需要经历一定时间才能被清理掉,在这段时间内该垃圾就会一直占据着内存空间,因此一定程度上分代回收技术会浪费内存空间。

2. 用户交互

2.1 什么是用户交互

使用者向计算机的程序输入数据,程序向使用者输出结果。

2.2 为什么需要用户交互

为了让计算机能够像人一样与用户进行沟通交流 。

2.3 如何进行用户交互

交互的本质就是输入、输出

2.3.1 输入

input() 是 Python3 的内置函数,用于从控制台读取用户输入的内容
执行input()时程序会立即暂停,等待用户输入内容,以字符串类型返回输入内容,再赋值给等号左边的变量名。
函数的参数表示提示信息,会显示在控制台上告诉用户应该输入什么样的内容,可以省略。

un_input = input('Please enter your username.')
2.3.2 Python 2 输入

Python2 中与输入相关的函数有两个:input() 和 raw_input()
Python2 的 raw_input() 和 Python3 的 input() 功能是一样的。
Python2 的 input() 要求用户输入一个明确的数据类型数据,输入什么类型就返回什么类型。
用户输入的内容必须符合 Python 的语法,例如若用户需要输入字符串,必须用引号包裹。
这样的处理方式会对使用者造成麻烦,很容易造成程序出错,Python3 已经取消了这种输入方式。

2.3.2 输出

print()

2.3.3 格式化输出

把一段字符串里面的某些内容替换掉之后再输出,就是格式化输出。
格式化输出需要占位符

2.3.3.1 %

占位符:%s,%d等等。
%s占位符:可以接收任意类型的值
%d占位符:只能接收数字
值按照位置与%s一一对应,数量必须一致。

  1. 以元组的方式传值
res = 'My name is %s, and I am %d years old.' % ('Fei', 18)
  1. 以字典的方式传值,可以打破位置的限制
res = 'My name is %(name)s, and I am %(age)d years old.' % {'name': 'Fei', 'age': 18}
2.3.3.2 format

占位符:{}

  1. 按照位置传值
res = 'My name is {}, and I am {} years old.'.format('Fei', 18)
res = 'My name is {0}, and I am {1} years old.'.format('Fei', 18)
res = 'My name is {1}, and I am {0} years old.'.format(18, 'Fei')
  1. 打破位置的限制
res = 'My name is {name}, and I am {age} years old.'.format(name='Fei', age=18)
2.3.3.3 f

占位符:{}

x = input('your name: ')
y = input('your age: ')
res = f'My name is {x}, and I am {y} years old.'

3. 基本运算符

3.1 算数运算符
+-*///%**

小练习,取个位,十位,百位,千位,万位等

num = 12345
num // 10000 % 10  # 万位数
num // 1000 % 10  # 千位数
num // 100 % 10  # 百位数
num // 10 % 10  # 十位数
num // 1 % 10  # 个位数
3.2 比较运算符
>>=<<===!=
3.3 赋值运算符
=
3.3.1 增量赋值
+=-=*=/=//=%=**=
3.3.2 链式赋值
x = y = z = 10
3.3.3 交叉赋值
x = 1
y = 2
x, y = y, x
3.3.4 解压赋值

用一行代码取出容器类型中的数据

lst = [1, 2, 3, 4]
x, y, z, w = lst

左边变量的个数与右边列表内元素个数一般需要相同
引入*,可以帮助我们取两头的值,一般不用于取中间的值

lst = [1, 2, 3, 4]
x, *_ = lst

变量 _ 存储了剩余的列表元素 [2, 3, 4]

解压字典默认解压出来的是字典的key

dic = {'a': 1,'b': 2,'c': 3}
x, y, z = dic 

4. 练习

4.1 用户输入姓名、年龄、工作、爱好 ,然后打印成以下格式

------------ info of Egon -----------
Name : Egon
Age : 22
Sex : male
Job : Teacher
------------- end -----------------

name_input = input('请输入姓名: ')
age_input = input('请输入年龄: ')
sex_input = input('请输入性别: ')
job_input = input('请输入工作: ')
hobby_input = input('请输入爱好: ')

output = f'''
------------ info of {name_input} -----------
Name  : {name_input}
Age   : {age_input}
Sex   : {sex_input}
Job   : {job_input}
------------- end -----------------
'''
print(output)
4.2 用户输入账号密码,程序分别单独判断账号与密码是否正确,正确输出True,错误输出False即可
user_info = {
    'user1': {'password': 'pw1'},
    'user2': {'password': 'pw2'},
    'user3': {'password': 'pw3'}
}
un_input = input('请输入用户名: ')

if un_input not in user_info:
    print('False')
else:
    pw_input = input('请输入密码: ')
    if user_info[un_input]['password'] != pw_input:
        print('False')
    else:
        print('True')
4.3 让计算机提前记下egon的年龄为18岁,写一个猜年龄的程序,要求用户输入所猜的年龄,然后程序拿到用户输入的年龄与egon的年龄比较,输出比较结果即可
res_age = 18
input_age = int(input('请输入Agon的年龄: '))
if input_age > res_age:
    print('年龄过大。')
elif input_age < res_age:
    print('年龄过小。')
else:
    print('猜对了。')
4.4

程序从数据库中取出来10000条数据,打算显示到页面中,
但一个页面最多显示30条数据,请选取合适的算数运算符,计算
显示满30条数据的页面总共有多少个?
最后一页显示几条数据?

total_num = 10000
max_per_page = 30
full_page_num = total_num // max_per_page  # 显示满30条数据的页面数量
last_page_num = total_num % max_per_page  # 最后一页显示数据数量
print(f'显示满30条数据的页面总共有{full_page_num}个, 最后一页显示{last_page_num}条数据。')
4.5 egon今年为18岁,请用增量赋值计算3年后egon老师的年龄
agon_age = 18
agon_age += 3
print(f'3年后egon老师的年龄为{agon_age}')
4.6 将值10一次性赋值给变量名x、y、z
x = y = z = 10
4.7 请将下面的值关联到它应该对应的变量名上
dsb = "egon"
superman = "alex"
dsb, superman = superman, dsb
4.8 我们只需要将列表中的人解压出来,一次性赋值给对应的变量名即可
names = ['alex_sb', 'wusir_sb', 'oldboy_sb', 'egon_nb', 'lxx_nb', 'tank_nb']
alex, wusir, oldboy, *_ = names
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值