【Python语言初识(五)】

一、文件和异常

在Python中实现文件的读写操作其实非常简单,通过Python内置的open函数,我们可以指定文件名、操作模式、编码信息等来获得操作文件的对象,接下来就可以对文件进行读写操作了。这里所说的操作模式是指要打开什么样的文件(字符文件还是二进制文件)以及做什么样的操作(读、写还是追加),具体的如下表所示。

操作模式具体含义
'r'读取 (默认)
'w'写入(会先截断之前的内容)
'x'写入,如果文件已经存在会产生异常
'a'追加,将内容写入到已有文件的末尾
'b'二进制模式
't'文本模式(默认)
'+'更新(既可以读又可以写)

下面这张图来自于菜鸟教程网站,它展示了如果根据应用程序的需要来设置操作模式。

1.1、读写文本文件 

读取文本文件时,需要在使用open函数时指定好带路径的文件名(可以使用相对路径或绝对路径)并将文件模式设置为'r'(如果不指定,默认值也是'r'),然后通过encoding参数指定编码(如果不指定,默认值是None,那么在读取文件时使用的是操作系统默认的编码),如果不能保证保存文件时使用的编码方式与encoding参数指定的编码方式是一致的,那么就可能因无法解码字符而导致读取失败。下面的例子演示了如何读取一个纯文本文件。

def main():f = open('致橡树.txt', 'r', encoding='utf-8')print(f.read())f.close()if __name__ == '__main__':main()

请注意上面的代码,如果open函数指定的文件并不存在或者无法打开,那么将引发异常状况导致程序崩溃。为了让代码有一定的健壮性和容错性,我们可以使用Python的异常机制对可能在运行时发生状况的代码进行适当的处理,如下所示。

def main():f = Nonetry:f = open('致橡树.txt', 'r', encoding='utf-8')print(f.read())except FileNotFoundError:print('无法打开指定的文件!')except LookupError:print('指定了未知的编码!')except UnicodeDecodeError:print('读取文件时解码错误!')finally:if f:f.close()if __name__ == '__main__':main()

在Python中,我们可以将那些在运行时可能会出现状况的代码放在try代码块中,在try代码块的后面可以跟上一个或多个except来捕获可能出现的异常状况。例如在上面读取文件的过程中,文件找不到会引发FileNotFoundError,指定了未知的编码会引发LookupError,而如果读取文件时无法按指定方式解码会引发UnicodeDecodeError,我们在try后面跟上了三个except分别处理这三种不同的异常状况。最后我们使用finally代码块来关闭打开的文件,释放掉程序中获取的外部资源,由于finally块的代码不论程序正常还是异常都会执行到(甚至是调用了sys模块的exit函数退出Python环境,finally块都会被执行,因为exit函数实质上是引发了SystemExit异常),因此我们通常把finally块称为“总是执行代码块”,它最适合用来做释放外部资源的操作。如果不愿意在finally代码块中关闭文件对象释放资源,也可以使用上下文语法,通过with关键字指定文件对象的上下文环境并在离开上下文环境时自动释放文件资源,代码如下所示。

def main():try:with open('致橡树.txt', 'r', encoding='utf-8') as f:print(f.read())except FileNotFoundError:print('无法打开指定的文件!')except LookupError:print('指定了未知的编码!')except UnicodeDecodeError:print('读取文件时解码错误!')if __name__ == '__main__':main()

除了使用文件对象的read方法读取文件之外,还可以使用for-in循环逐行读取或者用readlines方法将文件按行读取到一个列表容器中,代码如下所示。

import timedef main():# 一次性读取整个文件内容with open('致橡树.txt', 'r', encoding='utf-8') as f:print(f.read())# 通过for-in循环逐行读取with open('致橡树.txt', mode='r') as f:for line in f:print(line, end='')time.sleep(0.5)print()# 读取文件按行读取到列表中with open('致橡树.txt') as f:lines = f.readlines()print(lines)if __name__ == '__main__':main()

要将文本信息写入文件文件也非常简单,在使用open函数时指定好文件名并将文件模式设置为'w'即可。注意如果需要对文件内容进行追加式写入,应该将模式设置为'a'。如果要写入的文件不存在会自动创建文件而不是引发异常。下面的例子演示了如何将1-9999之间的素数分别写入三个文件中(1-99之间的素数保存在a.txt中,100-999之间的素数保存在b.txt中,1000-9999之间的素数保存在c.txt中)。

from math import sqrtdef is_prime(n):"""判断素数的函数"""assert n > 0for factor in range(2, int(sqrt(n)) + 1):if n % factor == 0:return Falsereturn True if n != 1 else Falsedef main():filenames = ('a.txt', 'b.txt', 'c.txt')fs_list = []try:for filename in filenames:fs_list.append(open(filename, 'w', encoding='utf-8'))for number in range(1, 10000):if is_prime(number):if number < 100:fs_list[0].write(str(number) + '\n')elif number < 1000:fs_list[1].write(str(number) + '\n')else:fs_list[2].write(str(number) + '\n')except IOError as ex:print(ex)print('写文件时发生错误!')finally:for fs in fs_list:fs.close()print('操作完成!')if __name__ == '__main__':main()

1.2、读写二进制文件

知道了如何读写文本文件要读写二进制文件也就很简单了,下面的代码实现了复制图片文件的功能。 

def main():try:with open('beautiful.png', 'rb') as fs1:data = fs1.read()print(type(data))  # <class 'bytes'>with open('super.png', 'wb') as fs2:fs2.write(data)except FileNotFoundError as e:print('指定的文件无法打开.')except IOError as e:print('读写文件时出现错误.')print('程序执行结束.')

1.3、读写JSON文件 

如果希望把一个列表或者一个字典中的数据保存到文件中又该怎么做呢?答案是将数据以JSON格式进行保存。JSON是“JavaScript Object Notation”的缩写,它本来是JavaScript语言中创建对象的一种字面量语法,现在已经被广泛的应用于跨平台跨语言的数据交换,原因很简单,因为JSON也是纯文本,任何系统任何编程语言处理纯文本都是没有问题的。目前JSON基本上已经取代了XML作为异构系统间交换数据的事实标准。关于JSON的知识,更多的可以参考JSON的官方网站,从这个网站也可以了解到每种语言处理JSON数据格式可以使用的工具或三方库,下面是一个JSON的简单例子。

{"name": "刘子翊","age": 24,"qq": 957658,"friends": ["王大锤", "白元芳"],"cars": [{"brand": "BYD", "max_speed": 180},{"brand": "Audi", "max_speed": 280},{"brand": "Benz", "max_speed": 320}]
}

可能大家已经注意到了,上面的JSON跟Python中的字典其实是一样一样的,事实上JSON的数据类型和Python的数据类型是很容易找到对应关系的,如下面两张表所示。

JSONPython
objectdict
arraylist
stringstr
number (int / real)int / float
true / falseTrue / False
nullNone
PythonJSON
dictobject
list, tuplearray
strstring
int, float, int- & float-derived Enumsnumber
True / Falsetrue / false
Nonenull

我们使用Python中的json模块就可以将字典或列表以JSON格式保存到文件中,代码如下所示。

import jsondef main():mydict = {'name': '刘子翊','age': 24,'qq': 231234,'friends': ['王大锤', '白元芳'],'cars': [{'brand': 'BYD', 'max_speed': 180},{'brand': 'Audi', 'max_speed': 280},{'brand': 'Benz', 'max_speed': 320}]}try:with open('data.json', 'w', encoding='utf-8') as fs:json.dump(mydict, fs)except IOError as e:print(e)print('保存数据完成!')

json模块主要有四个比较重要的函数,分别是:

  • dump - 将Python对象按照JSON格式序列化到文件中
  • dumps - 将Python对象处理成JSON格式的字符串
  • load - 将文件中的JSON数据反序列化成对象
  • loads - 将字符串的内容反序列化成Python对象

这里出现了两个概念,一个叫序列化,一个叫反序列化。“序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换为可以存储或传输的形式,这样在需要的时候能够恢复到原先的状态,而且通过序列化的数据重新获取字节时,可以利用这些字节来产生原始对象的副本(拷贝)。与这个过程相反的动作,即从一系列字节中提取数据结构的操作,就是反序列化(deserialization)”。

目前绝大多数网络数据服务(或称之为网络API)都是基于HTTP协议提供JSON格式的数据,关于HTTP协议的相关知识,可以看看阮一峰老师的《HTTP协议入门》,如果想了解国内的网络数据服务,可以看看聚合数据和阿凡达数据等网站,国外的可以看看{API}Search网站。下面的例子演示了如何使用requests模块(封装得足够好的第三方网络访问模块)访问网络API获取国内新闻,如何通过json模块解析JSON数据并显示新闻标题,这个例子使用了天行数据提供的国内新闻数据接口,其中的APIKey需要自己到该网站申请。

import requests
import jsondef main():resp = requests.get('https://apis.tianapi.com/topnews/index?key=APIKey276d4391c55b8b74a52d79525bb77f99&num=10')data_model = json.loads(resp.text)for news in data_model['result']['list']:print(news['title'])if __name__ == '__main__':main()

在Python中要实现序列化和反序列化除了使用json模块之外,还可以使用pickle和shelve模块,但是这两个模块是使用特有的序列化协议来序列化数据,因此序列化后的数据只能被Python识别。关于这两个模块的相关知识可以自己看看网络上的资料。另外,如果要了解更多的关于Python异常机制的知识,可以看看segmentfault上面的文章《总结:Python中的异常处理》,这篇文章不仅介绍了Python中异常机制的使用,还总结了一系列的最佳实践,很值得一读。 

二、进程和线程

今天我们使用的计算机早已进入多CPU或多核时代,而我们使用的操作系统都是支持“多任务”的操作系统,这使得我们可以同时运行多个程序,也可以将一个程序分解为若干个相对独立的子任务,让多个子任务并发的执行,从而缩短程序的执行时间,同时也让用户获得更好的体验。因此在当下不管是用什么编程语言进行开发,实现让程序同时执行多个任务也就是常说的“并发编程”。为此,我们需要先讨论两个概念,一个叫进程,一个叫线程。

2.1、概念

进程就是操作系统中执行的一个程序,操作系统以进程为单位分配存储空间,每个进程都有自己的地址空间、数据栈以及其他用于跟踪进程执行的辅助数据,操作系统管理所有进程的执行,为它们合理的分配资源。进程可以通过fork或spawn的方式来创建新的进程来执行其他的任务,不过新的进程也有自己独立的内存空间,因此必须通过进程间通信机制(IPC,Inter-Process Communication)来实现数据共享,具体的方式包括管道、信号、套接字、共享内存区等。

一个进程还可以拥有多个并发的执行线索,简单的说就是拥有多个可以获得CPU调度的执行单元,这就是所谓的线程。由于线程在同一个进程下,它们可以共享相同的上下文,因此相对于进程而言,线程间的信息共享和通信更加容易。当然在单核CPU系统中,真正的并发是不可能的,因为在某个时刻能够获得CPU的只有唯一的一个线程,多个线程共享了CPU的执行时间。使用多线程实现并发编程为程序带来的好处是不言而喻的,最主要的体现在提升程序的性能和改善用户体验。

当然多线程也并不是没有坏处,站在其他进程的角度,多线程的程序对其他程序并不友好,因为它占用了更多的CPU执行时间,导致其他程序无法获得足够的CPU执行时间;另一方面,站在开发者的角度,编写和调试多线程的程序都对开发者有较高的要求,对于初学者来说更加困难。

Python既支持多进程又支持多线程,因此使用Python实现并发编程主要有3种方式:多进程、多线程、多进程+多线程。

2.2、Python中的多进程

Unix和Linux操作系统上提供了fork()系统调用来创建进程,调用fork()函数的是父进程,创建出的是子进程,子进程是父进程的一个拷贝,但是子进程拥有自己的PID。fork()函数非常特殊它会返回两次,父进程中可以通过fork()函数的返回值得到子进程的PID,而子进程中的返回值永远都是0。Python的os模块提供了fork()函数。由于Windows系统没有fork()调用,因此要实现跨平台的多进程编程,可以使用multiprocessing模块的Process类来创建子进程,而且该模块还提供了更高级的封装,例如批量启动进程的进程池(Pool)、用于进程间通信的队列(Queue)和管道(Pipe)等。

下面用一个下载文件的例子来说明使用多进程和不使用多进程到底有什么差别,先看看下面的代码。

from random import randint
from time import time, sleepdef download_task(filename):print('开始下载%s...' % filename)time_to_download = randint(5, 10)sleep(time_to_download)print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))def main():start = time()download_task('Python从入门到住院.pdf')download_task('Peking Hot.avi')end = time()print('总共耗费了%.2f秒.' % (end - start))if __name__ == '__main__':main()

下面是运行程序得到的一次运行结果。

开始下载Python从入门到住院.pdf...
Python从入门到住院.pdf下载完成! 耗费了6秒
开始下载Peking Hot.avi...
Peking Hot.avi下载完成! 耗费了7秒
总共耗费了13.01秒.

从上面的例子可以看出,如果程序中的代码只能按顺序一点点的往下执行,那么即使执行两个毫不相关的下载任务,也需要先等待一个文件下载完成后才能开始下一个下载任务,很显然这并不合理也没有效率。接下来我们使用多进程的方式将两个下载任务放到不同的进程中,代码如下所示。

from multiprocessing import Process
from os import getpid
from random import randint
from time import time, sleepdef download_task(filename):print('启动下载进程,进程号[%d].' % getpid())print('开始下载%s...' % filename)time_to_download = randint(5, 10)sleep(time_to_download)print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))def main():start = time()p1 = Process(target=download_task, args=('Python从入门到住院.pdf', ))p1.start()p2 = Process(target=download_task, args=('Peking Hot.avi', ))p2.start()p1.join()p2.join()end = time()print('总共耗费了%.2f秒.' % (end - start))if __name__ == '__main__':main()

在上面的代码中,我们通过Process类创建了进程对象,通过target参数我们传入一个函数来表示进程启动后要执行的代码,后面的args是一个元组,它代表了传递给函数的参数。Process对象的start方法用来启动进程,而join方法表示等待进程执行结束。运行上面的代码可以明显发现两个下载任务“同时”启动了,而且程序的执行时间将大大缩短,不再是两个任务的时间总和。下面是程序的一次执行结果。

启动下载进程,进程号[1530].
开始下载Python从入门到住院.pdf...
启动下载进程,进程号[1531].
开始下载Peking Hot.avi...
Peking Hot.avi下载完成! 耗费了7秒
Python从入门到住院.pdf下载完成! 耗费了10秒
总共耗费了10.01秒.

我们也可以使用subprocess模块中的类和函数来创建和启动子进程,然后通过管道来和子进程通信,这些内容我们不在此进行讲解,有兴趣的读者可以自己了解这些知识。接下来我们将重点放在如何实现两个进程间的通信。我们启动两个进程,一个输出Ping,一个输出Pong,两个进程输出的Ping和Pong加起来一共10个。听起来很简单吧,但是如果这样写可是错的哦。

from multiprocessing import Process
from time import sleepcounter = 0def sub_task(string):global counterwhile counter < 10:print(string, end='', flush=True)counter += 1sleep(0.01)def main():Process(target=sub_task, args=('Ping', )).start()Process(target=sub_task, args=('Pong', )).start()if __name__ == '__main__':main()

看起来没毛病,但是最后的结果是Ping和Pong各输出了10个,Why?当我们在程序中创建进程的时候,子进程复制了父进程及其所有的数据结构,每个子进程有自己独立的内存空间,这也就意味着两个子进程中各有一个counter变量,所以结果也就可想而知了。要解决这个问题比较简单的办法是使用multiprocessing模块中的Queue类,它是可以被多个进程共享的队列,底层是通过管道和信号量(semaphore)机制来实现的。

2.2.1、介绍multiprocessing模块中的Queue

multiprocessing.Queue() 是 Python 中 multiprocessing 模块中的一个类,用于在多个进程之间进行通信和数据交换。它可以在多个进程之间安全地传递消息和数据,是一种线程安全的队列实现。
在使用 multiprocessing.Queue() 时,需要注意避免在多个进程之间传递可变对象(如列表、字典等),因为这可能会导致意外的行为。最好传递不可变对象(如整数、字符串等)或使用 multiprocessing.Manager 来创建共享数据结构。

multiprocess.Queue() 是跨进程通信队列,不能用于multiprocessing.Pool多进程的通信。

进程池 multiprocessing.Pool() 的多进程之间的通信要用 multiprocessing.Manager().Queue()

参数:

def __init__(self, maxsize=0, *, ctx)

maxsize:是队列中允许的最大项数。如果省略此参数,则无大小限制。

方法:

  1. put(item[, block[, timeout]]):将 item 放入队列。
  2. put_nowait():同 put()。
  3. get([block[, timeout]]):从队列中取出一个元素。
  4. get_nowait():同 get()。
    注意:当一个队列为空的时候如果再用get取则会堵塞,所以取队列的时候一般是用到
  5. get_nowait()方法,这种方法在向一个空队列取值的时候会抛一个Empty异常
    所以更常用的方法是先判断一个队列是否为空,如果不为空则取值。
  6. qsize():返回队列中当前的元素数量。
  7. empty():如果队列为空则返回 True,否则返回 False
  8. full():如果队列已满则返回 True,否则返回 False
  9. close():关闭队列,表示不再向队列中添加数据。
  10. join_thread():等待队列中的所有数据被处理完。
  11. cancel_join_thread():取消队列中的所有数据被处理完。

2.3、Python中的多线程 

在Python早期的版本中就引入了thread模块(现在名为_thread)来实现多线程编程,然而该模块过于底层,而且很多功能都没有提供,因此目前的多线程开发我们推荐使用threading模块,该模块对多线程编程提供了更好的面向对象的封装。我们把刚才下载文件的例子用多线程的方式来实现一遍。

from random import randint
from threading import Thread
from time import time, sleepdef download(filename):print('开始下载%s...' % filename)time_to_download = randint(5, 10)sleep(time_to_download)print('%s下载完成! 耗费了%d秒' % (filename, time_to_download))def main():start = time()t1 = Thread(target=download, args=('Python从入门到住院.pdf',))t1.start()t2 = Thread(target=download, args=('Peking Hot.avi',))t2.start()t1.join()t2.join()end = time()print('总共耗费了%.3f秒' % (end - start))if __name__ == '__main__':main()

我们可以直接使用threading模块的Thread类来创建线程,但是我们之前讲过一个非常重要的概念叫“继承”,我们可以从已有的类创建新类,因此也可以通过继承Thread类的方式来创建自定义的线程类,然后再创建线程对象并启动线程。代码如下所示。

from random import randint
from threading import Thread
from time import time, sleepclass DownloadTask(Thread):def __init__(self, filename):super().__init__()self._filename = filenamedef run(self):print('开始下载%s...' % self._filename)time_to_download = randint(5, 10)sleep(time_to_download)print('%s下载完成! 耗费了%d秒' % (self._filename, time_to_download))def main():start = time()t1 = DownloadTask('Python从入门到住院.pdf')t1.start()t2 = DownloadTask('Peking Hot.avi')t2.start()t1.join()t2.join()end = time()print('总共耗费了%.2f秒.' % (end - start))if __name__ == '__main__':main()

因为多个线程可以共享进程的内存空间,因此要实现多个线程间的通信相对简单,大家能想到的最直接的办法就是设置一个全局变量,多个线程共享这个全局变量即可。但是当多个线程共享同一个变量(我们通常称之为“资源”)的时候,很有可能产生不可控的结果从而导致程序失效甚至崩溃。如果一个资源被多个线程竞争使用,那么我们通常称之为“临界资源”,对“临界资源”的访问需要加上保护,否则资源会处于“混乱”的状态。下面的例子演示了100个线程向同一个银行账户转账(转入1元钱)的场景,在这个例子中,银行账户就是一个临界资源,在没有保护的情况下我们很有可能会得到错误的结果。

from time import sleep
from threading import Threadclass Account(object):def __init__(self):self._balance = 0def deposit(self, money):# 计算存款后的余额new_balance = self._balance + money# 模拟受理存款业务需要0.01秒的时间sleep(0.01)# 修改账户余额self._balance = new_balance@propertydef balance(self):return self._balanceclass AddMoneyThread(Thread):def __init__(self, account, money):super().__init__()self._account = accountself._money = moneydef run(self):self._account.deposit(self._money)def main():account = Account()threads = []# 创建100个存款的线程向同一个账户中存钱for _ in range(100):t = AddMoneyThread(account, 1)threads.append(t)t.start()# 等所有存款的线程都执行完毕for t in threads:t.join()print('账户余额为: ¥%d元' % account.balance)if __name__ == '__main__':main()

运行上面的程序,结果让人大跌眼镜,100个线程分别向账户中转入1元钱,结果居然远远小于100元。之所以出现这种情况是因为我们没有对银行账户这个“临界资源”加以保护,多个线程同时向账户中存钱时,会一起执行到new_balance = self._balance + money这行代码,多个线程得到的账户余额都是初始状态下的0,所以都是0上面做了+1的操作,因此得到了错误的结果。在这种情况下,“锁”就可以派上用场了。我们可以通过“锁”来保护“临界资源”,只有获得“锁”的线程才能访问“临界资源”,而其他没有得到“锁”的线程只能被阻塞起来,直到获得“锁”的线程释放了“锁”,其他线程才有机会获得“锁”,进而访问被保护的“临界资源”。下面的代码演示了如何使用“锁”来保护对银行账户的操作,从而获得正确的结果。

from time import sleep
from threading import Thread, Lockclass Account(object):def __init__(self):self._balance = 0self._lock = Lock()def deposit(self, money):# 先获取锁才能执行后续的代码self._lock.acquire()try:new_balance = self._balance + moneysleep(0.01)self._balance = new_balancefinally:# 在finally中执行释放锁的操作保证正常异常锁都能释放self._lock.release()@propertydef balance(self):return self._balanceclass AddMoneyThread(Thread):def __init__(self, account, money):super().__init__()self._account = accountself._money = moneydef run(self):self._account.deposit(self._money)def main():account = Account()threads = []for _ in range(100):t = AddMoneyThread(account, 1)threads.append(t)t.start()for t in threads:t.join()print('账户余额为: ¥%d元' % account.balance)if __name__ == '__main__':main()

比较遗憾的一件事情是Python的多线程并不能发挥CPU的多核特性,这一点只要启动几个执行死循环的线程就可以得到证实了。之所以如此,是因为Python的解释器有一个“全局解释器锁”(GIL)的东西,任何线程执行前必须先获得GIL锁,然后每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行,这是一个历史遗留问题,但是即便如此,就如我们之前举的例子,使用多线程在提升执行效率和改善用户体验方面仍然是有积极意义的。 

2.4、单线程+异步I/O 

现代操作系统对I/O操作的改进中最为重要的就是支持异步I/O。如果充分利用操作系统提供的异步I/O支持,就可以用单进程单线程模型来执行多任务,这种全新的模型称为事件驱动模型。Nginx就是支持异步I/O的Web服务器,它在单核CPU上采用单进程模型就可以高效地支持多任务。在多核CPU上,可以运行多个进程(数量与CPU核心数相同),充分利用多核CPU。用Node.js开发的服务器端程序也使用了这种工作模式,这也是当下并发编程的一种流行方案。

在Python语言中,单线程+异步I/O的编程模型称为协程,有了协程的支持,就可以基于事件驱动编写高效的多任务程序。协程最大的优势就是极高的执行效率,因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销。协程的第二个优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不用加锁,只需要判断状态就好了,所以执行效率比多线程高很多。如果想要充分利用CPU的多核特性,最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

2.5、应用案例

2.5.1、例子1:将耗时间的任务放到线程中以获得更好的用户体验。 

如下所示的界面中,有“下载”和“关于”两个按钮,用休眠的方式模拟点击“下载”按钮会联网下载文件需要耗费10秒的时间,如果不使用“多线程”,我们会发现,当点击“下载”按钮后整个程序的其他部分都被这个耗时间的任务阻塞而无法执行了,这显然是非常糟糕的用户体验,代码如下所示。

import time
import tkinter
import tkinter.messageboxdef download():"""模拟下载任务需要花费10s时间"""time.sleep(10)tkinter.messagebox.showinfo('提示', '下载完成!')def show_about():tkinter.messagebox.showinfo('作者', '刘子翊(v1.0)')def main():top = tkinter.Tk()top.title('单线程')top.geometry('200x150')top.wm_attributes('-topmost', True)panel = tkinter.Frame(top)button1 = tkinter.Button(panel, text='下载', command=download)button1.pack(side='left')button2 = tkinter.Button(panel, text='关于', command=show_about)button2.pack(side='right')panel.pack(side='bottom')tkinter.mainloop()

wm_attributes是Tk和Toplevel组件的一个方法,用于设置窗口管理器的属性。它可以接受以下参数:

-alpha:设置窗口透明度,取值为0.0(完全透明)到1.0(完全不透明)之间的浮点数。
-topmost:设置窗口是否置顶,取值为0或1。
-disabled:设置窗口是否被禁用,取值为0或1。
-fullscreen:设置窗口是否全屏显示,取值为0或1。
-toolwindow:设置窗口是否为工具窗口,取值为0或1。
-transparentcolor:设置窗口的透明颜色,取值为颜色名称或#RRGGBB格式的十六进制值。
-modified:设置窗口是否被修改,取值为0或1。
其中,只有-alpha和-topmost参数是在所有平台上都可用的,其他参数的可用性可能因平台而异。

框架Frame是一个容器控件,当我们设计的GUI程序很复杂时,此时可以考虑将一系列相关的Widget控件组织在一个框架内,这样比较方便管理。

如果使用多线程将耗时间的任务放到一个独立的线程中执行,这样就不会因为执行耗时间的任务而阻塞了主线程,修改后的代码如下所示。

import time
import tkinter
import tkinter.messagebox
from threading import Threaddef main():class DownloadTaskHandler(Thread):def run(self):time.sleep(10)tkinter.messagebox.showinfo('提示', '下载成功!')# 启用下载按钮button1.config(state=tkinter.NORMAL)def download():# 禁用下载按钮button1.config(state=tkinter.DISABLED)# 通过deamon参数将线程设置为守护线程(主程序退出就不再保留执行)# 在线程中处理耗时间的下载任务DownloadTaskHandler(daemon=True).start()def show_about():tkinter.messagebox.showinfo('关于', '作者: 刘子翊(v1.0)')top = tkinter.Tk()top.title('多线程')top.geometry('200x150')top.wm_attributes('-topmost', 1)panel = tkinter.Frame(top)button1 = tkinter.Button(panel, text='下载', command=download)button1.pack(side='left')button2 = tkinter.Button(panel, text='关于', command=show_about)button2.pack(side='right')panel.pack(side='bottom')tkinter.mainloop()

2.5.2、例子2:使用多进程对复杂任务进行“分而治之”。 

我们来完成1~100000000求和的计算密集型任务,这个问题本身非常简单,有点循环的知识就能解决,代码如下所示。

from time import timedef main():total = 0number_list = [x for x in range(1, 100000001)]start = time()for number in number_list:total += numberprint(total)end = time()print('Execution time: %.3fs' % (end - start))if __name__ == '__main__':main()

在上面的代码中,我故意先去创建了一个列表容器然后填入了100000000个数,这一步其实是比较耗时间的,所以为了公平起见,当我们将这个任务分解到8个进程中去执行的时候,我们暂时也不考虑列表切片操作花费的时间,只是把做运算和合并运算结果的时间统计出来,代码如下所示。

from multiprocessing import Process, Queue
from random import randint
from time import timedef task_handler(curr_list, result_queue):total = 0for number in curr_list:total += numberresult_queue.put(total)def main():processes = []number_list = [x for x in range(1, 100000001)]result_queue = Queue()index = 0# 启动八个进程将数据切片后运算for _ in range(8):p = Process(target=task_handler, args=(number_list[index:index+12500000], result_queue))index += 12500000processes.append(p)p.start()# 开始记录所有进程执行完成花费的时间start = time()for p in processes:p.join()# 合并执行结果total = 0while not result_queue.empty():total += result_queue.get()print(total)end = time()print('Execution time:', (start - end), 's',sep='')# sep参数是用来设定print()中的多个对象之间的连接符号是什么,默认是空格,而print中多个对象之间是通过逗号,来分隔。if __name__ == '__main__':main()

我在Win10的电脑系统上运行第一个代码大约花费4s,第二个代码大约花费1s,我们只是比较了运算的时间,不考虑列表创建及切片操作花费的时间,使用多进程后由于获得了更多的CPU执行时间以及更好的利用了CPU的多核特性,明显的减少了程序的执行时间,而且计算量越大效果越明显。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/54836.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

泛型(Java)

1.泛型&#xff1a; 将数据类型作为参数进行传递。(传递的数据类型必须是引用数据类型) 本质是参数化类型。 泛型集合&#xff1a;可以约束集合内的元素类型 典型泛型集合ArrayList<E>、HashMap<K,V> <E>、<K,V>表示该泛型集合中的元素类型泛型集合中的…

计算机视觉学习路线

计算机视觉&#xff08;Computer Vision&#xff09;是计算机科学的一个重要分支&#xff0c;旨在使计算机能够理解和解释视觉数据。以下是一个详细的计算机视觉学习路线&#xff0c;帮你系统地掌握这个领域所需的知识和技能。 1. 基础数学和编程 在深入学习计算机视觉之前&…

引入Scrum激发研发体系活力

引言 在当今快速变化的技术环境中&#xff0c;IT企业面临着持续的市场压力和竞争&#xff0c;传统的瀑布式开发模式已经难以满足现代企业的需要。瀑布模型过于僵化&#xff0c;缺乏灵活性&#xff0c;导致项目经常延期&#xff0c;成本增加&#xff0c;最终可能无法达到预期效果…

【Qualcomm】高通SNPE框架的使用 | 原始模型转换为量化的DLC文件 | 在Android的DSP端运行模型

目录 ① 激活snpe环境 ② 设置环境变量 ③ 模型转换 ④ run 首先&#xff0c;默认SNPE工具已经下载并且Setup相关工作均已完成。同时&#xff0c;拥有原始模型文件&#xff0c;本文使用的模型文件为SNPE 框架示例的inception_v3_2016_08_28_frozen.pb文件。image_file_list…

数据集-目标检测系列-口罩检测数据集 mask>> DataBall

数据集-目标检测系列-口罩检测数据集 mask>> DataBall 数据集-目标检测系列-口罩检测数据集 mask 数据量&#xff1a;1W DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;享有百种数据集&#xff0c;持续增加中。 数据项目地址&#xff1a; gitcode: https…

【Python报错已解决】TypeError: list indices must be integers or slices, not str

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 专栏介绍 在软件开发和日常使用中&#xff0c;BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

打造同城O2O平台:外卖跑腿APP的架构与功能设计详解

今天&#xff0c;小编将于大家共同讨论外卖跑腿APP的架构设计及其核心功能&#xff0c;旨在为开发者提供一份详尽的参考。 一、外卖跑腿APP的架构设计 1.整体架构概述 通常包括前端、后端和数据库。 2.前端设计 用户端提供直观的界面&#xff0c;方便用户下单、查询订单状态…

初学51单片机之I2C总线与E2PROM

首先先推荐B站的I2C相关的视频I2C入门第一节-I2C的基本工作原理_哔哩哔哩_bilibili 看完视频估计就大概知道怎么操作I2C了&#xff0c;他的LCD1602讲的也很不错&#xff0c;把数据建立tsp和数据保持thd&#xff0c;比喻成拍照时候的摆pose和按快门两个过程&#xff0c;感觉还是…

C语言实现归并排序(Merge Sort)

目录 一、递归实现归并排序 1. 归并排序的基本步骤 2.动图演示 3.基本思路 4.代码 二、非递归实现 1.部分代码 2.代码分析 修正后代码&#xff1a; 归并过程打印 性能分析 复杂度分析 归并排序是一种高效的排序算法&#xff0c;采用分治法&#xff08;Divide and Con…

javase复习day35反射

反射 获取class对象的方法 public class Demo1 {public static void main(String[] args) throws ClassNotFoundException {//获取反射的三种方式//第一种 Class.forName(全类名)//用法&#xff1a;最为常用Class<?> clazz1 Class.forName("Reflection.Student&q…

程序员如何以最快的方式提升自己?分享4个有效方法!

作家周国平说&#xff1a;人与人之间最重要的区别&#xff0c;不在物质的贫富和社会方面的境遇&#xff0c;是内在的素质和层次&#xff0c;把人分出了伟大与渺小、优秀与平庸。有的人醉心于三五成群的消遣&#xff0c;有的人专注于一步一脚印的努力&#xff0c;人和人之间的差…

Shiro-550—漏洞分析(CVE-2016-4437)

文章目录 漏洞原理源码分析加密过程解密过程 漏洞复现 漏洞原理 Shiro-550(CVE-2016-4437)反序列化漏洞 在调试cookie加密过程的时候发现开发者将AES用来加密的密钥硬编码了&#xff0c;并且所以导致我们拿到密钥后可以精心构造恶意payload替换cookie&#xff0c;然后让后台最…

利用Puppeteer-Har记录与分析网页抓取中的性能数据

引言 在现代网页抓取中&#xff0c;性能数据的记录与分析是优化抓取效率和质量的重要环节。本文将介绍如何利用Puppeteer-Har工具记录与分析网页抓取中的性能数据&#xff0c;并通过实例展示如何实现这一过程。 Puppeteer-Har简介 Puppeteer是一个Node.js库&#xff0c;提供…

VUE.js笔记

1.介绍vue Vue 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c;帮助你高效地开发用户界面。无论是简单还是复杂的界面&#xff0c;Vue 都可以胜任。 Vue 应用程序的基本…

初识C语言(三)

感兴趣的朋友们可以留个关注&#xff0c;我们共同交流&#xff0c;相互促进学习。 文章目录 前言 八、函数 九、数组 &#xff08;1&#xff09;数组的定义 &#xff08;2&#xff09;数组的下标和使用 十、操作符 &#xff08;1&#xff09;算数操作符 &#xff08;2&#xff…

统计本周的订单数,统计最近7天的订单数

3个函数 DATE_SUB和SUBDATE在MySQL中的作用是一样的&#xff0c;它们都是用于执行日期的减法运算。具体来说&#xff0c;这两个函数都允许你从给定的日期或日期时间值中减去一个指定的时间间隔&#xff0c;然后返回一个新的日期或日期时间值。 DATE函数 DATE(time) 用于获取…

多机部署,负载均衡-LoadBalance

文章目录 多机部署,负载均衡-LoadBalance1. 开启多个服务2. 什么是负载均衡负载均衡的实现客户端负载均衡 3. Spring Cloud LoadBalance快速上手使用Spring Cloud LoadBalance实现负载均衡修改IP,端口号为服务名称启动多个服务 负载均衡策略自定义负载均衡策略 LoadBalance原理…

图像处理04

图像处理 问题&#xff1a;把不规则的图片按照参考图摆放 步骤&#xff1a; 1. 用ORB找关键点 2. 关键点匹配 3. 根据上一步匹配的关键点得出单应性矩阵 4. 根据单应性矩阵对不规则进行透视变换 import cv2 import numpy as np import matplotlib.pyplot as pltimgl cv2.imrea…

liunxcentos7下 跟目录空间不足docker load镜像报错空间不足

前两天在公司&#xff0c;做jenkins流水线项目&#xff0c;然后把项目放到docker容器里面运行&#xff0c;就在我把镜像打好包的时候正准备往服务器里面导入镜像的时候报错&#xff1a;如图所示 这时发现自己的根目录空间不足。 解决办法&#xff1a;重新加一块磁盘将磁盘挂载…

Java线程池和原子性

文章目录 前言1 线程池1.1 线程池概述1.1.1 线程池存在的意义1.1.2 Executors默认线程池 1.2 线程状态介绍1.2.1 线程状态源码1.2.2 线程状态含义1.2.3 线程状态转换图 2 原子性2.1 volatile关键字2.2 synchronized解决2.3 原子性2.4 AtomicInteger类2.5 悲观锁和乐观锁 前言 …