Package: py-code-timing
py_code_timingThis package provides a comprehensive range of Python code timing utilities.Features and usageCodeTiming,startandelapsedThe classCodeTimingprovides the basic timing functionality, operating rather like a stopwatch.  The basic pattern of use goes like this:tobj = CodeTiming()
    ...
    tobj.start()
    ...
    cur_time = tobj.elapsed()
    ...
    tobj.start()
    ...
    cur_time = tobj.elapsed()
    ...The call tostartbegins the timing process.   The code betweenstartandelapsedis timed, with the measured duration returned from theelapsedcall. Timing stops with the call toelapsed, and only resumes whenstartis called again, etc.sampleAdditionally, it can be useful to capture intermediate timings in betweenstartandelapsed.  This can be done using a call tosample, as in this example:tobj = CodeTiming()
    ...
    tobj.start()
    ...
    cur_time1 = tobj.sample()
    ...
    cur_time2 = tobj.sample()
    ...
    cur_time = tobj.elapsed()As forelapsed, thesamplecall returns the duration since thestart.   However, unlikeelapsed, usingsampledoesn't stop timing.Named timing objects for gathering statisticsMore complex scenarios, such as loops and complex control flows may easily require timing statistics to be gathered.   This can be done by usingnamed timing objects, as in the following example.tobj = CodeTiming('MyCode')
    ...
    for i in range(...):
        ...
        tobj.start()
        ...
        cur_time = tobj.elapsed()
        ...Statistics are gathered against the name "MyCode" so that each time the code betweenstartandelapsedis executed, the measured duration will then contribute to the statistics for the named object "MyCode".  Thesamplemethod can be used to sample the timing (as shown earlier), butwithoutcontributing to the timing statistics.What aboutnestedtiming?Withnesteduse of a timing objecttobj, the pattern of use would look something like this:tobj = CodeTiming("MyTimer")
    ...
    tobj.start()      # start - outermost use of tobj
    ...
       ...
       tobj.start()      # start -- nested use of tobj
       ...
       tobj.elapsed()    # elapsed -- nested use of tobj
       ...
       
    ...
    tobj.elapsed()    # elapsed - outermost use of tobj
    ...Any inner nested uses of the timing objecttobjare included within the scope of the outermost timing period.   Therefore, to avoid "double counting",the inner nested uses oftobjare ignored and do not contribute to the statistics fortobj.Getting statistics info (1) --get_statsTo access the current statistics for a timing object, use the functionget_statsto obtain a dictionary containing the statistics info:stats_map = tobj.get_stats()This returns a dictionary with the keys:mean,std-dev,max,min,eventsandtotal, as in this example:{'mean': 2504, 'std_dev': 2, 'max': 2507, 'min': 2501, 'events': 5, 'total': 12519}where the entries are:mean-- Average of timingsstd_dev-- Standard deviation of timingsmax-- Maximum timingmin-- Minimum timingevents-- Number of timing events measuredtotal-- Total time measuredThe timings above are given in milliseconds (default).  See theQuick Referencebelow for how to change thetiming unit(seconds or milliseconds).Getting all statistics info (2) --get_stats_by_nameTheclassmethodget_stats_by_nametakes a given name (e.g. "MyCode") and returns a dictionary containing the stats info for that named timing object.   This method fails if there is no timing object having the given name.my_code_stats = CodeTiming.get_stats_by_name("MyCode")Getting all statistics info (3) --get_all_statsFinally, theclassmethodget_all_statsprovides a dictionary of named timing objects:timing_map = CodeTiming.get_all_stats()The resulting dictionary only contains entries for named timing objects having timing data -- timing objects with no timing data are omitted.Conveniencedisplayutility functionsA number ofdisplayfunctions are provided to give a convenient textual rendering of the stats info as specified above. The display functions are:display_stats-- This function accesses the stats for a particular timing object and renders the information textually.For example, if the stats info were:{'mean': 747, 'std_dev': 144, 'max': 974, 'min': 503, 'events': 30, 'total': 22409}then the display would be:mean: 747ms, std_dev: (+ or -) 144ms, max: 974ms, min: 503ms, events: 30, total: 22s 409msdisplay_stats_by_name-- Thisclassmethodfunction accesses a named timing object specified by given name and displays the stats info textually.   Fails if there is no named timing object having the given name.display_all_stats-- Thisclassmethodfunction extracts the stats info for named timing objects and displays the information in a table-like form.  As forget_all_statsonly those named timimg objects having timing data are displayed -- timed objects having no timing data are omitted.These functions broadly correspond to the various functions used for getting statistics described earlier.Display keyword optionsEach of thedisplayfunctions outlined above use the same set ofkeywordarguments to specify display options.hms-- If True, format timing info in hrs-mins-secs-millisecs format (default: True).   Otherwise, format timings numerically in chosen units.shape-- Layout eitherhorizontallyorvertically(default: horizontal).skip-- Number of blank lines following (default: 0).Timing function calls -- Using the@timingdecoratorThe@timingdecorator provides a way to time individual Python function codes.   Given a function definition, the@timingdecorator adds anamed timing object(with the same name as the function) to gather timing statistics of calls for that function.@timing
    def my_function(..., ...):
        ...
        return result  
    ...
    ... my_function( ... , ...) ...
    ...All (outermost) calls of timed functions likemy_functioncontribute to their individual statistics. Because functions could berecursivelydefined (i.e. reentrant), timing of any inner calls is naturally included within the outermost call and therefore shouldnotbe measured separately. The@timingdecorator approach dynamically tracks inner calls and avoids the inaccuracy due to the potential for "double-counting" these calls.Since timed functions use named timed objects, the statistical timing info for those functions can be retrieved using classmethodsget_stats_by_nameorget_all_statsmentioned earlier.Using timing objects in with-statementsNamed timing objects can be used in Python'swith-statements.  Thewith-statementprovides a specific scope within which timings areimplicitlytaken of the code that is running.  The timing of that code doesn't need a timing object directly.  In particular, such timing objects must be named so that statistics can be retrieved, by using classmethods such asget_stats_by_nameordisplay_stats_by_namementioned earlier.with CodeTiming("TestCode"):
        ...
        # code in scope being timed
        ...
        ...
   
    ...
    print(CodeTiming.get_stats_by_name("TestCode"))
    print(CodeTiming.display_stats_by_name("TestCode"))
    ...Note:Fails if the timing object isanonymous(i.e. unnamed).   This is because it would be impossible to access anonymous timing objectsUsingautostartTiming objects can also be used inautostartmode.  This is where timing begins when the object is created and then automatically restarts following calls toelapsed.   Thestartmethod can still be called and simplyrestartsthe timing.   Thesamplemethod works as before and doesn't affect the statistics gathered.The pattern of use withautostartgoes as follows:tobj = CodeTiming(autostart=True)
    ...
    cur_time = tobj.elapsed()  # first timing
    ...
    cur_time = tobj.elapsed()  # second timing
    ...Note that, inautostartmode, thestartmethod is not necessary to begin timing -- that happens automatically when the timing object entersautostartmode.Quick ReferenceThis section gives a quick summary of the utilities provided.CodeTimingClass providing utilities for timing Python code.class CodeTiming

__init__ (self, name="", timeunit=MILLISECS_TIMEUNIT, timetype=TOTAL_TIMETYPE, autostart=False)name-- Optional name of timing object (default : "").   Unnamed (oranonymous) timing objects areindependentof each other.  There may be several instances of a timing object, named or unnamed.  However, the statistics for all instances of a named timing object with the same name will be aggregated together.timeunit-- Unit of time measurement - either seconds or milliseconds (default: milliseconds).timetype-- Type of time measurement (default : total).Types available are:
+total-- Total system time
+process-- Process time
+thread-- Thread timeautostart-- If True, timing object is inautostartmode (default: False).When in autostart mode, timing starts immediately at creation of the timing object and continues indefinitely.Class ConstantsThese class constants give standard options (as strings) for various keyword options.  Strings are used instead of Enum's to simplify APIs.# Timetypes
	TOTAL_TIMETYPE =   "total timetype"
	PROCESS_TIMETYPE = "process timetype"
	THREAD_TIMETYPE =  "thread timetype"
	
	# Timeunits
	SECONDS_TIMEUNIT =   "seconds _timeunit"
	MILLISECS_TIMEUNIT = "millisecs _timeunit"
	
	# Display shape options
	DISPLAY_HORIZONTALLY = "horizontal"
	DISPLAY_VERTICALLY =   "vertical"Class ParametersThese class parameters affect the behaviour of timing objects in general.# If not None, this sets the timeunit globally
	GLOBAL_TIMEUNIT = None
	
	# If not None, this sets the timetype globally
	GLOBAL_TIMETYPE = None
	
    # Timeunit for function timing decorator.
    FUNCTION_TIMEUNIT = None 
    
    # Timetype for function timing decorator.
    FUNCTION_TIMETYPE = None 
	
	# Global enable
	GLOBAL_ENABLE = TrueTiming MethodsThese methods provide the timing capabilities for the timing object.startInitiates timing for timing object. In autostart mode, this restarts timing.def start(self)Note:to avoid misleading results, any nested uses of a timing object is detected and doesn't contribute to statistics -- only the outermost use of a timing object is recorded.elapsedMeasures duration since the current start, andstopstiming -- except in autostart mode, when instead timing continues. By usingelapsed, the durations it measures contribute to calculating timing statistics (for both named and anonymous timing objects).   Returns the duration measured (orNoneif timing not started or disabled).def elapsed(self)sampleReturns duration since the current start -- but doesn't contribute to the statistics for the timing object.  This could returnNoneif timing not started or disabled.def sample(self)Stats Access MethodsStatistics are gathered for all timing objects.   The methods listed hereget_statsProduces a dictionary object representing the statistical information gathered by the timing object.def get_stats(self)The fields aremean, std_dev, max, min, events, totaland defined as:mean-- Average of timingsstd_dev-- Standard deviation of timingsmax-- Maximum timingmin-- Minimum timingevents-- Number of timing events measuredtotal-- Total time measuredget_stats_by_nameThis classmethod accesses the named timing object as specified by given name and, if found, produces a dictionary object as described above.  Fails if no named timing object exists with given name.@classmethod
    def get_stats_by_name(_, name)get_all_statsThis classmethod produces a dictionary, keyed by name, of corresponding stats dictionary objects for named timing objects.  Entries are only given for each named timing object having timing data -- others not having any timing data are omitted.@classmethod
    def get_all_stats(_)Stats Display MethodsAs a convenience, a number of display methods are provided for rendering the statistics information for timing objects as strings.  These broadly correspond to the various stats access methods given earlier.display_statsRenders as a string the dictionary object representing the statistical information gathered by the timing object.def display_stats(self, hms=True, shape=DISPLAY_HORIZONTALLY, skip=0)The keyword arguments are common for all three of these display functions:hms-- If True, format timing info in a "h m s ms" structured format (default: True).   Otherwise, format timings numerically in chosen units.shape-- Layout eitherhorizontallyorvertically(default: horizontal).skip-- Number of blank lines following (default: 0)._Note:In vertical layout, the minimum skip value is set to 1 to ensure separation of entries.Note:HMS formatThis displays timing information in a structured, easily readable format.   For example:4s 23ms            (4023 msecs)
        2h 14m 31s 257ms   (8071257 msecs)display_stats_by_nameThis classmethod accesses the named timing object specified by given name and, if found, renders as a string the associated dictionary object as described above.  Fails if no named timing object exists with given name.@classmethod
    def display_stats_by_name(_, name, hms=True, shape=DISPLAY_HORIZONTALLY, skip=0)display_all_statsThis classmethod renders as a string the table, keyed by name, of corresponding stats dictionary objects for named timing objects.  Entries are only given for each named timing object having timing data -- others not having any timing data are omitted.@classmethod
    def display_all_stats(_, hms=True, shape=DISPLAY_HORIZONTALLY, skip=0)State Setter MethodsThese methods are used to configure timing objects.reset_allThis classmethod resets the state of all named timimg objects.  Any timing objects in autostart mode are restarted.@classmethod
    def reset_all(_):resetThis resets the state of a specific timing object.  Objects in autostart mode are restarted.def reset(self)set_autostartSets autostart mode and restarts timing (default : False).   Fails if timing has already begun.  This method returnsselfto enable chaining of setter methods.def set_autostart(self)set_enabledThis is used to enable or disable the timing object (default: enabled).  Disabling the object means that statistics are not gathered and that the methodsenabledandsamplereturnNone.  This method returnsselfto enable chaining of setter methods.def set_enabled(self, flag)set_nameThis associates a name with a currently anonymous timing object (i.e. with empty name).  This is coordinated so  that looking up the object by name (e.g.get_stats_by_name) and accessing it directly (e.g.get_stats) produces the same behaviour.  Fails if the object is already named.  This method returnsselfto enable chaining of setter methods.def set_name(self, new_name)Note:The names of named objects need not beuniquelydefined - that is, there may be several instances of a timed object each one having the same name.However, in all such cases, the resulting object are all alike.This means that the statistics gathered by each instance is aggregated with others of the same name.NoteChaining of methods allows for directly applying chaining methods one after the other:tobj = CodeTiming()
	...
	tobj.set_autostart().set_name("MyTimer")LICENSEMIT LicenseCopyright (c) 2023 Purple ElephantPermission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.