PEP 418: Cameron Simpson rewrote the text of his alternative
This commit is contained in:
parent
b6f2709957
commit
51b61fa439
58
pep-0418.txt
58
pep-0418.txt
|
@ -2,7 +2,7 @@ PEP: 418
|
|||
Title: Add monotonic time, performance counter and process time functions
|
||||
Version: $Revision$
|
||||
Last-Modified: $Date$
|
||||
Author: Jim Jewett <jimjjewett@gmail.com>, Victor Stinner <victor.stinner@gmail.com>
|
||||
Author: Cameron Simpson <cs@zip.com.au>, Jim Jewett <jimjjewett@gmail.com>, Victor Stinner <victor.stinner@gmail.com>
|
||||
Status: Draft
|
||||
Type: Standards Track
|
||||
Content-Type: text/x-rst
|
||||
|
@ -432,20 +432,45 @@ Issues:
|
|||
else?
|
||||
|
||||
|
||||
One function choosing the clock from a list of constraints
|
||||
----------------------------------------------------------
|
||||
Choosing the clock from a list of constraints
|
||||
---------------------------------------------
|
||||
|
||||
``time.get_clock(*flags)`` with the following flags:
|
||||
The PEP as proposed offers a few new clocks, but their guarentees
|
||||
are deliberately loose in order to offer useful clocks on different
|
||||
platforms. This inherently embeds policy in the calls, and the
|
||||
caller must thus choose a policy.
|
||||
|
||||
The "choose a clock" approach suggests an additional API to let
|
||||
callers implement their own policy if necessary
|
||||
by making most platform clocks available and letting the caller pick amongst them.
|
||||
The PEP's suggested clocks are still expected to be available for the common
|
||||
simple use cases.
|
||||
|
||||
To do this two facilities are needed:
|
||||
an enumeration of clocks, and metadata on the clocks to enable the user to
|
||||
evaluate their suitability.
|
||||
|
||||
The primary interface is a function make simple choices easy:
|
||||
the caller can use ``time.get_clock(*flags)`` with some combination of flags.
|
||||
This include at least:
|
||||
|
||||
* time.MONOTONIC: clock cannot go backward
|
||||
* time.STEADY: clock rate is steady and the clock is not adjusted
|
||||
* time.STEADY: clock rate is steady
|
||||
* time.ADJUSTED: clock may be adjusted, for example by NTP
|
||||
* time.HIGHRES: clock with the highest precision
|
||||
|
||||
time.get_clock() returns None if the clock is found and so calls can
|
||||
be chained using the or operator. Example::
|
||||
It returns a clock object with a .now() method returning the current time.
|
||||
The clock object is annotated with metadata describing the clock feature set;
|
||||
its .flags field will contain at least all the requested flags.
|
||||
|
||||
get_time = time.get_clock(time.MONOTONIC) or time.get_clock(time.STEADY) or time.time()
|
||||
t = get_time()
|
||||
time.get_clock() returns None if no matching clock is found and so calls can
|
||||
be chained using the or operator. Example of a simple policy decision::
|
||||
|
||||
T = get_clock(MONOTONIC) or get_clock(STEADY) or get_clock()
|
||||
t = T.now()
|
||||
|
||||
The available clocks always at least include a wrapper for ``time.time()``,
|
||||
so a final call with no flags can always be used to obtain a working clock.
|
||||
|
||||
Example of flags of system clocks:
|
||||
|
||||
|
@ -455,6 +480,21 @@ Example of flags of system clocks:
|
|||
* CLOCK_MONOTONIC_RAW: MONOTONIC | STEADY
|
||||
* gettimeofday(): (no flag)
|
||||
|
||||
The clock objects contain other metadata including the clock flags
|
||||
with additional feature flags above those listed above, the name
|
||||
of the underlying OS facility, and clock precisions.
|
||||
|
||||
``time.get_clock()`` still chooses a single clock; an enumeration
|
||||
facility is also required.
|
||||
The most obvious method is to offer ``time.get_clocks()`` with the
|
||||
same signature as ``time.get_clock()``, but returning a sequence
|
||||
of all clocks matching the requested flags.
|
||||
Requesting no flags would thus enumerate all available clocks,
|
||||
allowing the caller to make an arbitrary choice amongst them based
|
||||
on their metadata.
|
||||
|
||||
Example partial implementation:
|
||||
`clockutils.py <http://hg.python.org/peps/file/tip/pep-0418/clockutils.py>`_.
|
||||
|
||||
One function with a flag: time.monotonic(fallback=True)
|
||||
-------------------------------------------------------
|
||||
|
|
|
@ -0,0 +1,408 @@
|
|||
#!/usr/bin/python
|
||||
#
|
||||
# Framework to present system clocks by feature, intended to avoid
|
||||
# the library-as-policy pitfalls of the discussion around PEP 418.
|
||||
#
|
||||
# My 2c:
|
||||
# http://www.gossamer-threads.com/lists/python/dev/977474#977474
|
||||
# http://www.gossamer-threads.com/lists/python/dev/977495#977495
|
||||
# or:
|
||||
# http://www.mail-archive.com/python-dev@python.org/msg66174.html
|
||||
# http://www.mail-archive.com/python-dev@python.org/msg66179.html
|
||||
# - Cameron Simpson <cs@zip.com.au> 02apr2012
|
||||
#
|
||||
|
||||
from collections import namedtuple
|
||||
from time import time
|
||||
import os
|
||||
|
||||
# the module exposing OS clock features
|
||||
_time = os
|
||||
|
||||
HIGHRES = 0x01 # high resolution
|
||||
MONOTONIC = 0x02 # never goes backwards
|
||||
STEADY = 0x04 # never steps; implies MONOTONIC
|
||||
ADJUSTED = 0x08 # may be adjusted, for example by NTP
|
||||
WALLCLOCK = 0x10 # tracks real world time, will usually be ADJUSTED too
|
||||
RUNTIME = 0x20 # track system run time - stops when system suspended
|
||||
SYNTHETIC = 0x40 # a synthetic clock, computed from other clocks
|
||||
|
||||
def get_clock(flags=0, clocklist=None):
|
||||
''' Return a clock based on the supplied `flags`.
|
||||
The returned clock shall have all the requested flags.
|
||||
If no clock matches, return None.
|
||||
'''
|
||||
for clock in get_clocks(flags=flags, clocklist=clocklist):
|
||||
return clock
|
||||
return None
|
||||
|
||||
def get_clocks(flags=0, clocklist=None):
|
||||
''' Yield all clocks matching the supplied `flags`.
|
||||
The returned clocks shall have all the requested flags.
|
||||
'''
|
||||
if clocklist is None:
|
||||
clocklist = ALL_CLOCKS
|
||||
for clock in clocklist:
|
||||
if clock.flags & flags == flags:
|
||||
yield clock.factory()
|
||||
|
||||
def monotonic_clock(other_flags=0):
|
||||
''' Return a monotonic clock, preferably high resolution.
|
||||
'''
|
||||
return get_clock(MONOTONIC|HIGHRES|other_flags, MONOTONIC_CLOCKS) \
|
||||
or get_clock(MONOTONIC|other_flags, MONOTONIC_CLOCKS)
|
||||
|
||||
def steady_clock(other_flags=0):
|
||||
''' Return a steady clock, preferably high resolution.
|
||||
'''
|
||||
return get_clock(STEADY|HIGHRES|other_flags, STEADY_CLOCKS) \
|
||||
or get_clock(STEADY|other_flags, STEADY_CLOCKS)
|
||||
|
||||
def highres_clock(other_flags=0):
|
||||
''' Return a high resolution clock, preferably steady.
|
||||
'''
|
||||
return get_clock(HIGHRES|STEADY|other_flags, HIGHRES_CLOCKS) \
|
||||
or get_clock(HIGHRES|other_flags, HIGHRES_CLOCKS)
|
||||
|
||||
_global_monotonic = None
|
||||
|
||||
def monotonic():
|
||||
''' Return the current time according to the default monotonic clock.
|
||||
'''
|
||||
global _global_monotonic
|
||||
if _global_monotonic is None:
|
||||
_global_monotonic = monotonic_clock()
|
||||
if _global_monotonic is None:
|
||||
raise RunTimeError("no monotonic clock available")
|
||||
return _global_monotonic.now()
|
||||
|
||||
_global_hires = None
|
||||
|
||||
def highres():
|
||||
''' Return the current time according to the default high resolution clock.
|
||||
'''
|
||||
global _global_hires
|
||||
if _global_hires is None:
|
||||
_global_hires = highres()
|
||||
if _global_hires is None:
|
||||
raise RunTimeError("no highres clock available")
|
||||
return _global_hires.now()
|
||||
|
||||
_global_steady = None
|
||||
|
||||
def steady():
|
||||
''' Return the current time according to the default steady clock.
|
||||
'''
|
||||
global _global_steady
|
||||
if _global_steady is None:
|
||||
_global_steady = steady()
|
||||
if _global_steady is None:
|
||||
raise RunTimeError("no steady clock available")
|
||||
return _global_steady.now()
|
||||
|
||||
class _Clock_Flags(int):
|
||||
''' An int with human friendly str() and repr() for clock flags.
|
||||
'''
|
||||
|
||||
_flag_names = (
|
||||
'HIGHRES',
|
||||
'MONOTONIC',
|
||||
'STEADY',
|
||||
'ADJUSTED',
|
||||
'WALLCLOCK',
|
||||
'RUNTIME',
|
||||
'SYNTHETIC',
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
f = self
|
||||
G = globals()
|
||||
names = []
|
||||
for name in _Clock_Flags._flag_names:
|
||||
n = G[name]
|
||||
if f & n:
|
||||
names.append(name)
|
||||
f &= ~n
|
||||
if f:
|
||||
names.append('0x%02x' % f)
|
||||
return '|'.join(names) if names else '0'
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %02x %s>' % (self.__class__.__name__, self, self)
|
||||
|
||||
# now assemble the list of platform clocks
|
||||
|
||||
class _Clock(object):
|
||||
''' A _Clock is the private base class of clock objects.
|
||||
A clock has the following mandatory attributes:
|
||||
.flags Feature flags describing the clock.
|
||||
A clock may have the following optional attributes:
|
||||
.epoch If present, the offset from time.time()'s epoch
|
||||
of this clock's epoch(). Not all clocks have epochs; some
|
||||
measure elapsed time from an unknown point and only the
|
||||
difference in two measurements is useful.
|
||||
.resolution
|
||||
The resolution of the underlying clock facility's
|
||||
reporting units. The clock can never be more precise than
|
||||
this value. The actual accuracy of the reported time may
|
||||
vary with adjustments and the real accuracy of the
|
||||
underlying OS clock facility (which in turn may be
|
||||
dependent on the precision of some hardware clock).
|
||||
A clock must also supply the following methods:
|
||||
.now() Report the current time in seconds, a float.
|
||||
'''
|
||||
def __init__(self):
|
||||
''' Set instance attributes from class attributes, suitable to
|
||||
singleton clocks.
|
||||
'''
|
||||
klass = self.__class__
|
||||
self.flags = _Clock_Flags(klass.flags)
|
||||
for attr in 'epoch', 'resolution':
|
||||
try:
|
||||
attrval = getattr(klass, attr)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
setattr(self, attr, attrval)
|
||||
|
||||
def __repr__(self):
|
||||
props = [self.__class__.__name__]
|
||||
for attr in sorted( [ attr for attr in dir(self)
|
||||
if attr
|
||||
and attr[0].isalpha()
|
||||
and attr not in ('now',)] ):
|
||||
props.append("%s=%s" % (attr, getattr(self, attr)))
|
||||
return "<" + " ".join(props) + ">"
|
||||
|
||||
ClockEntry = namedtuple('ClockEntry', 'flags factory')
|
||||
|
||||
ALL_CLOCKS = []
|
||||
|
||||
def _SingletonClockEntry( klass ):
|
||||
''' Construct a ClockEntry for a Singleton class, typical for system clocks.
|
||||
'''
|
||||
klock = klass()
|
||||
return ClockEntry( klass.flags, lambda: klock )
|
||||
|
||||
# always provide good old time.time()
|
||||
# provide no flags - this is a fallback - totally implementation agnostic
|
||||
class _TimeDotTimeClock(_Clock):
|
||||
''' A clock made from time.time().
|
||||
'''
|
||||
flags = 0
|
||||
epoch = 0
|
||||
def now(self):
|
||||
return time()
|
||||
ALL_CLOCKS.append( _SingletonClockEntry(_TimeDotTimeClock) )
|
||||
|
||||
# load system specific clocks here
|
||||
# in notional order of preference
|
||||
|
||||
if os.name == "nt":
|
||||
|
||||
class _NT_GetSystemTimeAsFileTimeClock(_Clock):
|
||||
''' A clock made from GetSystemTimeAsFileTime().
|
||||
'''
|
||||
flags = WALLCLOCK
|
||||
epoch = EPOCH_16010101T000000 # 01jan1601
|
||||
# a negative value wrt 01jan1970
|
||||
resolution = 0.0000001 # 100 nanosecond units
|
||||
# accuracy HW dependent?
|
||||
def now(self):
|
||||
# convert 100-nanosecond intervals since 1601 to UNIX style seconds
|
||||
return ( _time._GetSystemTimeAsFileTime() / 10000000
|
||||
+ NT_GetSystemTimeAsFileTimeClock.epoch
|
||||
)
|
||||
ALL_CLOCKS.append( _SingletonClockEntry(_NT_GetSystemTimeAsFileTimeClock) )
|
||||
|
||||
class _NT_GetTickCount64(_Clock):
|
||||
''' Based on
|
||||
http://msdn.microsoft.com/en-us/library/windows/desktop/ms724411%28v=vs.85%29.aspx
|
||||
Note this this specificly disavows high resolution.
|
||||
'''
|
||||
flags = RUNTIME|MONOTONIC
|
||||
resolution = 0.001
|
||||
def now(self):
|
||||
msecs = _time.GetTickCount64()
|
||||
return msecs / 1000
|
||||
ALL_CLOCKS.append( _SingletonClockEntry(_NT_GetTickCount64) )
|
||||
|
||||
else:
|
||||
|
||||
# presuming clock_gettime() and clock_getres() exposed in the os
|
||||
# module, along with the clock id names
|
||||
if hasattr(_time, "clock_gettime"):
|
||||
|
||||
try:
|
||||
clk_id = _time.CLOCK_REALTIME
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
timespec = _time.clock_getres(clk_id)
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
class _UNIX_CLOCK_REALTIME(_Clock):
|
||||
''' A clock made from clock_gettime(CLOCK_REALTIME).
|
||||
'''
|
||||
epoch = 0
|
||||
flags = WALLCLOCK
|
||||
resolution = timespec.tv_sec + timespec.tv_nsec / 1000000000
|
||||
def now():
|
||||
timespec = _time.clock_gettime(_time.CLOCK_REALTIME)
|
||||
return timespec.tv_sec + timespec.tv_nsec / 1000000000
|
||||
ALL_CLOCKS.append( _SingletonClockEntry(_UNIX_CLOCK_REALTIME) )
|
||||
|
||||
try:
|
||||
clk_id = _time.CLOCK_MONOTONIC
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
timespec = _time.clock_getres(clk_id)
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
class _UNIX_CLOCK_MONOTONIC(_Clock):
|
||||
''' A clock made from clock_gettime(CLOCK_MONOTONIC).
|
||||
'''
|
||||
flags = MONOTONIC|STEADY|ADJUSTED
|
||||
resolution = timespec.tv_sec + timespec.tv_nsec / 1000000000
|
||||
def now():
|
||||
timespec = _time.clock_gettime(_time.CLOCK_MONOTONIC)
|
||||
return timespec.tv_sec + timespec.tv_nsec / 1000000000
|
||||
ALL_CLOCKS.append( _SingletonClockEntry(_UNIX_CLOCK_MONOTONIC) )
|
||||
|
||||
try:
|
||||
clk_id = _time.CLOCK_MONOTONIC_RAW
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
timespec = _time.clock_getres(clk_id)
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
class _UNIX_CLOCK_MONOTONIC_RAW(_Clock):
|
||||
''' A clock made from clock_gettime(CLOCK_MONOTONIC_RAW).
|
||||
'''
|
||||
flags = MONOTONIC|STEADY
|
||||
resolution = timespec.tv_sec + timespec.tv_nsec / 1000000000
|
||||
def now():
|
||||
timespec = _time.clock_gettime(_time.CLOCK_MONOTONIC_RAW)
|
||||
return timespec.tv_sec + timespec.tv_nsec / 1000000000
|
||||
ALL_CLOCKS.append( _SingletonClockEntry(_UNIX_CLOCK_MONOTONIC_RAW) )
|
||||
|
||||
try:
|
||||
clk_id = _time.CLOCK_PROCESS_CPUTIME_ID
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
timespec = _time.clock_getres(clk_id)
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
class _UNIX_CLOCK_PROCESS_CPUTIME_ID(_Clock):
|
||||
''' A clock made from clock_gettime(CLOCK_PROCESS_CPUTIME_ID).
|
||||
'''
|
||||
flags = MONOTONIC
|
||||
resolution = timespec.tv_sec + timespec.tv_nsec / 1000000000
|
||||
def now():
|
||||
timespec = _time.clock_gettime(_time.CLOCK_PROCESS_CPUTIME_ID)
|
||||
return timespec.tv_sec + timespec.tv_nsec / 1000000000
|
||||
ALL_CLOCKS.append( _SingletonClockEntry(_CLOCK_PROCESS_CPUTIME_ID) )
|
||||
|
||||
try:
|
||||
clk_id = _time.CLOCK_THREAD_CPUTIME_ID
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
timespec = _time.clock_getres(clk_id)
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
class _UNIX_CLOCK_THREAD_CPUTIME_ID(_Clock):
|
||||
''' A clock made from clock_gettime(CLOCK_THREAD_CPUTIME_ID).
|
||||
'''
|
||||
flags = MONOTONIC
|
||||
resolution = timespec.tv_sec + timespec.tv_nsec / 1000000000
|
||||
def now():
|
||||
timespec = _time.clock_gettime(_time.CLOCK_THREAD_CPUTIME_ID)
|
||||
return timespec.tv_sec + timespec.tv_nsec / 1000000000
|
||||
ALL_CLOCKS.append( _SingletonClockEntry(_CLOCK_CLOCK_THREAD_CPUTIME_ID) )
|
||||
|
||||
if hasattr(_time, "gettimeofday"):
|
||||
class _UNIX_gettimeofday(_Clock):
|
||||
''' A clock made from gettimeofday().
|
||||
'''
|
||||
epoch = 0
|
||||
flags = WALLCLOCK
|
||||
resolution = 0.000001
|
||||
def now(self):
|
||||
timeval = _time.gettimeofday()
|
||||
return timeval.tv_sec + timeval.tv_usec / 1000000
|
||||
ALL_CLOCKS.append( _SingletonClockEntry(_UNIX_gettimeofday) )
|
||||
|
||||
if hasattr(_time, "ftime"):
|
||||
class _UNIX_ftime(_Clock):
|
||||
''' A clock made from ftime().
|
||||
'''
|
||||
epoch = 0
|
||||
flags = WALLCLOCK|ADJUSTED
|
||||
resolution = 0.001
|
||||
def now(self):
|
||||
timeb = _time.ftime()
|
||||
return timeb.time + timeb.millitm / 1000
|
||||
ALL_CLOCKS.append( _SingletonClockEntry(_UNIX_ftime) )
|
||||
|
||||
# an example synthetic clock, coming after time.time()
|
||||
# because I think synthetic clocks should be less desired
|
||||
# - they tend to have side effects; but perhaps offered anyway because
|
||||
# they can offer flag combinations not always presented by the system
|
||||
# clocks
|
||||
|
||||
# a simple synthetic monotonic clock
|
||||
# may skew with respect to other instances
|
||||
# Steven D'Aprano wrote a better one
|
||||
class SyntheticMonotonic(_Clock):
|
||||
flags = SYNTHETIC|MONOTONIC
|
||||
def __init__(self, base_clock=None):
|
||||
_Clock.__init__(self)
|
||||
if base_clock is None:
|
||||
base_clock = _TimeDotTimeClock()
|
||||
self.base_clock = base_clock
|
||||
for attr in 'epoch', 'resolution':
|
||||
try:
|
||||
attrval = getattr(base_clock, attr)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
setattr(self, attr, attrval)
|
||||
self.__last = None
|
||||
self.__base = base_clock
|
||||
def now(self):
|
||||
last = self.__last
|
||||
t = self.__base.now()
|
||||
if last is None or last < t:
|
||||
self.__last = t
|
||||
else:
|
||||
t = last
|
||||
return t
|
||||
|
||||
ALL_CLOCKS.append( ClockEntry(SyntheticMonotonic.flags, SyntheticMonotonic) )
|
||||
|
||||
# With more clocks, these will be ALL_CLOCKS listed in order of preference
|
||||
# for these types i.e. MONOTONIC_CLOCKS will list only monotonic clocks
|
||||
# in order of quality (an arbitrary measure, perhaps).
|
||||
MONOTONIC_CLOCKS = ALL_CLOCKS
|
||||
HIGHRES_CLOCKS = ALL_CLOCKS
|
||||
STEADY_CLOCKS = ALL_CLOCKS
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("ALL_CLOCKS =", repr(ALL_CLOCKS))
|
||||
for clock in get_clocks():
|
||||
print("clock = %r" % (clock,))
|
||||
print(clock.__class__.__doc__)
|
Loading…
Reference in New Issue