1.continue & break
跳出循环
True
and False
,当输入1
时,a=False
时,会执行接下来的语句后再跳出这个循环。
a=True
while a:
b= input('type somesthing')
if b=='1':
a= False
else:
pass
print ('finish run')
''''
type somesthing:2
still in while
type somesthing:3
still in while
type somesthing:1
still in while #会执行下面的语句再跳出
finish run
''''
break
break
用法,在循环语句中,使用 break
, 当符合跳出条件时,会直接结束循环,这是 break
和 True False
的区别。
while True:
b= input('type somesthing:')
if b=='1':
break
else:
pass
print('still in while')
print ('finish run')
"""
type somesthing:4
still in while
type somesthing:5
still in while
type somesthing:1
finish run
"""
continue
在代码中,满足b=1
的条件时,因为使用了 continue
, python
不会执行 else
后面的代码,而会直接进入下一次循环。
while True:
b=input('input somesthing:')
if b=='1':
continue
else:
pass
print('still in while' )
print ('finish run')
"""
input somesthing:3
still in while
input somesthing:1 # 没有"still in while"。直接进入下一次循环
input somesthing:4
still in while
input somesthing:
"""
2.错误处理
输出错误:try:
, except ... as ...:
看如下代码
try:
file=open('eeee.txt','r') #会报错的代码
except Exception as e: # 将报错存储在 e 中
print(e)
"""
[Errno 2] No such file or directory: 'eeee.txt'
"""
处理错误:会使用到循环语句。首先报错:没有这样的文件No such file or directory
. 然后决定是否输入y
, 输入y
以后,系统就会新建一个文件(要用写入的类型),再次运行后,文件中就会写入ssss
try:
file=open('eeee.txt','r+')
except Exception as e:
print(e)
response = input('do you want to create a new file:')
if response=='y':
file=open('eeee.txt','w')
else:
pass
else:
file.write('ssss')
file.close()
"""
[Errno 2] No such file or directory: 'eeee.txt'
do you want to create a new file:y
ssss #eeee.txt中会写入'ssss'
3.zip lambda map
zip
zip
函数接受任意多个(包括0个和1个)序列作为参数,合并后返回一个tuple
列表,请看示例:
a=[1,2,3]
b=[4,5,6]
ab=zip(a,b)
print(list(ab)) #需要加list来可视化这个功能
"""
[(1, 4), (2, 5), (3, 6)]
"""
zip
中的运算
a=[1,2,3]
b=[4,5,6]
ab=zip(a,b)
print(list(ab))
for i,j in zip(a,b):
print(i/2,j*2)
"""
0.5 8
1.0 10
1.5 12
"""
lambda
lambda
定义一个简单的函数,实现简化代码的功能,看代码会更好理解。
fun = lambda x,y : x+y
, 冒号前的x,y
为自变量,冒号后x+y
为具体运算。
fun= lambda x,y:x+y
x=int(input('x=')) #这里要定义int整数,否则会默认为字符串
y=int(input('y='))
print(fun(x,y))
"""
x=6
y=6
12
"""
map
map
是把函数和参数绑定在一起进行输出。
>>> def fun(x,y):
return (x+y)
>>> list(map(fun,[1],[2]))
"""
[3]
"""
>>> list(map(fun,[1,2],[3,4]))
"""
[4,6]
"""
4.copy & deepcopy 浅复制 & 深复制
Python中,对象的赋值,拷贝(深/浅拷贝)之间是有差异的,如果使用的时候不注意,就可能产生意外的结果。
id
什么是id
?一个对象的id
值在CPython
解释器里就代表它在内存中的`地址
>>> import copy
>>> a=[1,2,3]
>>> b=a
>>> id(a)
"""
4382960392
"""
>>> id(b)
"""
4382960392
"""
>>> id(a)==id(b) #附值后,两者的id相同,为true。
True
>>> b[0]=222222 #此时,改变b的第一个值,也会导致a值改变。
>>> print(a,b)
[222222, 2, 3] [222222, 2, 3] #a,b值同时改变
浅拷贝
当使用浅拷贝时,python
只是拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已。看代码:
>>> import copy
>>> a=[1,2,3]
>>> c=copy.copy(a) #拷贝了a的外围对象本身,
>>> id(c)
4383658568
>>> print(id(a)==id(c)) #id 改变 为false
False
>>> c[1]=22222 #此时,我去改变c的第二个值时,a不会被改变。
>>> print(a,c)
[1, 2, 3] [1, 22222, 3] #a值不变,c的第二个值变了,这就是copy和‘==’的不同
深拷贝
deepcopy
对外围和内部元素都进行了拷贝对象本身,而不是对象的引用。
#copy.copy()
>>> a=[1,2,[3,4]] #第三个值为列表[3,4],即内部元素
>>> d=copy.copy(a) #浅拷贝a中的[3,4]内部元素的引用,非内部元素对象的本身
>>> id(a)==id(d)
False
>>> id(a[2])==id(d[2])
True
>>> a[2][0]=3333 #改变a中内部原属列表中的第一个值
>>> d #这时d中的列表元素也会被改变
[1, 2, [3333, 4]]
#copy.deepcopy()
>>> e=copy.deepcopy(a) #e为深拷贝了a
>>> a[2][0]=333 #改变a中内部元素列表第一个的值
>>> e
[1, 2, [3333, 4]] #因为时深拷贝,这时e中内部元素[]列表的值不会因为a中的值改变而改变
>>>
5.多线程的讲解
多线程就是增加多个线程同时进行一个任务,这样可以加快任务的运行速度
添加线程
threading模块的一些基本操作,如获取线程数,添加线程等。首先导入模块:
import threading
获取已激活的线程数
threading.active_count()
# 2
查看所有线程信息
threading.enumerate()
# [<_MainThread(MainThread, started 140736011932608)>, <Thread(SockThread, started daemon 123145376751616)>]
输出的结果是一个<_MainThread(...)>
带多个<Thread(...)>
。
查看现在正在运行的线程
threading.current_thread()
# <_MainThread(MainThread, started 140736011932608)>
添加线程,threading.Thread()
接收参数target
代表这个线程要完成的任务,需自行定义
def thread_job():
print('This is a thread of %s' % threading.current_thread())
def main():
thread = threading.Thread(target=thread_job,) # 定义线程
thread.start() # 让线程开始工作
if __name__ == '__main__':
main()
不加 join() 的结果
我们让 T1
线程工作的耗时增加.
import threading
import time
def thread_job():
print("T1 start\n")
for i in range(10):
time.sleep(0.1) # 任务间隔0.1s
print("T1 finish\n")
added_thread = threading.Thread(target=thread_job, name='T1')
added_thread.start()
print("all done\n")
预想中输出的结果是否为:
T1 start
T1 finish
all done
但实际却是:
T1 start
all done
T1 finish
加入 join() 的结果
线程任务还未完成便输出all done
。如果要遵循顺序,可以在启动线程后对它调用join
:
added_thread.start()
added_thread.join()
print("all done\n")
使用join
对控制多个线程的执行顺序非常关键。举个例子,假设我们现在再加一个线程T2
,T2
的任务量较小,会比T1
更快完成:
def T1_job():
print("T1 start\n")
for i in range(10):
time.sleep(0.1)
print("T1 finish\n")
def T2_job():
print("T2 start\n")
print("T2 finish\n")
thread_1 = threading.Thread(target=T1_job, name='T1')
thread_2 = threading.Thread(target=T2_job, name='T2')
thread_1.start() # 开启T1
thread_2.start() # 开启T2
print("all done\n")
输出的”一种”结果是:
T1 start
T2 start
T2 finish
all done
T1 finish
现在T1
和T2
都没有join
,注意这里说”一种”是因为all done
的出现完全取决于两个线程的执行速度, 完全有可能T2 finish
出现在all done
之后。这种杂乱的执行方式是我们不能忍受的,因此要使用join
加以控制。
我们试试在T1
启动后,T2
启动前加上thread_1.join()
:
thread_1.start()
thread_1.join() # notice the difference!
thread_2.start()
print("all done\n")
输出结果:
T1 start
T1 finish
T2 start
all done
T2 finish
可以看到,T2
会等待T1
结束后才开始运行。
如果我们在T2
启动后放上thread_1.join()
会怎么样呢?
thread_1.start()
thread_2.start()
thread_1.join() # notice the difference!
print("all done\n")
输出结果:
T1 start
T2 start
T2 finish
T1 finish
all done
T2
在T1
之后启动,并且因为T2
任务量小会在T1
之前完成;而T1
也因为加了join
,all done
在它完成后才显示。
你也可以添加thread_2.join()
进行尝试,但为了规避不必要的麻烦,推荐如下这种1221
的V型排布:
thread_1.start() # start T1
thread_2.start() # start T2
thread_2.join() # join for T2
thread_1.join() # join for T1
print("all done\n")
"""
T1 start
T2 start
T2 finish
T1 finish
all done
"""
储存进程结果 Queue
代码实现功能,将数据列表中的数据传入,使用四个线程处理,将结果保存在Queue
中,线程执行完后,从Queue
中获取存储的结果
导入线程,队列的标准模块
import threading
import time
from queue import Queue
定义一个被多线程调用的函数
函数的参数是一个列表l和一个队列q
,函数的功能是,对列表的每个元素进行平方计算,将结果保存在队列中
def job(l,q):
for i in range (len(l)):
l[i] = l[i]**2
q.put(l) #多线程调用的函数不能用return返回值
定义一个多线程函数
在多线程函数中定义一个Queue
,用来保存返回值,代替return
,定义一个多线程列表,初始化一个多维数据列表,用来处理:
def multithreading():
q =Queue() #q中存放返回值,代替return的返回值
threads = []
data = [[1,2,3],[3,4,5],[4,4,4],[5,5,5]]
在多线程函数中定义四个线程,启动线程,将每个线程添加到多线程的列表中
for i in range(4): #定义四个线程
t = threading.Thread(target=job,args=(data[i],q)) #Thread首字母要大写,被调用的job函数没有括号,只是一个索引,参数在后面
t.start()#开始线程
threads.append(t) #把每个线程append到线程列表中
分别join
四个线程到主线程
for thread in threads:
thread.join()
定义一个空的列表results
,将四个线运行后保存在队列中的结果返回给空列表results
results = []
for _ in range(4):
results.append(q.get()) #q.get()按顺序从q中拿出一个值
print(results)
完整的代码
import threading
import time
from queue import Queue
def job(l,q):
for i in range (len(l)):
l[i] = l[i]**2
q.put(l)
def multithreading():
q =Queue()
threads = []
data = [[1,2,3],[3,4,5],[4,4,4],[5,5,5]]
for i in range(4):
t = threading.Thread(target=job,args=(data[i],q))
t.start()
threads.append(t)
for thread in threads:
thread.join()
results = []
for _ in range(4):
results.append(q.get())
print(results)
if __name___=='__main__':
multithreading()
最后运行结果为:
[[1, 4, 9], [9, 16, 25], [16, 16, 16], [25, 25, 25]]
尽管Python完全支持多线程编程, 但是解释器的C语言实现部分在完全并行执行时并不是线程安全的。 实际上,解释器被一个全局解释器锁保护着,它确保任何时候都只有一个Python线程执行。 GIL最大的问题就是Python的多线程程序并不能利用多核CPU的优势 (比如一个使用了多个线程的计算密集型程序只会在一个单CPU上面运行)。
在讨论普通的GIL之前,有一点要强调的是GIL只会影响到那些严重依赖CPU的程序(比如计算型的)。 如果你的程序大部分只会涉及到I/O,比如网络交互,那么使用多线程就很合适, 因为它们大部分时间都在等待。实际上,你完全可以放心的创建几千个Python线程, 现代操作系统运行这么多线程没有任何压力,没啥可担心的。
测试 GIL
我们创建一个 job
, 分别用 threading 和 一般的方式执行这段程序. 并且创建一个 list 来存放我们要处理的数据. 在 Normal 的时候, 我们这个 list 扩展4倍, 在 threading 的时候, 我们建立4个线程, 并对运行时间进行对比.
import threading
from queue import Queue
import copy
import time
def job(l, q):
res = sum(l)
q.put(res)
def multithreading(l):
q = Queue()
threads = []
for i in range(4):
t = threading.Thread(target=job, args=(copy.copy(l), q), name='T%i' % i)
t.start()
threads.append(t)
[t.join() for t in threads]
total = 0
for _ in range(4):
total += q.get()
print(total)
def normal(l):
total = sum(l)
print(total)
if __name__ == '__main__':
l = list(range(1000000))
s_t = time.time()
normal(l*4)
print('normal: ',time.time()-s_t)
s_t = time.time()
multithreading(l)
print('multithreading: ', time.time()-s_t)
如果你成功运行整套程序, 你大概会有这样的输出. 我们的运算结果没错, 所以程序 threading 和 Normal 运行了一样多次的运算. 但是我们发现 threading 却没有快多少, 按理来说, 我们预期会要快3-4倍, 因为有建立4个线程, 但是并没有. 这就是其中的 GIL 在作怪.
1999998000000
normal: 0.10034608840942383
1999998000000
multithreading: 0.08421492576599121
不使用 Lock 的情况
函数一:全局变量A的值每次加1,循环10次,并打印
def job1():
global A
for i in range(10):
A+=1
print('job1',A)
函数二:全局变量A的值每次加10,循环10次,并打印
def job2():
global A
for i in range(10):
A+=10
print('job2',A)
主函数:定义两个线程,分别执行函数一和函数二
if __name__== '__main__':
A=0
t1=threading.Thread(target=job1)
t2=threading.Thread(target=job2)
t1.start()
t2.start()
t1.join()
t2.join()
完整代码:
import threading
def job1():
global A
for i in range(10):
A+=1
print('job1',A)
def job2():
global A
for i in range(10):
A+=10
print('job2',A)
if __name__== '__main__':
lock=threading.Lock()
A=0
t1=threading.Thread(target=job1)
t2=threading.Thread(target=job2)
t1.start()
t2.start()
t1.join()
t2.join()
运行结果(在spyder编译器下运行的打印结果):
job1job2 11
job2 21
job2 31
job2 41
job2 51
job2 61
job2 71
job2 81
job2 91
job2 101
1
job1 102
job1 103
job1 104
job1 105
job1 106
job1 107
job1 108
job1 109
job1 110
可以看出,打印的结果非常混乱
使用 Lock 的情况
lock在不同线程使用同一共享内存时,能够确保线程之间互不影响,使用lock的方法是, 在每个线程执行运算修改共享内存之前,执行lock.acquire()
将共享内存上锁, 确保当前线程执行时,内存不会被其他线程访问,执行运算完毕后,使用lock.release()
将锁打开, 保证其他的线程可以使用该共享内存。
函数一和函数二加锁
def job1():
global A,lock
lock.acquire()
for i in range(10):
A+=1
print('job1',A)
lock.release()
def job2():
global A,lock
lock.acquire()
for i in range(10):
A+=10
print('job2',A)
lock.release()
主函数中定义一个Lock
if __name__== '__main__':
lock=threading.Lock()
A=0
t1=threading.Thread(target=job1)
t2=threading.Thread(target=job2)
t1.start()
t2.start()
t1.join()
t2.join()
完整的代码
import threading
def job1():
global A,lock
lock.acquire()
for i in range(10):
A+=1
print('job1',A)
lock.release()
def job2():
global A,lock
lock.acquire()
for i in range(10):
A+=10
print('job2',A)
lock.release()
if __name__== '__main__':
lock=threading.Lock()
A=0
t1=threading.Thread(target=job1)
t2=threading.Thread(target=job2)
t1.start()
t2.start()
t1.join()
t2.join()
运行结果
job1 1
job1 2
job1 3
job1 4
job1 5
job1 6
job1 7
job1 8
job1 9
job1 10
job2 20
job2 30
job2 40
job2 50
job2 60
job2 70
job2 80
job2 90
job2 100
job2 110
从打印结果来看,使用lock
后,一个一个线程执行完。使用lock
和不使用lock
,最后打印输出的结果是不同的。