短路运算 深拷贝浅拷贝 流程控制之while语句
1 短路运算
短路运算就是偷懒,偷懒到哪个位置,就将该位置的值返回,
and/or 返回的值不只是True/False,而是返回符合条件的那个值。
not 返回的值是True/False
and: 全真则真,一假即假
or: 全假则假,一真即真
res = 1 and 10
# 对于and全真则真,结果取决于后者,运算结果为真,返回10
res = 0 and 10
# 对于and一假即假,结果取决于前者,运算结果为假,返回0
# 当and的前面为假时,已经可以提前确定结果是假了,无需进一步考虑后面,可以偷懒
res = 1 or 0
# 对于or一真即真,结果取决于前者,运算结果为真,返回1
# 当or的前面为真时,已经可以提前确定结果是真了,可以偷懒
res = None or 0
# 对于or全假则假,结果取决于后者,运算结果为假,返回0
2 深拷贝浅拷贝
2.1 可变类型与不可变类型
可变类型:值改变,id不变
id不变说明原内存空间地址未发生变化,即在不改变变量所指向的内存地址的前提下,可以修改其所指向的地址中的值。
不可变类型:值改变,id改变
id改变说明产生了新的内存空间来存储数值,并没有影响或改变原值,即变量所指向的地址上的值是不能修改的。
列表为可变数据类型
l1 = [1, 2, 3]
l2 = l1
l1[0] = 4
l1 和 l2 指向同一片内存空间,当 l1 存储的值被修改时,内存地址不会变,其中被修改的元素指向新的内存地址,由于 l2 也指向了 l1 所指向的内存空间,因此 l2 的第一个元素存储的内存地址也指向新的内存地址。
print(l1[0]) # 4
对于可变类型,赋值操作不能称为拷贝,l1 和 l2 两者没有分隔开,即 l1 存储的元素发生变化时,l2也会随之发生变化。
我们需要的拷贝是:
- 拷贝原列表后产生新的列表;
- 两个列表完全独立,修改数据后互不影响,这里针对的是改操作而非读操作。
2.2 浅拷贝
把原列表存储的第一层的内存地址完全复制一份放入新的内存空间中,再将这个新内存空间的地址交给新列表的变量名,这里不区分可变数据类型和不可变数据类型。
l1 = [0, 1, 2]
l2 = l1
l3 = l1.copy()
print(id(l1) == id(l2)) # True 赋值操作不会开辟新的内存空间
print(id(l1) == id(l3)) # False 拷贝操作开辟了新内存空间,在新内存空间中存储拷贝的值
l1[0] = 3
print(l1[0] == l2[1]) # True l2 改为[3, 1, 2]
print(l1[0] == l3[2]) # False l3 仍为[0, 1, 2] l1的改变不会影响到l3
上面的例子中列表存储的是不可变数据类型。
- 不可变数据类型
对于列表中存储的不可变类型数据,修改过程都是产生了新值。
浅拷贝后,若修改原列表中的元素,就是让原列表中被修改的元素指向新的内存地址,并不会影响到新列表。 - 可变数据类型
对于列表中存储的可变类型数据,例如列表中存储列表,浅拷贝过程所拷贝的仅仅是浅层次(第一层)的数据,即元素指向的地址,而子列表(被拷贝列表中的元素)中存储的数据作为深层次的数据结构没有被拷贝到新的内存空间中,换句话说,新列表的子列表中的元素和原列表的子列表中的元素仍指向同一内存空间。
l1 = [[1, 2], 3]
l2 = l1.copy()
print(id(l1) == id(l2)) # False 浅拷贝过程所拷贝的仅仅是元素指向的地址
print(id(l1[0]) == id(l2[0])) # True
print(id(l1[1]) == id(l2[1])) # True
l1[0][0] = 4
print(l2[0][0]) # 4 不独立
# 浅拷贝,原列表中的元素发生变化的话会影响到新列表中的对应元素,如果新列表中的元素发生变化,同样也会影响原列表中的对应元素。
综上所述,需要一种新的拷贝机制来针对可变类型数据,将两个列表完全独立开,即深拷贝。
2.3 深拷贝
在深拷贝中,列表内部的浅层次数据和深层次数据都会被拷贝,都会在新的内存空间中进行存储。
深拷贝过程中会区分可变数据类型和不可变数据类型。
对于不可变数据类型,将内存地址直接拷贝到新列表中去;
对于可变数据类型,会创建新的内存空间(容器)去存储其子元素。
深拷贝针对的是改操作,若不需要修改,就不需要深拷贝操作。
import copy
l1 = [[1, 2], 3]
l2 = copy.deepcopy(l1)
print(id(l1) == id(l2)) # False
print(id(l1[0]) == id(l2[0])) # False
print(id(l1[1]) == id(l2[1])) # True
l1[0][0] = 4
print(l2[0][0]) # 1 独立
# 深拷贝 就是新列表跟原列表没有任何关系,原列表怎么改都不会影响新列表
2.4 总结
- 赋值只是增加了一个变量的引用,新变量和原变量指向同一片内存空间;
- 浅拷贝会开辟新的内存空间来存储变量,但只拷贝第一层,不会为其子元素开辟新内存空间;
- 深拷贝会为新变量及其子元素开辟新的内存空间进行拷贝,做到了与原变量的完全独立。如果需要拷贝一个列表,并且将改操作完全独立,请使用深拷贝操作。
- 对于不可变数据类型, 深、浅拷贝过程不会开辟新内存空间,相当于赋值操作;
浅拷贝操作只拷贝第一层中的引用,不会区分可变/不可变数据类型;
深拷贝在拷贝过程中若出现不可变数据类型,则会逐层进行拷贝,直到所有的引用都是不可变数据类型为止。
3 流程控制之while语句
如果条件成立,则重复执行相同的操作,如果条件不符合,则跳出循环。
因此while循环也可以称为条件循环。
while 条件:
代码块
else:
代码块
例如:
i = 0
while i < 10:
代码
i += 1
3.1 死循环与效率问题
无法依靠自身的条件而终止的循环称为死循环。
纯计算无IO操作的死循环会导致致命的效率问题。
例如
while 1:
1 + 1
3.2 退出循环的两种方式
- 将条件改为False,等到下次循环判断条件时才会生效,即本次循环体内的代码执行完毕后才会退出循环
flg = True
while flg:
# 代码
flg = False
退出多层嵌套的while循环
flg = True
while flg:
# 代码
while flg:
# 代码
while flg:
# 代码
flg = False
- break
只要运行到break就会立刻跳出本层循环
while True:
# 代码
break
退出多层嵌套的while循环,需要为每一层循环配一个break
while True:
# 代码
while True:
# 代码
while True:
# 代码
break
break
break
3.3 continue
终止本次循环,直接进入下一次循环
在break和continue后面的同级别代码永远不会被执行,因此毫无意义。
3.4 while + else
如果在while循环中没有跳出,正常执行结束,则执行else内的代码块;
如果在while循环中执行了跳出循环的语句(break),不会执行else内的代码块。
理解:
当使用break语句跳出循环时,while的条件依旧为True,因此不会执行else内的代码块;
若while循环正常执行结束,while的条件变为False,因此会执行else内的代码块
while 条件:
# 代码块
else:
# 代码块
4 练习
4.1 使用while循环输出1 2 3 4 5 6 8 9 10
i = 1
while i <= 10:
if i != 7:
print(i, end=' ')
else:
print(' ', end=' ')
i += 1
4.2 求1-100的所有数的和
i = 1
res = 0
while i <= 100:
res += i
i += 1
print(res)
4.3 输出 1-100 内的所有奇数
i = 1
lst = []
while i <= 100:
if i % 2 != 0:
lst.append(i)
i += 1
print(lst)
4.4 输出 1-100 内的所有偶数
i = 1
lst = []
while i <= 100:
if i % 2 == 0:
lst.append(i)
i += 1
print(lst)
4.5 求1-2+3-4+5 … 99的所有数的和
i = 1
res = 0
while i <= 100:
if i % 2 != 0:
res += i
else:
res -= i
i += 1
print(res)
4.6 用户登陆(三次机会重试)
i = 0
user_info = {'un': 'un1', 'pw': 'pw1'}
while i < 3:
un_input = input('请输入用户名:')
pw_input = input('请输入密码:')
if un_input == user_info['un'] and pw_input == user_info['pw']:
print('登陆成功!')
break
if i < 2:
print('用户名或密码有误,请重新输入。')
i += 1
else:
print('用户名或密码输错3次了。')
4.7 猜年龄游戏
要求:允许用户最多尝试3次,3次都没猜对的话,就直接退出,如果猜对了,打印恭喜信息并退出
i = 0
age = 18
while i < 3:
age_input = input('请输入年龄估计值:')
''' 可以增加功能,判断用户的输入是否为整数
while 1:
if age_input.isdigit():
break
else:
age_input = input('年龄为整数,请重新输入年龄估计值:')
'''
if age == int(age_input):
print('恭喜!')
break
i += 1
4.8 猜年龄游戏
允许用户最多尝试3次,每尝试3次后,
如果还没猜对,就问用户是否还想继续玩,
如果回答Y或y, 就继续让其猜3次,
如果回答N或n,就退出程序
如何猜对了,就直接退出
i = 1
age = 18
play_flg = True
while play_flg:
age_input = input('请输入年龄估计值:')
if age == int(age_input):
print('恭喜!')
break
if i % 3 == 0:
while 1:
keep_flg = input('是否还想继续玩? (Y/y/ N/n)')
if keep_flg == 'Y' or keep_flg == 'y':
break
elif keep_flg == 'N' or keep_flg == 'n':
play_flg = False
break
i += 1