Python课程笔记(十一)
一、线程与多线程
1、线程与进程
线程指的是 进程(运行中的程序)中单一顺序的执行流。
多个独立执行的线程相加 = 一个进程
多线程程序是指一个程序中包含有多个执行流,多线程是实现并发机制的一种有效手段。
2、线程的生命周期
每个线程都要经历创建
、就绪
、运行
、阻塞
和死亡
等5个状态,线程从产生到消失的状态变化称之为生命周期
从图中可以看到,一个线程的生命周期一般经过如下步骤:
- 一个线程通过实例化创建线程对象后,进入新生状态。
- 线程对象通过调用
start()
方法进入就绪状态,一个处在就绪状态的线程将被调度执行,执行该线程相应的run()
方法中的代码。 - 通过调用线程的(或从 Object类继承过来的)
sleep()
、wait()
、suspent()
方法,这个线程进入阻塞状态。一个线程也可能自己完成阻塞操作。 - 当
run()
方法执行完毕,或者有一个例外产生,或者执行System. exit()
方法,则一个线程就进入死亡状态。
方法 | 说明 |
---|---|
_ _ init _ _(self,name=threadname) | 初始化方法,threadname为线程名称 |
run() | 编写线程代码,实现线程所有要完成的功能 |
getName() | 获得线程对象名字 |
setName() | 设置线程对象名字 |
start() | 启动线程 |
jion([timeout]) | 等待另一线程结束后再运行 |
setDaemon(bool) | 子线程是否随主线程一起结束,必须在start()之前调用,默认false |
isAlive() | 检查线程是否在运行中 |
3、创建线程
Python中,可采用两种方式创建线程:
- 应用
Thread
类的构造函数创建一个多线程对象
import threading
def fun(i):
print("thread id = %d \n" %i)
def main():
for i in range(1,10):
t = threading.Thread(target=fun, args=(i,))
t.start()
if __name__ == "__main__":
main()
'''
thread id = 1
thread id = 2
thread id = 5
thread id = 3
thread id = 6
thread id = 4
thread id = 9
thread id = 8
thread id = 7
'''
- 通过创建
Thread
类的子类来构造线程,并重写它的run
方法
import threading
import time
# 定义线程子类
class MyThread(threading.Thread):
def __init__(self): #, func, args, name=''):
threading.Thread.__init__(self)
#self.func = func
#self.args = args
#self.name = name
def run(self):
print("starting", self.name)
def main():
t1 = MyThread()
t1.start()
t2 = MyThread()
t2.start()
if __name__ == "__main__":
main()
'''
starting Thread-1
starting Thread-2
'''
4、比较二种创建线程对象
我们用Thread子类程序来模拟航班售票系统,实现二个售票窗口发售某班次航班的10张机票,一个售票窗口用一个线程来表示。
import threading
import time
# 定义线程子类
class MyThread(threading.Thread):
tickets = 10
def __init__(self):
threading.Thread.__init__(self)
def run(self):
while(1):
if(self.tickets>0):
self.tickets = self.tickets-1
print(self.name,"售机票售出第",self.tickets, " 号")
else:
exit()
def main():
t1 = MyThread()
t1.start()
t2 = MyThread()
t2.start()
if __name__ == "__main__":
main()
'''
Thread-1 售机票售出第 9 号
Thread-1 售机票售出第 8 号
Thread-1 售机票售出第 7 号
Thread-2 售机票售出第 9 号
Thread-1 售机票售出第 6 号
Thread-2 售机票售出第 8 号
Thread-1 售机票售出第 5 号
Thread-2 售机票售出第 7 号
Thread-1 售机票售出第 4 号
Thread-2 售机票售出第 6 号
Thread-1 售机票售出第 3 号
Thread-2 售机票售出第 5 号
Thread-1 售机票售出第 2 号
Thread-2 售机票售出第 4 号
Thread-1 售机票售出第 1 号
Thread-1 售机票售出第 0 号
Thread-2 售机票售出第 3 号
Thread-2 售机票售出第 2 号
Thread-2 售机票售出第 1 号
Thread-2 售机票售出第 0 号
'''
从运行结果中可以看到,每张机票被卖了2次,即2个线程各自卖10张机票,而不是去卖共同的10张机票。为什么会这样呢?我们需要的是,多个线程去处理同一资源,一个资源只能对应一个对象。而在上面的程序中,我们创建了2个MyThread
对象,每个MyThread
对象中都有10张机票,每个线程都在独立地处理各自的资源。
我们用Thread类的构造函数创建的线程程序来模拟航班售票系统,实现二个售票窗口发售某班次航班的10张机票,一个售票窗口用一个线程来表示。
import threading
global tickets
tickets= 11
def fun(i):
global tickets
while(tickets>1):
tickets = tickets-1
print("第", i,"售机票窗口售出第",tickets, " 号")
def main():
for i in range(1,3):
t = threading.Thread(target=fun, args=(i,))
t.start()
if __name__ == "__main__":
main()
'''
第 1 售机票窗口售出第 10 号
第 1 售机票窗口售出第 9 号
第 1 售机票窗口售出第 8 号
第 1 售机票窗口售出第 6 号
第 2 售机票窗口售出第 7 号
第 1 售机票窗口售出第 5 号
第 2 售机票窗口售出第 4 号
第 1 售机票窗口售出第 3 号
第 2 售机票窗口售出第 2 号
第 1 售机票窗口售出第 1 号
'''
在上面的程序中,创建了2个线程,每个线程调用的是同一个Thread对象中的fun()方法,访问的是同一个对象中的变量(tickets)的实例。因此,这个程序能满足我们的要求。
5、线程同步
多线程使用不当可能造成数据混乱。例如,两个线程都要访问同一个共享变量,一个线程读这个变量的值并在这个值的基础上完成某些操作,但就在此时,另一个线程改变了这个变量值,但第一个线程并不知道,这可能造成数据混乱。
下面模拟二个用户从银行取款的操作造成数据混乱的一个例子。
设计一个模拟用户从银行取款的应用程序。设某银行帐户存款额的初值是2000元,用线程模拟两个用户从银行取款的情况。
import threading
import time
#定义银行帐户类
class Mbank:
global sum
sum=2000
def take(k):
global sum
temp=sum
temp=temp - k
time.sleep(0.2)
sum = temp
print("sum=",sum)
# 模拟用户取款的线程子类
class MyThread(threading.Thread):
tickets = 10
def __init__(self):
threading.Thread.__init__(self)
def run(self):
for i in range(1,5): #循环四次
Mbank.take(100)
def main():
t1 = MyThread()
t1.start()
t2 = MyThread()
t2.start()
if __name__ == "__main__":
main()
'''
sum= 1900
sum= 1900
sum= 1800
sum= 1800
sum= 1700
sum= 1700
sum= 1600
sum= 1600
'''
我们该如何解决呢?
6、线程同步的方法
使用同步线程是为了保证在一个进程中多个线程能协同工作,所以线程的同步很重要。所谓线程同步就是在执行多线程任务时,一次只能有一个线程访问共享资源,其他线程只能等待,只有当该线程完成自己的执行后,另外的线程才可以进入。
使用Thread对象的Lock
和Rlock
可以实现简单的线程同步,这两个对象都有acquire()
方法和release()
方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到acquire
和release
方法之间。
用线程同步的方法重新设计用户从银行取款的应用程序。
import threading
import time
threadLock = threading.Lock() #创建一个锁对象
#定义银行帐户类
class Mbank:
global sum
sum=2000
def take(k):
global sum
temp=sum
temp=temp - k
time.sleep(0.2)
sum = temp
print("sum=",sum)
# 模拟用户取款的线程子类
class MyThread(threading.Thread):
tickets = 10
def __init__(self):
threading.Thread.__init__(self)
def run(self):
for i in range(1,5):
threadLock.acquire() #获得锁
Mbank.take(100)
threadLock.release() #释放锁
def main():
t1 = MyThread()
t1.start()
t2 = MyThread()
t2.start()
if __name__ == "__main__":
main()
'''
sum= 1900
sum= 1800
sum= 1700
sum= 1600
sum= 1500
sum= 1400
sum= 1300
sum= 1200
'''
二、异常处理
1、异常
不严重的错误我们称之异常。(如:3/0
)
Python标准常见异常(网上摘抄滴):
异常名称 | 描述 |
---|---|
BaseException | 所有异常的基类 |
SystemExit | 解释器请求退出 |
KeyboardInterrupt | 用户中断执行(通常是输入^C) |
Exception | 常规错误的基类 |
StopIteration | 迭代器没有更多的值 |
GeneratorExit | 生成器(generator)发生异常来通知退出 |
StandardError | 所有的内建标准异常的基类 |
ArithmeticError | 所有数值计算错误的基类 |
FloatingPointError | 浮点计算错误 |
OverflowError | 数值运算超出最大限制 |
ZeroDivisionError | 除(或取模)零 (所有数据类型) |
AssertionError | 断言语句失败 |
AttributeError | 对象没有这个属性 |
EOFError | 没有内建输入,到达EOF 标记 |
EnvironmentError | 操作系统错误的基类 |
IOError | 输入/输出操作失败 |
OSError | 操作系统错误 |
WindowsError | 系统调用失败 |
ImportError | 导入模块/对象失败 |
LookupError | 无效数据查询的基类 |
IndexError | 序列中没有此索引(index) |
KeyError | 映射中没有这个键 |
MemoryError | 内存溢出错误(对于Python 解释器不是致命的) |
NameError | 未声明/初始化对象 (没有属性) |
UnboundLocalError | 访问未初始化的本地变量 |
ReferenceError | 弱引用(Weak reference)试图访问已经垃圾回收了的对象 |
RuntimeError | 一般的运行时错误 |
NotImplementedError | 尚未实现的方法 |
SyntaxError | Python 语法错误 |
IndentationError | 缩进错误 |
TabError | Tab 和空格混用 |
SystemError | 一般的解释器系统错误 |
TypeError | 对类型无效的操作 |
ValueError | 传入无效的参数 |
UnicodeError | Unicode 相关的错误 |
UnicodeDecodeError | Unicode 解码时的错误 |
UnicodeEncodeError | Unicode 编码时错误 |
UnicodeTranslateError | Unicode 转换时错误 |
Warning | 警告的基类 |
DeprecationWarning | 关于被弃用的特征的警告 |
FutureWarning | 关于构造将来语义会有改变的警告 |
OverflowWarning | 旧的关于自动提升为长整型(long)的警告 |
PendingDeprecationWarning | 关于特性将会被废弃的警告 |
RuntimeWarning | 可疑的运行时行为(runtime behavior)的警告 |
SyntaxWarning | 可疑的语法的警告 |
UserWarning | 用户代码生成的警告 |
2、异常的捕捉与处理
(1)使用try...except语句:
try:
<被检测的语句块>
except <异常类型名称>:
<处理异常的语句块>
例如:元组下标越界引发异常
s=[1,2,3,4,5]
try:
print(s[5])
except IndexError:
print("索引出界")
'''
发生异常原因:索引出界
'''
(2)使用try...except...else语句:
try:
<被检测的语句块>
except <异常类型名称>:
<处理异常的语句块>
else:
<无异常时执行的语句块>
例如:编写程序,从键盘输入1,2,……,5中的一个数字,否则,当输入其他数字或字符则提示为异常。
s=[1,2,3,4,5]
while True:
try:
i = eval(input('input:'))
print(s[i])
except IndexError:
print("发生异常原因:索引出界")
break
except NameError:
print("发生异常原因:不是数字")
break
except KeyboardInterrupt:
print("发生异常原因:用户中断输入")
break
else:
pass
(3)带有finally子句的try语句:
s=input("请输入你的年龄:")
if s =="":
raise Exception("输入不能为空.")
try:
i=int(s)
except Exception as err:
print(err)
finally:
print("Goodbye!")
三、正则表达式
1、字符匹配与匹配模式
(1)字符匹配
我们先从简单的问题开始。假设要搜索一个包含字符“cat”的字符串,搜索用的子字符串就是“cat”。如果搜索对大小写不敏感,单词“catalog”、“Catherine”、“sophisticated”都可以匹配。也就是说:
子字符串:cat
匹配:catalog、Catherine、sophisticated
(2)匹配模式
例如,使用?
和*
通配符来查找硬盘上的文件。?
通配符匹配文件名中的1个字符,而*
通配符匹配多个字符。这时,?
和*
通配符就是一种匹配模式。
比如,data?.dat
这样的模式将查找下列文件:
data.dat
data1.dat
data2.dat
datax.dat
dataN.dat
若使用*
字符代替?
字符扩大了找到的文件的数量。data*.dat
匹配下列所有文件:
data.dat
data1.dat
data2.dat
data12.dat
datax.dat
dataXYZ.dat
2、正则表达式的规则
正则表达式是一种可以用于模式匹配和替换的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。一个正则表达式就是由普通的字符(例如字符‘a’~‘z’)以及特殊字符(称为“元字符”)组成的文字模式。该模式用以描述在查找文字主体时待匹配的一个或多个字符串。
正则表达式的使用,可以通过简单的办法来实现强大的功能。
下面先看一个简单的用特殊字符(元字符)表示正则表达式规则的示例:
^ [ 0 – 9 ] + abc$
其中:
-
^
匹配字符串的开始位置。 -
[0-9]+
匹配多个数字,[0-9]
匹配单个数字,+
匹配一个或者多个。 -
abc$
匹配字母 abc 并以“abc”结尾 -
$
为匹配输入字符串的结束位置。
该规则表示,需要匹配以数字开头并以“abc”结尾的字符串。
编写程序,匹配以数字开头,并以“abc”结尾的字符串。
import re
str = r"123abc" # 需要匹配的源文本
p1 = r"^[0-9]+abc$" # 编写正则表达式规则
patt1 = re.compile(p1) # 编译正则表达式
matc1 = re.search(patt1, str) # 在源文本看搜索符合正则表达式的部分
print(matc1.group(0)) # 123abc
匹配的流程如下:
3、正则表达式re模块的方法
(1)re模块的方法
正则表达式re模块提供了正则表达式操作所需要的方法,利用这些方法,可以很方便地得到匹配结果。
(2)模式对象的方法
- group():
group()用于获取子模式(组)的匹配项。
例如:
pat = re.compile(r'www\.(.*)\.(.*)') # 用()表示1个组,这里定义2个组
m = pat.match('www.dxy.com')
m.group() # 默认值为0,表示匹配整个字符串,返回'www.dxy.com'
m.group(1) # 返回给定组1匹配的子字符串'dxy'
m.group(2) # 返回给定组2匹配的子字符串'com'
- start():
start()为指定组匹配项的开始位置
例如:
m.start(2) # 组2开始的索引,返回值为8
- end():
end()为指定组匹配项的结束位置
例如:
m.end(2) # 组2结束的索引,返回值为11
小案例演示:
编译正则表达式,创建模式对象示例
import re
pat=re.compile('A') #编译正则表达式
m=pat.search('CBA') #等价于 re.search('A','CBA')
print(m)
#匹配到了,返回<_sre.SRE_Match object: span=(2, 3)>
m=pat.search('CBD')
print(m)
#没有匹配到,返回None(False)
在一个字符串中查找子串示例
#第一步,要引入re模块
import re
#第二步,调用模块函数
a = re.findall("我在吃大西瓜呢", "博客园的我在吃大西瓜呢正在准备考研,他要考计算机专业")
print(a) #以列表形式返回匹配到的字符串
三、一个案例
应用多线程,编写一个“幸运大转盘”抽奖游戏程序。
######################
#幸运大转盘 #
######################
import tkinter
import time
import threading
root = tkinter.Tk()
root.title('“幸运大转盘”抽奖游戏')
root.minsize(300,300)
btn1 = tkinter.Button(text = '奔驰',bg = 'red')
btn1.place(x = 20, y = 20, width = 50, height = 50)
btn2 = tkinter.Button(text = '宝马',bg = 'white')
btn2.place(x = 90, y = 20, width = 50, height = 50)
btn3 = tkinter.Button(text = '奥迪',bg = 'white')
btn3.place(x = 160, y = 20, width = 50, height = 50)
btn4 = tkinter.Button(text = '日产',bg = 'white')
btn4.place(x = 230, y = 20, width = 50, height = 50)
btn5 = tkinter.Button(text = '宾利',bg = 'white')
btn5.place(x = 230, y = 90, width = 50, height = 50)
btn6 = tkinter.Button(text = '劳斯',bg = 'white')
btn6.place(x = 230, y = 160, width = 50, height = 50)
btn7 = tkinter.Button(text = '奇瑞',bg = 'white')
btn7.place(x = 230, y = 230, width = 50, height = 50)
btn8 = tkinter.Button(text = '吉利',bg = 'white')
btn8.place(x = 160, y = 230, width = 50, height = 50)
btn9 = tkinter.Button(text = '大众',bg = 'white')
btn9.place(x = 90, y = 230, width = 50, height = 50)
btn10 = tkinter.Button(text = '沃尔沃',bg = 'white')
btn10.place(x = 20, y = 230, width = 50, height = 50)
btn11 = tkinter.Button(text = '红旗',bg = 'white')
btn11.place(x = 20, y = 160, width = 50, height = 50)
btn12 = tkinter.Button(text = '长城',bg = 'white')
btn12.place(x = 20, y = 90, width = 50, height = 50)
#将所有选项组成列表
carlist = [btn1,btn2,btn3,btn4,btn5,btn6,btn6,btn7,btn8,btn9,btn10,btn11,btn12]
#是否开始循环的标志
isloop = False
def round():
#判断是否开始循环
if isloop == True:
return
#初始化计数 变量
i = 0
#死循环
while True:
time.sleep(0.1)
#将所有的组件背景变为白色
for x in carlist:
x['bg'] = 'white'
#将当前数值对应的组件变色
carlist[i]['bg'] = 'red'
#变量+1
i += 1
#如果i大于最大索引直接归零
if i >= len(carlist):
i = 0
if functions ==True :
continue
else :
break
#“开始”按钮事件:建立一个新线程的函数。
def newtask():
global isloop
global functions
#建立新线程
t = threading.Thread(target= round)
#开启线程运行
t.start()
#设置程序开始标志
isloop = True
#是否运行的标志
functions = True
#“停止”按钮事件:停止循环
def stop():
global isloop
global functions
functions = False
isloop = False
#开始按钮
btn_start = tkinter.Button(root,text = '开始',command = newtask)
btn_start.place(x = 90, y = 120, width = 50, height = 50)
btn_stop = tkinter.Button(root,text = '停止',command = stop)
btn_stop.place(x = 160, y = 120, width = 50, height = 50)
root.mainloop()
四、大数据预备学习
下节课要讲,很是期待啊,大学都是学Javaweb了,从来没有尝试去接触大数据学习。
大数据(Big Data)有5大特性:volume(大量)、velocity(高速)、variety(多样)、value(低价值密度)、veracity(真实性)
一些准备,下载以下三个库,网速慢就找三方库:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 库名
数据挖掘: jieba
(分析《三国演义》人物出场次数统计)
数据可视化:numpy
、pandas
、matplotlib
(霍兰德“人格分析”雷达图)
虽然网上可能很早就有了,但是毕竟是入门,值得期待。