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对象的LockRlock可以实现简单的线程同步,这两个对象都有acquire()方法和release()方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到acquirerelease方法之间。

用线程同步的方法重新设计用户从银行取款的应用程序。

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 (分析《三国演义》人物出场次数统计)

数据可视化:numpypandasmatplotlib (霍兰德“人格分析”雷达图)

虽然网上可能很早就有了,但是毕竟是入门,值得期待。

posted @ 2020-05-06 16:49  我在吃大西瓜呢  阅读(198)  评论(0编辑  收藏  举报