[Python-checkins] peps: PEP 418: Cameron Simpson rewrote the text of his alternative
victor.stinner
python-checkins at python.org
Sun Apr 15 11:46:28 CEST 2012
http://hg.python.org/peps/rev/b792150ba6b2
changeset: 4250:b792150ba6b2
user: Victor Stinner <victor.stinner at gmail.com>
date: Sun Apr 15 11:46:01 2012 +0200
summary:
PEP 418: Cameron Simpson rewrote the text of his alternative
files:
pep-0418.txt | 57 +++-
pep-0418/clockutils.py | 408 +++++++++++++++++++++++++++++
2 files changed, 457 insertions(+), 8 deletions(-)
diff --git a/pep-0418.txt b/pep-0418.txt
--- a/pep-0418.txt
+++ b/pep-0418.txt
@@ -2,7 +2,7 @@
Title: Add monotonic time, performance counter and process time functions
Version: $Revision$
Last-Modified: $Date$
-Author: Jim Jewett <jimjjewett at gmail.com>, Victor Stinner <victor.stinner at gmail.com>
+Author: Cameron Simpson <cs at zip.com.au>, Jim Jewett <jimjjewett at gmail.com>, Victor Stinner <victor.stinner at gmail.com>
Status: Draft
Type: Standards Track
Content-Type: text/x-rst
@@ -432,20 +432,45 @@
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 @@
* 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)
-------------------------------------------------------
diff --git a/pep-0418/clockutils.py b/pep-0418/clockutils.py
new file mode 100644
--- /dev/null
+++ b/pep-0418/clockutils.py
@@ -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 at 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__)
--
Repository URL: http://hg.python.org/peps
More information about the Python-checkins
mailing list