[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