Python编程让繁琐的工作自动化(7)-保持时间,计划任务和启动程序

利用脚本让计算机调度程序、定时运行或者在午夜让计算机执行CPU密集型任务是一件非常有效率的事情。

time模块

time模块为python内置模块,可以读取系统时钟的当前时间。unix纪元是编程中经常参考的时间:1970年1月1日0点,即协调世界时间UTC。time.time()函数返回自那一刻以来的秒数,是一个浮点值,这个时间被称为UNIX纪元时间戳。

# 纪元时间戳可以用于剖析代码
import time 
def calcProd(): 
    # Calculate the product of the first 100,000 numbers. 
    product = 1 
    for i in range(1, 100000): 
        product = product * i 
    return product 
 
startTime = time.time() 
prod = calcProd() 
endTime = time.time() 
print('The result is %s digits long.' % (len(str(prod)))) 
print('Took %s seconds to calculate.' % (endTime - startTime)) 
# 另外,cProfile.run()函数也可以剖析代码并提供更多信息,这里不再介绍

# 让时间暂停1秒钟
import time 
>>> for i in range(3): 
           print('Tick') 
           time.sleep(1) 
           print('Tock') 
          time.sleep(1) 
 
Tick 
Tock 
Tick 
Tock 
Tick 
Tock 
 >>> time.sleep(5) 
#请注意,在 IDLE 中按 Ctrl-C 不会中断 time.sleep()调用。IDLE 会等待到暂停结束,再抛出 KeyboardInterrupt 异常。
#要绕过这个问题,不要用一次 time.sleep(30)调用来暂停 30 秒,而是使用 for 循环执行 30 次 time.sleep(1)调用。

# 数字四舍五入
import time 
>>> now = time.time() 
>>> now 
1425064108.017826 
>>> round(now, 2) 
1425064108.02 
>>> round(now, 4) 
1425064108.0178 
>>> round(now) 
1425064108  

项目:超级秒表

假设要记录在没有自动化的枯燥任务上花了多少时间。你没有物理秒表,但是要为
笔记本或智能手机找到一个免费的秒表应用(没有广告,且不会将你的浏览历史发
送给市场营销人员)又出乎意料地困难。你可以自己用 Python 写一个简单的秒表程序。
总的来说,你的程序需要完成:

  1. 记录从按下回车键开始,每次按键的时间,每次按键都是一个新的“单圈”。
  2. 打印圈数、总时间和单圈时间。
    这意味着代码将需要完成以下任务:
  3. 在程序开始时,通过调用 time.time()得到当前时间,将它保存为一个时间戳。
    在每个单圈开始时也一样。
  4. 记录圈数,每次用户按下回车键时加 1。
  5. 用时间戳相减,得到计算流逝的时间。
  6. 处理 KeyboardInterrupt 异常,这样用户可以按 Ctrl-C 退出。
    打开一个新的文件编辑器窗口,并保存为 stopwatch.py
#第 1 步:设置程序来记录时间 
#秒表程序需要用到当前时间,所以要导入的 time 模块。程序在调用 input()之前,也应该向用户打印一些简短的说明,这样计时器可以在用户按下回车键后开始。
#! python3 
# stopwatch.py - A simple stopwatch program. 
 
import time 
 
# Display the program's instructions. 
print('Press ENTER to begin. Afterwards, press ENTER to "click" the stopwatch. 
Press Ctrl-C to quit.') 
input()          # press Enter to begin 
print('Started.') 
startTime = time.time()    # get the first lap's start time 
lastTime = startTime 
lapNum = 1 
 
# 第 2 步:记录并打印单圈时间 
# Start tracking the lap times. 
try: 
    while True: 
        input() 
        lapTime = round(time.time() - lastTime, 2) 
        totalTime = round(time.time() - startTime, 2) 
        print('Lap #%s: %s (%s)' % (lapNum, totalTime, lapTime), end='') 
        lapNum += 1 
        lastTime = time.time() # reset the last lap time 
except KeyboardInterrupt: 
    # Handle the Ctrl-C exception to keep its error message from displaying. 
    print('\nDone.') 

类似程序的想法

时间追踪为程序打开了几种可能性。虽然可以下载应用程序来做其中一些事
情,但自己编程的好处是它们是免费的,而且不会充斥着广告和无用的功能。可以
编写类似的程序来完成以下任务:

  1. 创建一个简单的工时表应用程序,当输入一个人的名字时,用当前的时间记录
    下他们进入或离开的时间。
  2. 为你的程序添加一个功能,显示自一项处理开始以来的时间,诸如利用 requests
    模块进行的下载(参见第 11 章)。
  3. 间歇性地检查程序已经运行了多久,并为用户提供了一个机会,取消耗时太长
    的任务。

datetime模块

time模块用于取得unix时间戳,但是如果需要更方便的显示日期或者对日期进行算术运算,要使用datetime模块。datetime模块有自己的datetime数据类型,datetime值表示一个特定时刻。

>>> import datetime 
# 返回一个datetime对象,这个对象包含当前时刻的年,月,日,时,分,秒和微秒
>>> datetime.datetime.now() 
datetime.datetime(2015, 2, 27, 11, 10, 49, 55, 53) 
# 通过传递参数,也可以创建一个datetime对象
>>> 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对象,表示unix纪元后1000000秒的时刻
>>> datetime.datetime.fromtimestamp(1000000) 
datetime.datetime(1970, 1, 12, 5, 46, 40) 
#  返回当前的datetime对象,和datetime.datetime.now()一样
>>> datetime.datetime.fromtimestamp(time.time()) 
datetime.datetime(2015, 2, 27, 11, 13, 0, 604980) 
# 比较时间的先后顺序
>>> halloween2015 = datetime.datetime(2015, 10, 31, 0, 0, 0) 
>>> newyears2016 = datetime.datetime(2016, 1, 1, 0, 0, 0) 
>>> oct31_2015 = datetime.datetime(2015, 10, 31, 0, 0, 0) 
>>> halloween2015 == oct31_2015 
True 
>>> halloween2015 > newyears2016 
False 
>>> newyears2016 > halloween2015 
True 
>>> newyears2016 != oct31_2015 
True 

#timedelta对象,表示一段时间
# 通过timedelta创建对象,但不接受month和year关键字参数,因为这两个参数是可变的。
# timedelta对象拥有的总时间以天,秒,微妙来表示,将timedelta对象传入str函数,会返回可读性良好的字符串表示。
>>> 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' 

# 算术运算符可以用于对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) 
>>> 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)   

# 暂停至特定的日期
import datetime 
import time 
halloween2016 = datetime.datetime(2016, 10, 31, 0, 0, 0) 
while datetime.datetime.now() < halloween2016: 
    time.sleep(1) 
  
#将datetime对象转换为字符串
#Unix 纪元时间戳和 datetime 对象对人类来说都不是很友好可读。利用 strftime()方法,可以将 datetime 对象显示为字符串。
#strftime()函数名中的 f 表示格式,format。 
# strftime 指令    含义 
# %Y  带世纪的年份,例如'2014' 
# %y  不带世纪的年份,'00'至'99'(1970 至 2069) 
# %m  数字表示的月份, '01'至'12' 
# %B  完整的月份,例如'November' 
# %b  简写的月份,例如'Nov' 
# %d  一月中的第几天,'01'至'31' 
# %j  一年中的第几天,'001'至'366' 
# %w  一周中的第几天,'0'(周日)至'6'(周六) 
# %A  完整的周几,例如'Monday' 
# %a  简写的周几,例如'Mon' 
# %H  小时(24 小时时钟),'00'至'23' 
# %I  小时(12 小时时钟),'01'至'12' 
# %M  分,'00'至'59' 
# %S  秒,'00'至'59' 
# %p  'AM'或'PM' 
# %%  就是'%'字符 
#向 strftime()传入一个定制的格式字符串,其中包含格式化指定(以及任何需要的斜线、冒号等),strftime()将返回一个格式化的字符串,表示 datetime 对象的信息。
>>> 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" 
# 将字符串转换成datetime对象
# 格式字符串传入 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) 

多线程

import threading, time 
print('Start of program.') 
 
def takeANap(): 
    time.sleep(5) 
    print('Wake up!') 
# 注意,关键字参数是 target=takeANap,而不是 target=takeANap()。这是因为你想将 takeANap()函数本身作为参数,而不是调用 takeANap(),并传入它的返回值
#创建线程对象
threadObj = threading.Thread(target=takeANap) 
# 创建新线程
threadObj.start() 
 
print('End of program.') 
# 向线程的目标函数传递参数,目标函数的常规参数可以作为列表传给args关键字参数,关键字参数可以传递给thread的kwargs。
>>> import threading 
>>> threadObj = threading.Thread(target=print, args=['Cats', 'Dogs', 'Frogs'], 
kwargs={'sep': ' & '}) 
>>> threadObj.start() 
Cats & Dogs & Frogs 

'''
线程的并发问题:可以轻松地创建多个新线程,让它们同时运行。但多线程也可能会导致所谓的
并发问题。如果这些线程同时读写变量,导致互相干扰,就会发生并发问题。并发
问题可能很难一致地重现,所以难以调试。 
多线程编程本身就是一个广泛的主题,超出了本书的范围。必须记住的是:为了避
免并发问题,绝不让多个线程读取或写入相同的变量。当创建一个新的 Thread 对象时,
要确保其目标函数只使用该函数中的局部变量。这将避免程序中难以调试的并发问题。
在 http://nostarch.com/automatestuff/,有关于多线程编程的初学者教程。 '''

项目:多线程 XKCD 下载程序

在第 11 章,你编写了一个程序,从 XKCD 网站下载所有的 XKCD 漫画。这是
一个单线程程序:它一次下载一幅漫画。程序运行的大部分时间,都用于建立网络
连接来开始下载,以及将下载的图像写入硬盘。如果你有宽带因特网连接,单线程
程序并没有充分利用可用的带宽。
多线程程序中有一些线程在下载漫画,同时另一些线程在建立连接,或将漫画图
像文件写入硬盘。它更有效地使用 Internet 连接,更迅速地下载这些漫画。打开一个新
的文件编辑器窗口,并保存为 multidownloadXkcd.py。你将修改这个程序,添加多线程。

# 第一步: 修改程序以使用函数
#! python3 
# multidownloadXkcd.py - Downloads XKCD comics using multiple threads. 
 
import requests, os, bs4, threading 
os.makedirs('xkcd', exist_ok=True)    # store comics in ./xkcd 
 
def downloadXkcd(startComic, endComic): 
    for urlNumber in range(startComic, endComic): 
        # Download the page. 
        print('Downloading page http://xkcd.com/%s...' % (urlNumber)) 
        res = requests.get('http://xkcd.com/%s' % (urlNumber)) 
        res.raise_for_status() 
 
        soup = bs4.BeautifulSoup(res.text) 
 
        # Find the URL of the comic image. 
        comicElem = soup.select('#comic img') 
        if comicElem == []: 
            print('Could not find comic image.') 
        else: 
            comicUrl = comicElem[0].get('src') 
            # Download the image. 
            print('Downloading image %s...' % (comicUrl)) 
            res = requests.get(comicUrl) 
            res.raise_for_status() 
 
            # Save the image to ./xkcd. 
            imageFile = open(os.path.join('xkcd', os.path.basename(comicUrl)), 'wb') 
            for chunk in res.iter_content(100000): 
                imageFile.write(chunk) 
            imageFile.close() 
 
# 第 2 步:创建并启动线程 
# Create and start the Thread objects. 
downloadThreads = []        # a list of all the Thread objects 
for i in range(0, 1400, 100):     # loops 14 times, creates 14 threads 
    downloadThread = threading.Thread(target=downloadXkcd, args=(i, i + 99)) 
    downloadThreads.append(downloadThread) 
    downloadThread.start()3 步:等待所有线程结束 
# Wait for all threads to end. 
for downloadThread in downloadThreads: 
    downloadThread.join() 
print('Done.') 

python启动其他程序

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

 >>> calcProc = subprocess.Popen('c:\\Windows\\System32\\calc.exe') 
# poll是轮询的意思,就是判断是否执行完这个进程,如果进程仍在运行,则返回None,如果已经完成,则返回整数退出代码,用于说明进程是否是无错终止,0表示无错。
 >>> calcProc.poll() == None 
True 
# wait会阻塞主进程,直到启动的进程终止。如果你希望你的程序暂停直到用户完成其他程序,则很有用。这里,我们关掉计算器就会显示进程已无错终止。 
 >>> calcProc.wait() 
0 
>>> calcProc.poll() 
0 
# 向Popen()传递命令行参数
# 向 Popen()传递一个列表,作为唯一的参数。该列表中的第一个字符串是要启动的程序的可执行文件名,所有后续的字符串将是该程序启动时,传递给该程序的命令行参数。
# 实际上,这个列表将作为被启动程序的 sys.argv 的值。
>>> subprocess.Popen(['C:\\Windows\\notepad.exe', 'C:\\hello.txt']) 
<subprocess.Popen object at 0x00000000032DCEB8> 
# 这不仅会启动记事本应用程序,也会让它立即打开 C:\hello.txt。 

''' Task Scheduler、launchd 和 cron 
如果你精通计算机,可能知道 Windows 上的 Task  Scheduler,OS  X 上的
launchd,或 Linux 上的 cron 调度程序。这些工具文档齐全,而且可靠,它们都允许
你安排应用程序在特定的时间启动。如果想更多地了解它们,可以在 http://nostarch. 
com/automatestuff/找到教程的链接。 
利用操作系统内置的调度程序,你不必自己写时钟检查代码来安排你的程序。但
是,如果只需要程序稍作停顿,就用 time.sleep()函数。或者不使用操作系统的调度程
序,代码可以循环直到特定的日期和时间,每次循环时调用 time.sleep(1)。 '''

# 不同于将 Python 程序导入为一个模块,如果 Python 程序启动了另一个 Python程序,两者将在独立的进程中运行,不能分享彼此的变量。 
'hello.py']) 
<subprocess.Popen object at 0x000000000331CF28> 

# 使用默认应用程序打开文件
#每个操作系统都有一个程序,其行为等价于双击文档文件来打开它。在 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) 
'''Unix 哲学 
程序精心设计,能被其他程序启动,这样的程序比单独使用它们自己的代码
更强大。Unix 的哲学是一组由 UNIX 操作系统(现代的 Linux 和 OS X 也是基于
它)的程序员建立的软件设计原则。它认为:编写小的、目的有限的、能互操作
的程序,胜过大的、功能丰富的应用程序。 
较小的程序更容易理解,通过能够互操作,它们可以是更强大的应用程序的
构建块。智能手机应用程序也遵循这种方式。如果你的餐厅应用程序需要显示一
间咖啡店的方位,开发者不必重新发明轮子,编写自己的地图代码。餐厅应用程
序只是启动一个地图应用程序,同时传入咖啡店的地址,就像 Python 代码调用
一个函数,并传入参数一样。 
你在本书中编写的 Python 程序大多符合 Unix 哲学,尤其是在一个重要的方
面:它们使用命令行参数,而不是 input()函数调用。如果程序需要的所有信息都
可以事先提供,最好是用命令行参数传入这些信息,而不是等待用户键入它。这
样,命令行参数可以由人类用户键入,也可以由另一个程序提供。这种互操作的方式,让你的程序可以作为另一个程序的部分而复用。 
唯一的例外是,你不希望口令作为命令行参数传入,因为命令行可能记录它们,
作为命令历史功能的一部分。在需要输入口令时,程序应该调用 input()函数。 
在 https://en.wikipedia.org/wiki/Unix_philosophy/,你可以阅读更多有关 Unix
哲学的内容。
'''

项目:简单的倒计时程序

就像很难找到一个简单的秒表应用程序一样,也很难找到一个简单的倒计时程
序。让我们来写一个倒计时程序,在倒计时结束时报警。
总的来说,程序要做到:

  1. 从 60 倒数。
  2. 倒数至 0 时播放声音文件(alarm.wav)。
    这意味着代码将需要做到以下几点:
  3. 在显示倒计时的每个数字之间,调用 time.sleep()暂停一秒。
  4. 调用 subprocess.Popen(),用默认的应用程序播放声音文件。
    打开一个新的文件编辑器窗口,并保存为 countdown.py
# 第 1 步:倒计时 
#! python3 
# countdown.py - A simple countdown script. 
 
import time, subprocess 
 
timeLeft = 60 
while timeLeft > 0: 
      print(timeLeft, end='') 
      time.sleep(1) 
      timeLeft = timeLeft - 1 
# 第2步: 播放声音文件
# At the end of the countdown, play a sound file. 
subprocess.Popen(['start', 'alarm.wav'], shell=True)  

类似程序的想法

倒计时是简单的延时,然后继续执行程序。这也可以用于其他应用程序和功能,
诸如:

  1. 利用 time.sleep()给用户一个机会,按下 Ctrl-C 取消的操作,例如删除文件。你
    的程序可以打印“Press Ctrl-C to cancel”,然后用 try 和 except 语句处理所有
    KeyboardInterrupt 异常。
  2. 对于长期的倒计时,可以用 timedelta 对象来测量直到未来某个时间点(生日?
    周年纪念?)的天、时、分和秒数。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值