跳转至

6.3.time

Windows 10
Python 3.7.3 @ MSC v.1915 64 bit (AMD64)
import time
import textwrap
import warnings
# from tools import Help as H
warnings.filterwarnings("ignore")

时间模块 time 提供了几种不同类型时间的方法,每一种对不同目的都有用。

  1. 标准系统通过调用 time() (模块)报告系统挂钟时间("wall clock" 一般指程序执行时间)。
  2. 单调时钟模块 monotonic() 用于估算长时间运行的程序的运行时间,因为即使系统时间发生了变化,它也保证不会后退。
  3. 对于程序测试,perf_counter() (模块) 提供了高分辨率的时钟访问方式,使短时间的测试更准确。
  4. CPU 时间可以通过 clock() 获得,而 process_time() 则会返回处理器时间和系统时间的组合。

各个时钟

时钟的实现细节因平台而异。使用 “get_clock_info ()” 来访问关于当前方式的基本信息,包括时钟的分辨率。

当前Python版本所有可用的时钟如下:

available_clocks = [
    ('clock', time.clock),
    ('monotonic', time.monotonic),
    ('perf_counter', time.perf_counter),
    ('process_time', time.process_time),
    ('time', time.time),
    ('thread_time', time.thread_time),
]
for clock_name, func in available_clocks:
    print(textwrap.dedent('''\
    {name}:
        adjustable    : {info.adjustable}
        implementation: {info.implementation}
        monotonic     : {info.monotonic}
        resolution    : {info.resolution}
        current       : {current}
    ''').format(
        name=clock_name,
        info=time.get_clock_info(clock_name),
        current=func())
    )
clock:
    adjustable    : False
    implementation: QueryPerformanceCounter()
    monotonic     : True
    resolution    : 1e-07
    current       : 516160.6853218

monotonic:
    adjustable    : False
    implementation: GetTickCount64()
    monotonic     : True
    resolution    : 0.015625
    current       : 1123921.625

perf_counter:
    adjustable    : False
    implementation: QueryPerformanceCounter()
    monotonic     : True
    resolution    : 1e-07
    current       : 516160.6890105

process_time:
    adjustable    : False
    implementation: GetProcessTimes()
    monotonic     : True
    resolution    : 1e-07
    current       : 565.125

time:
    adjustable    : True
    implementation: GetSystemTimeAsFileTime()
    monotonic     : False
    resolution    : 0.015625
    current       : 1580735857.8439054

thread_time:
    adjustable    : False
    implementation: GetThreadTimes()
    monotonic     : True
    resolution    : 1e-07
    current       : 528.671875

Windows 10 的输出显示其 perf_counter 和 clocks 模块调用相同的底层。clocks 在Python 3.8会被移除。

挂钟时间——time

time 模块的核心函数之一是 time() ,它可以把从 「epoch」 开始之后的秒数以浮点数的格式返回。

print('The time is:', time.time())
print('The time is:', time.time_ns())
The time is: 1580735857.8729014
The time is: 1580735857877901000

时元 (epoch) 是测量时间的开始,对于 Unix 系统来说,时元是 1970 年 1 月 1 日 0 点。 虽然该数值一直是一个浮点数,但是它的实际精度是依赖于平台的。

虽然浮点表示在存储或者比较日期时非常有效,但是,在生成人类可读的表示时就显得有点力不从心。对于记录或者打印时间, ctime() 可能会更有效。

ctime()将自epoch以来的时间(以秒为单位)转换为本地时间的字符串。这等效于asctime(localtime(seconds))。如果时间元组不存在,则使用localtime()返回的当前时间。

print('The time is:', time.ctime())
The time is: Mon Feb  3 21:17:37 2020

单调时钟——monotonic

因为 time() 函数返回的时间值是系统时钟,并且,为了在多台计算机之间同步时钟,系统时钟可以被用户或者是系统服务更改;所以,在重复调用 time() 函数时产生的时间值可能会有前后波动。在测量持续时间或者使用这些时间进行计算的时候,这可能会导致意料之外的行为。通过使用 monotonic() 函数就可以避免这些情况,因为 monotonic() 函数总是返回前向的时间值。

start = time.monotonic()
time.sleep(0.1)
end = time.monotonic()
print('start : {:>9.2f}'.format(start))
print('end   : {:>9.2f}'.format(end))
print('span  : {:>9.2f}'.format(end - start))
start : 1123921.73
end   : 1123921.84
span  :      0.11

我们并没有定义单调时钟的起始点。因此,只有在使用其它时钟值进行计算时,单调时钟的返回值才会有用。 在这个例子中,睡眠的持续时间就是用 monotonic() 函数测量的。

处理器时钟时间

time() 函数返回的是挂钟时间, perf_counter() 函数返回的是处理器时钟时间。 perf_counter() 函数的返回值反映了程序运行时使用的实际时间。

for i in range(4):
    time.sleep(1)
    print(time.ctime(), ': {:0.3f} {:0.3f}'.format(time.time(), time.perf_counter()))
Mon Feb  3 21:17:39 2020 : 1580735859.086 516161.933
Mon Feb  3 21:17:40 2020 : 1580735860.087 516162.934
Mon Feb  3 21:17:41 2020 : 1580735861.088 516163.935
Mon Feb  3 21:17:42 2020 : 1580735862.089 516164.936

如果你想要在自己的系统上运行这个例子,需要想办法增加单次内循环的循环周期或者显著地增加循环的总次数,才可以真正地看到时间差异。(如果循环周期太短,循环次数太少的话,返回的时间值可能还没有来得及变化,整个程序就结束了。)

通常情况下,如果程序什么事情没有做,处理器时钟就不会滴答计时,或者计时量很小。

print(time.ctime(), ': {:0.3f} {:0.3f}'.format(time.time(), time.clock()))
for i in range(4):
    print('Sleeping')
    print(time.ctime(), ': {:0.3f} {:0.3f}'.format(time.time(), time.clock()))
Mon Feb  3 21:17:42 2020 : 1580735862.112 516164.959
Sleeping
Mon Feb  3 21:17:42 2020 : 1580735862.113 516164.960
Sleeping
Mon Feb  3 21:17:42 2020 : 1580735862.113 516164.961
Sleeping
Mon Feb  3 21:17:42 2020 : 1580735862.114 516164.961
Sleeping
Mon Feb  3 21:17:42 2020 : 1580735862.114 516164.961

性能计数器

有一个高分辨率的单调时钟来衡量性能是非常重要的。要想确定最佳时钟数据源,需要一些特定于平台的知识,这些知识由 Python 中的 perf_counter() 提供。

monotonic() 函数一样, perf_counter() 函数的时元(epoch)是未定义的。并且,函数的这些返回值用于比较和计算,而不是用作绝对时间。

struct_time

按秒计数,把秒作为单位来存储时间在某些场合很适用,但有时候程序需要访问的(或者说程序感兴趣的)是日期的不同单位所对应的各个部分(比如,某年、某月等)。为了让日期的各个部分便于访问, time 模块定义了 struct_time ,它以各个部分分离的格式来存储日期和时间值。 有些函数以 struct_time 类型的数值作为参数,而不是浮点型的数值。

  1. gmtime() 函数返回当前 UTC1 时间(世界标准时间)。
  2. localtime() 函数返回的是与当前时区的当前时间 。

mktime() 接收一个 struct_time 型数值并将其转换为浮点型数值。

print(time.gmtime())
print("")
print(time.localtime())
time.struct_time(tm_year=2020, tm_mon=2, tm_mday=3, tm_hour=13,
tm_min=17, tm_sec=42, tm_wday=0, tm_yday=34, tm_isdst=0)

time.struct_time(tm_year=2020, tm_mon=2, tm_mday=3, tm_hour=21,
tm_min=17, tm_sec=42, tm_wday=0, tm_yday=34, tm_isdst=0)

与时区相关的工作

确定当前时间的函数依赖于时区设置,时区可以由程序设置,也可以使用系统的默认时区设置。改变时区并不会改变实际的时间,只是改变它的表现方式。time模块有4个属性记录了时区的信息:

import os

def show_zone_info():
    print('  TZ    :', os.environ.get('TZ', '(not set)'))
    print('  tzname:', str(time.tzname).encode('latin-1').decode('gbk'))
    print('  Zone  : {} ({})'.format(time.timezone, (time.timezone / 3600)))
    print('  DST   :', time.daylight)
    print('  ALZone  : {} ({})'.format(time.altzone, (time.altzone / 3600)))
    print('  Time  :', time.ctime())
    print()

show_zone_info()
  TZ    : (not set)
  tzname: ('中国标准时间', '中国夏令时')
  Zone  : -28800 (-8.0)
  DST   : 0
  ALZone  : -32400 (-9.0)
  Time  : Mon Feb  3 21:17:42 2020

解析和格式化时间

有两个函数 —— strptime()strftime() —— 可以使时间值在 struct_time 表示和字符串表示之间相互转换。有一个很长的格式说明列表可以用来支持不同格式的输入和输出。完整的列表记录在 time 模块的库文件中。

这个例子将当前时间从字符串格式转换成 struct_time 实例,然后又将其转换为字符串格式。

struct_time → 浮点数时间(秒)

struct_time = time.localtime()
print(struct_time)

seconds = time.mktime(struct_time)
print(seconds)
time.struct_time(tm_year=2020, tm_mon=2, tm_mday=3, tm_hour=21,
tm_min=17, tm_sec=42, tm_wday=0, tm_yday=34, tm_isdst=0)
1580735862.0

浮点数时间 → struct_time

浮点数时间 → 字符串时间

print(time.time())
time.ctime(time.time())
1580735862.2027004
'Mon Feb  3 21:17:42 2020'

字符串时间 → struct_time

print(time.ctime(time.time()))
time.strptime(time.ctime(time.time()))
Mon Feb  3 21:17:42 2020
time.struct_time(tm_year=2020, tm_mon=2, tm_mday=3, tm_hour=21,
tm_min=17, tm_sec=42, tm_wday=0, tm_yday=34, tm_isdst=-1)

struct_time → 字符串时间

print(time.localtime())
time.strftime("%a %b %d %H:%M:%S %Y", time.localtime())
time.struct_time(tm_year=2020, tm_mon=2, tm_mday=3, tm_hour=21,
tm_min=17, tm_sec=42, tm_wday=0, tm_yday=34, tm_isdst=0)
'Mon Feb 03 21:17:42 2020'
time.asctime(time.localtime())
'Mon Feb  3 21:17:42 2020'

  1. 0时区的时间是UTC时间,UTC时间和格林威治标准时间(GMT)很接近,大多数情况下两者相互替代不会出问题,但严格来说UTC和GMT并不相等。