《Python编程快速上手——让繁琐工作自动化》第十五章

第15章 保持时间、计划任务和启动程序

15.1 time模块

内置的time模块让Python程序能读取系统时钟的当前时间。在time模块中,time.time()和time.sleep()函数是最有用的模块。

15.1.1 time.time()函数

Unix纪元是编程中经常参考的时间:1970年1月1日0点,即协调世界时(UTC)。time.time()函数返回自那一刻以来的秒数,是一个浮点值。

纪元时间戳可以用于剖析代码,也就是测量一段代码的运行时间。如果在代码块开始时调用time.time(),并在结束时再次调用,就可以用第二个时间戳减去第一个,得到这两次调用之间经过的时间。

15.1.2 time.sleep()函数

如果需要让程序暂停一下,就调用time.sleep()函数,并传入希望程序暂停的秒数。

请注意,在IDLE中按Ctrl-C不会中断time.sleep()调用。IDLE会等待到暂停结束,再抛出KeyboardInterrupt异常。要绕过这个问题,不要用一次time.sleep(30)调用来暂停30秒,而是使用for循环执行30次time.sleep(1)调用。如果在这30秒内的某个时候按Ctrl-C,应该马上看到抛出KeyboardInterrupt异常。

15.2 数字四舍五入

以用Python内置的round()函数将浮点值缩短,该函数按照指定的精度四舍五入到一个浮点数。只要传入要舍入的数字,再加上可选的第二个参数,指明需要传入到小数点后多少位。如果省略第二个参数,round()将数字四舍五入到最接近的整数。

15.4 datetime模块

如果以更方便的格式显示日期,或对日期进行算术运算(例如,搞清楚205天前是什么日期,或123天后是什么日期),就应该使用datetime模块。

datetime模块有自己的datetime数据类型。datetime值表示一个特定的时刻。

>>> import datetime
❶ >>> datetime.datetime.now()
❷ datetime.datetime(2015, 2, 27, 11, 10, 49, 55, 53)
❸ >>> dt = datetime.datetime(2015, 10, 21, 16, 29, 0)
❹ >>> dt.year, dt.month, dt.day
 (2015, 10, 21)
  >>> dt.hour, dt.minute, dt.second
 (16, 29, 0)

调用datetime.datetime.now()❶返回一个datetime对象❷,表示当前的日期和时间,根据你的计算机的时钟。这个对象包含当前时刻的年、月、日、时、分、秒和微秒。也可以利用datetime.datetime()函数❸,向它传入代表年、月、日、时、分、秒的整数,得到特定时刻的datetime对象。这些整数将保存在datetime对象的year、month、day❹、hour、minute和second❺属性中。

datetime对象可以用比较操作符进行比较,弄清楚谁在前面。后面的datetime对象是“更大”的值。

15.4.1 timedelta数据类型

datetime模块还提供了timedelta数据类型,它表示一段时间,而不是一个时刻。

❶ >>> delta = datetime.timedelta(days=11, hours=10, minutes=9, seconds=8)
❷ >>> delta.days, delta.seconds, delta.microseconds
 (11, 36548, 0)
 >>> delta.total_seconds()
 986948.0
 >>> str(delta)
 '11 days, 10:09:08'

timedelta对象拥有的总时间以天、秒、微秒来表示。这些数字分别保存在days、seconds和microseconds属性中。total_seconds()方法返回只以秒表示的时间。将一个timedelta对象传入str(),将返回格式良好的、人类可读的字符串表示。

在这个例子中,我们将关键字参数传入datetime.delta(),指定11天、10小时、9分和8秒的时间,将返回的timedelta对象保存在delta中❶。该timedelta对象的days属性为11,seconds属性为36548(10小时、9分钟、8秒,以秒表示)❷。调用total_seconds()告诉我们,11天、10小时、9分和8秒是986948秒。最后,将这个timedelta对象传入str(),返回一个字符串,明确解释了这段时间。

算术运算符可以用于对datetime值进行日期运算。

>>> dt = datetime.datetime.now()
>>> dt
datetime.datetime(2015, 2, 27, 18, 38, 50, 636181)
>>> thousandDays = datetime.timedelta(days=1000)
>>> dt + thousandDays
datetime.datetime(2017, 11, 23, 18, 38, 50, 636181)

利用+和-运算符,timedelta对象与datetime对象或其他timedelta对象相加或相减。

❶ >>> oct21st = datetime.datetime(2015, 10, 21, 16, 29, 0)
❷ >>> aboutThirtyYears = datetime.timedelta(days=365 * 30)
 >>> oct21st
 datetime.datetime(2015, 10, 21, 16, 29)
 >>> oct21st - aboutThirtyYears
 datetime.datetime(1985, 10, 28, 16, 29)
 >>> oct21st - (2 * aboutThirtyYears)
  datetime.datetime(1955, 11, 5, 16, 29)

15.4.2 暂停直至特定日期

import datetime
import time
halloween2016 = datetime.datetime(2016, 10, 31, 0, 0, 0)
while datetime.datetime.now() <  halloween2016:
    time.sleep(1)

15.4.3 将datetime对象转换为字符串
Unix纪元时间戳和datetime对象对人类来说都不是很友好可读。利用strftime()方法,可以将datetime对象显示为字符串。(strftime()函数名中的f表示格式,format)。

向strftime()传入一个定制的格式字符串,其中包含格式化指定(以及任何需要的斜线、冒号等),strftime()将返回一个格式化的字符串,表示datetime对象的信息。

>>> oct21st = datetime.datetime(2015, 10, 21, 16, 29, 0)
>>> oct21st.strftime('%Y/%m/%d %H:%M:%S')
'2015/10/21 16:29:00'
>>> oct21st.strftime('%I:%M %p')
'04:29 PM'
>>> oct21st.strftime("%B of '%y")
"October of '15"

15.4.4 将字符串转换成datetime对象

如果有一个字符串的日期信息,如'2015/10/21 16:29:00'或'October 21, 2015',需要将它转换为datetime对象,就用datetime.datetime.strftime()函数。strptime()函数与strftime()方法相反。定制的格式字符串使用相同的指令,像strftime()一样。必须将格式字符串传入strptime(),这样它就知道如何解析和理解日期字符串(strptime()函数名中p表示解析,parse)。

❶ >>> datetime.datetime.strptime('October 21, 2015', '%B %d, %Y')
 datetime.datetime(2015, 10, 21, 0, 0)
 >>> datetime.datetime.strptime('2015/10/21 16:29:00', '%Y/%m/%d %H:%M:%S')
 datetime.datetime(2015, 10, 21, 16, 29)
 >>> datetime.datetime.strptime("October of '15", "%B of '%y")
 datetime.datetime(2015, 10, 1, 0, 0)
 >>> datetime.datetime.strptime("November of '63", "%B of '%y")
 datetime.datetime(2063, 11, 1, 0, 0)

15.5 回顾Python的时间函数

15.6 多线程

 

要理解什么是执行线程,就要回忆第2章关于控制流的讨论,当时你想象程序的执行就像把手指放在一行代码上,然后移动到下一行,或是流控制语句让它去的任何地方。单线程程序只有一个“手指”。但多线程的程序有多个“手指”。每个“手指”仍然移动到控制流语句定义的下一行代码,但这些“手指”可以在程序的不同地方,同时执行不同的代码行(到目前为止,本书所有的程序一直是单线程的)。

不必让所有的代码等待,直到time.sleep()函数完成,你可以使用Python的threading模块,在单独的线程中执行延迟或安排的代码。这个单独的线程将因为time.sleep()调用而暂停。同时,程序可以在原来的线程中做其他工作。

要得到单独的线程,首先要调用threading.Thread()函数,生成一个Thread对象。

import threading, time
 print('Start of program.')

❶ def takeANap():
     time.sleep(5)
     print('Wake up!')

❷ threadObj = threading.Thread(target=takeANap)
❸ threadObj.start()

 print('End of program.')

在❶行,我们定义了一个函数,希望用于新线程中。为了创建一个Thread对象,我们调用threading.Thread(),并传入关键字参数target=takeANap❷。这意味着我们要在新线程中调用的函数是takeANap()。请注意,关键字参数是target=takeANap,而不是target=takeANap()。这是因为你想将takeANap()函数本身作为参数,而不是调用takeANap(),并传入它的返回值。

我们将threading.Thread()创建的Thread对象保存在threadObj中,然后调用threadObj.start()❸,创建新的线程,并开始在新线程中执行目标函数。

15.6.1 向线程的目标函数传递参数

如果想在新线程中运行的目标函数有参数,可以将目标函数的参数传入threading.Thread()。

>>> import threading
>>> threadObj = threading.Thread(target=print, args=['Cats', 'Dogs', 'Frogs'],
kwargs={'sep': ' & '})
>>> threadObj.start()
Cats & Dogs & Frogs

为了确保参数'Cats'、'Dogs'和'Frogs'传递给新线程中的print(),我们将args=['Cats', 'Dogs', 'Frogs']传入threading.Thread()。为了确保关键字参数sep=' & '传递给新线程中的print(),我们将kwargs={'sep': '& '}传入threading.Thread()。

threadObj.start()调用将创建一个新线程来调用print()函数,它会传入'Cats'、'Dogs'和'Frogs'作为参数,以及' & '作为sep关键字参数。

15.6.2 并发问题

可以轻松地创建多个新线程,让它们同时运行。但多线程也可能会导致所谓的并发问题。如果这些线程同时读写变量,导致互相干扰,就会发生并发问题。并发问题可能很难一致地重现,所以难以调试。

为了避免并发问题,绝不让多个线程读取或写入相同的变量。当创建一个新的Thread对象时,要确保其目标函数只使用该函数中的局部变量。这将避免程序中难以调试的并发问题。

15.7 项目:多线程XKCD下载程序

15.8 从Python启动其他程序

利用内建的subprocess模块中的Popen()函数,Python程序可以启动计算机中的其他程序(Popen()函数名中的P表示process,进程)。如果你打开了一个应用程序的多个实例,每个实例都是同一个程序的不同进程。例如,如果你同时打开了Web浏览器的多个窗口,每个窗口都是Web浏览器程序的不同进程。

每个进程可以有多个线程。不像线程,进程无法直接读写另一个进程的变量。如果你认为多线程程序是多个手指在追踪源代码,那么同一个程序打开多个进程就像有一个朋友拿着程序源代码的独立副本。你们都独立地执行相同的程序。

如果想在Python脚本中启动一个外部程序,就将该程序的文件名传递给subprocess.Popen()(在Windows中,右键点击该应用程序的开始菜单项,然后选择“属性”,查看应用程序的文件名。在OS X上,按住Ctrl键单击该应用程序并选择“显示包内容”,找到可执行文件的路径)。Popen()函数随后将立即返回。请记住,启动的程序和你的Python程序不在同一线程中运行。

>>> import subprocess
>>> subprocess.Popen('C:\\Windows\\System32\\calc.exe')
< subprocess.Popen object at 0x0000000003055A58>

返回值是一个Popen对象,它有两个有用的方法:poll()和wait()。

可以认为poll()方法是问你的朋友,她是否执行完毕你给她的代码。如果这个进程在poll()调用时仍在运行,poll()方法就返回None。如果该程序已经终止,它会返回该进程的整数退出代码。退出代码用于说明进程是无错终止(退出代码为0),还是一个错误导致进程终止(退出代码非零,通常为1,但可能根据程序而不同)。

wait()方法就像是等着你的朋友执行完她的代码,然后你继续执行你的代码。wait()方法将阻塞,直到启动的进程终止。如果你希望你的程序暂停,直到用户完成与其他程序,这非常有用。wait()的返回值是进程的整数退出代码。

15.8.1 向Popen()传递命令行参数

用Popen()创建进程时,可以向进程传递命令行参数。要做到这一点,向Popen()传递一个列表,作为唯一的参数。该列表中的第一个字符串是要启动的程序的可执行文件名,所有后续的字符串将是该程序启动时,传递给该程序的命令行参数。实际上,这个列表将作为被启动程序的sys.argv的值。

大多数具有图形用户界面(GUI)的应用程序,不像基于命令行或基于终端的程序那样尽可能地使用命令行参数。但大多数GUI应用程序将接受一个参数,表示应用程序启动时立即打开的文件。例如,如果你使用的是Windows,创建一个简单的文本文件C:\hello.txt,然后在交互式环境中输入以下代码:

>>> subprocess.Popen(['C:\\Windows\\notepad.exe', 'C:\\hello.txt'])
< subprocess.Popen object at 0x00000000032DCEB8>

15.8.2 Task Scheduler、launchd和cron

http://nostarch. com/automatestuff/

15.8.3 用Python打开网站

15.8.4 运行其他Python脚本

可以在Python中启动另一个Python脚本,就像任何其他的应用程序一样。只需向Popen()传入python.exe可执行文件,并将想运行的.py脚本的文件名作为它的参数。

不同于将Python程序导入为一个模块,如果Python程序启动了另一个Python程序,两者将在独立的进程中运行,不能分享彼此的变量。

15.8.5 用默认的应用程序打开文件

每个操作系统都有一个程序,其行为等价于双击文档文件来打开它。在Windows上,这是start程序。在OS X上,这是open程序。在Ubuntu Linux上,这是see程序。在交互式环境中输入以下代码,根据操作系统,向Popen()传入'start'、'open'或'see':

>>> fileObj = open('hello.txt', 'w')
>>> fileObj.write('Hello world!')
12
>>> fileObj.close()
>>> import subprocess
>>> subprocess.Popen(['start', 'hello.txt'], shell=True)

这里,我们将Hello world!写入一个新的hello.txt文件。然后调用Popen(),传入一个列表,其中包含程序名称(在这个例子中,是Windows上的'start'),以及文件名。我们也传入了shell=True关键字参数,这只在Windows上需要。操作系统知道所有的文件关联,能弄清楚应该启动哪个程序,比如Notepad.exe,来处理hello.txt文件。

15.9 项目:简单的倒计时程序

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值