Как измерить время работы программы python

The simplest way in Python:

import time
start_time = time.time()
main()
print("--- %s seconds ---" % (time.time() - start_time))

This assumes that your program takes at least a tenth of second to run.

Prints:

--- 0.764891862869 seconds ---

Peter Mortensen's user avatar

answered Oct 13, 2009 at 0:00

rogeriopvl's user avatar

rogeriopvlrogeriopvl

50.6k8 gold badges54 silver badges58 bronze badges

10

In Linux or Unix:

$ time python yourprogram.py

In Windows, see this StackOverflow question: How do I measure execution time of a command on the Windows command line?

For more verbose output,

$ time -v python yourprogram.py
    Command being timed: "python3 yourprogram.py"
    User time (seconds): 0.08
    System time (seconds): 0.02
    Percent of CPU this job got: 98%
    Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.10
    Average shared text size (kbytes): 0
    Average unshared data size (kbytes): 0
    Average stack size (kbytes): 0
    Average total size (kbytes): 0
    Maximum resident set size (kbytes): 9480
    Average resident set size (kbytes): 0
    Major (requiring I/O) page faults: 0
    Minor (reclaiming a frame) page faults: 1114
    Voluntary context switches: 0
    Involuntary context switches: 22
    Swaps: 0
    File system inputs: 0
    File system outputs: 0
    Socket messages sent: 0
    Socket messages received: 0
    Signals delivered: 0
    Page size (bytes): 4096
    Exit status: 0

vipulgupta2048's user avatar

answered Oct 12, 2009 at 23:59

steveha's user avatar

stevehasteveha

74k20 gold badges90 silver badges116 bronze badges

4

I put this timing.py module into my own site-packages directory, and just insert import timing at the top of my module:

import atexit
from time import clock

def secondsToStr(t):
    return "%d:%02d:%02d.%03d" % 
        reduce(lambda ll,b : divmod(ll[0],b) + ll[1:],
            [(t*1000,),1000,60,60])

line = "="*40
def log(s, elapsed=None):
    print line
    print secondsToStr(clock()), '-', s
    if elapsed:
        print "Elapsed time:", elapsed
    print line
    print

def endlog():
    end = clock()
    elapsed = end-start
    log("End Program", secondsToStr(elapsed))

def now():
    return secondsToStr(clock())

start = clock()
atexit.register(endlog)
log("Start Program")

I can also call timing.log from within my program if there are significant stages within the program I want to show. But just including import timing will print the start and end times, and overall elapsed time. (Forgive my obscure secondsToStr function, it just formats a floating point number of seconds to hh:mm:ss.sss form.)

Note: A Python 3 version of the above code can be found here or here.

Community's user avatar

answered Oct 13, 2009 at 2:08

PaulMcG's user avatar

PaulMcGPaulMcG

61.6k16 gold badges92 silver badges129 bronze badges

7

I like the output the datetime module provides, where time delta objects show days, hours, minutes, etc. as necessary in a human-readable way.

For example:

from datetime import datetime
start_time = datetime.now()
# do your work here
end_time = datetime.now()
print('Duration: {}'.format(end_time - start_time))

Sample output e.g.

Duration: 0:00:08.309267

or

Duration: 1 day, 1:51:24.269711

As J.F. Sebastian mentioned, this approach might encounter some tricky cases with local time, so it’s safer to use:

import time
from datetime import timedelta
start_time = time.monotonic()
end_time = time.monotonic()
print(timedelta(seconds=end_time - start_time))

Peter Mortensen's user avatar

answered Sep 29, 2014 at 11:55

metakermit's user avatar

metakermitmetakermit

20.7k12 gold badges85 silver badges95 bronze badges

3

import time

start_time = time.clock()
main()
print(time.clock() - start_time, "seconds")

time.clock() returns the processor time, which allows us to calculate only the time used by this process (on Unix anyway). The documentation says «in any case, this is the function to use for benchmarking Python or timing algorithms»

Shidouuu's user avatar

Shidouuu

3413 silver badges14 bronze badges

answered Oct 13, 2009 at 1:25

newacct's user avatar

newacctnewacct

119k29 gold badges161 silver badges223 bronze badges

3

I really like Paul McGuire’s answer, but I use Python 3. So for those who are interested: here’s a modification of his answer that works with Python 3 on *nix (I imagine, under Windows, that clock() should be used instead of time()):

#python3
import atexit
from time import time, strftime, localtime
from datetime import timedelta

def secondsToStr(elapsed=None):
    if elapsed is None:
        return strftime("%Y-%m-%d %H:%M:%S", localtime())
    else:
        return str(timedelta(seconds=elapsed))

def log(s, elapsed=None):
    line = "="*40
    print(line)
    print(secondsToStr(), '-', s)
    if elapsed:
        print("Elapsed time:", elapsed)
    print(line)
    print()

def endlog():
    end = time()
    elapsed = end-start
    log("End Program", secondsToStr(elapsed))

start = time()
atexit.register(endlog)
log("Start Program")

If you find this useful, you should still up-vote his answer instead of this one, as he did most of the work ;).

Georgy's user avatar

Georgy

11.8k7 gold badges65 silver badges72 bronze badges

answered Sep 10, 2012 at 2:03

Nicojo's user avatar

NicojoNicojo

1,0138 silver badges8 bronze badges

7

You can use the Python profiler cProfile to measure CPU time and additionally how much time is spent inside each function and how many times each function is called. This is very useful if you want to improve performance of your script without knowing where to start. This answer to another Stack Overflow question is pretty good. It’s always good to have a look in the documentation too.

Here’s an example how to profile a script using cProfile from a command line:

$ python -m cProfile euler048.py

1007 function calls in 0.061 CPU seconds

Ordered by: standard name
ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    1    0.000    0.000    0.061    0.061 <string>:1(<module>)
 1000    0.051    0.000    0.051    0.000 euler048.py:2(<lambda>)
    1    0.005    0.005    0.061    0.061 euler048.py:2(<module>)
    1    0.000    0.000    0.061    0.061 {execfile}
    1    0.002    0.002    0.053    0.053 {map}
    1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler objects}
    1    0.000    0.000    0.000    0.000 {range}
    1    0.003    0.003    0.003    0.003 {sum}

Peter Mortensen's user avatar

answered Jan 2, 2014 at 0:35

jacwah's user avatar

jacwahjacwah

2,6472 gold badges21 silver badges42 bronze badges

2

Just use the timeit module. It works with both Python 2 and Python 3.

import timeit

start = timeit.default_timer()

# All the program statements
stop = timeit.default_timer()
execution_time = stop - start

print("Program Executed in "+str(execution_time)) # It returns time in seconds

It returns in seconds and you can have your execution time. It is simple, but you should write these in thew main function which starts program execution. If you want to get the execution time even when you get an error then take your parameter «Start» to it and calculate there like:

def sample_function(start,**kwargs):
     try:
         # Your statements
     except:
         # except statements run when your statements raise an exception
         stop = timeit.default_timer()
         execution_time = stop - start
         print("Program executed in " + str(execution_time))

djamaile's user avatar

djamaile

6752 gold badges12 silver badges30 bronze badges

answered Sep 18, 2017 at 19:08

Ravi Kumar's user avatar

Ravi KumarRavi Kumar

7527 silver badges8 bronze badges

1

time.clock()

Deprecated since version 3.3: The behavior of this function depends
on the platform: use perf_counter() or process_time() instead,
depending on your requirements, to have a well-defined behavior.

time.perf_counter()

Return the value (in fractional seconds) of a performance counter,
i.e. a clock with the highest available resolution to measure a short
duration. It does include time elapsed during sleep and is
system-wide.

time.process_time()

Return the value (in fractional seconds) of the sum of the system and
user CPU time of the current process. It does not include time elapsed
during sleep.

start = time.process_time()
... do something
elapsed = (time.process_time() - start)

answered May 18, 2016 at 3:49

Yas's user avatar

YasYas

4,7472 gold badges39 silver badges23 bronze badges

1

time.clock has been deprecated in Python 3.3 and will be removed from Python 3.8: use time.perf_counter or time.process_time instead

import time
start_time = time.perf_counter ()
for x in range(1, 100):
    print(x)
end_time = time.perf_counter ()
print(end_time - start_time, "seconds")

Suraj Rao's user avatar

Suraj Rao

29.3k11 gold badges96 silver badges103 bronze badges

answered Jun 20, 2021 at 9:11

Md. Imrul Kayes's user avatar

1

For the data folks using Jupyter Notebook

In a cell, you can use Jupyter’s %%time magic command to measure the execution time:

%%time
[ x**2 for x in range(10000)]

Output

CPU times: user 4.54 ms, sys: 0 ns, total: 4.54 ms
Wall time: 4.12 ms

This will only capture the execution time of a particular cell. If you’d like to capture the execution time of the whole notebook (i.e. program), you can create a new notebook in the same directory and in the new notebook execute all cells:

Suppose the notebook above is called example_notebook.ipynb. In a new notebook within the same directory:

# Convert your notebook to a .py script:
!jupyter nbconvert --to script example_notebook.ipynb

# Run the example_notebook with -t flag for time
%run -t example_notebook

Output

IPython CPU timings (estimated):
  User   :       0.00 s.
  System :       0.00 s.
Wall time:       0.00 s.

Peter Mortensen's user avatar

answered Jul 28, 2018 at 16:48

Matt's user avatar

MattMatt

5,5761 gold badge43 silver badges40 bronze badges

The following snippet prints elapsed time in a nice human readable <HH:MM:SS> format.

import time
from datetime import timedelta

start_time = time.time()

#
# Perform lots of computations.
#

elapsed_time_secs = time.time() - start_time

msg = "Execution took: %s secs (Wall clock time)" % timedelta(seconds=round(elapsed_time_secs))

print(msg)    

answered Jul 1, 2016 at 22:24

Sandeep's user avatar

SandeepSandeep

27.9k3 gold badges32 silver badges24 bronze badges

1

Similar to the response from @rogeriopvl I added a slight modification to convert to hour minute seconds using the same library for long running jobs.

import time
start_time = time.time()
main()
seconds = time.time() - start_time
print('Time Taken:', time.strftime("%H:%M:%S",time.gmtime(seconds)))

Sample Output

Time Taken: 00:00:08

answered Mar 12, 2020 at 5:27

user 923227's user avatar

user 923227user 923227

2,3984 gold badges25 silver badges46 bronze badges

1

For functions, I suggest using this simple decorator I created.

def timeit(method):
    def timed(*args, **kwargs):
        ts = time.time()
        result = method(*args, **kwargs)
        te = time.time()
        if 'log_time' in kwargs:
            name = kwargs.get('log_name', method.__name__.upper())
            kwargs['log_time'][name] = int((te - ts) * 1000)
        else:
            print('%r  %2.22f ms' % (method.__name__, (te - ts) * 1000))
        return result
    return timed

@timeit
def foo():
    do_some_work()

# foo()
# 'foo'  0.000953 ms

answered Oct 29, 2020 at 10:24

Nikita Tonkoskur's user avatar

Nikita TonkoskurNikita Tonkoskur

1,4231 gold badge15 silver badges28 bronze badges

3

from time import time
start_time = time()
...
end_time = time()
time_taken = end_time - start_time # time_taken is in seconds
hours, rest = divmod(time_taken,3600)
minutes, seconds = divmod(rest, 60)

The6thSense's user avatar

The6thSense

8,0437 gold badges31 silver badges64 bronze badges

answered Apr 6, 2016 at 7:45

Qina Yan's user avatar

Qina YanQina Yan

1,1969 silver badges5 bronze badges

I’ve looked at the timeit module, but it seems it’s only for small snippets of code. I want to time the whole program.

$ python -mtimeit -n1 -r1 -t -s "from your_module import main" "main()"

It runs your_module.main() function one time and print the elapsed time using time.time() function as a timer.

To emulate /usr/bin/time in Python see Python subprocess with /usr/bin/time: how to capture timing info but ignore all other output?.

To measure CPU time (e.g., don’t include time during time.sleep()) for each function, you could use profile module (cProfile on Python 2):

$ python3 -mprofile your_module.py

You could pass -p to timeit command above if you want to use the same timer as profile module uses.

See How can you profile a Python script?

answered Mar 3, 2015 at 9:04

jfs's user avatar

jfsjfs

393k189 gold badges969 silver badges1654 bronze badges

I was having the same problem in many places, so I created a convenience package horology. You can install it with pip install horology and then do it in the elegant way:

from horology import Timing

with Timing(name='Important calculations: '):
    prepare()
    do_your_stuff()
    finish_sth()

will output:

Important calculations: 12.43 ms

Or even simpler (if you have one function):

from horology import timed

@timed
def main():
    ...

will output:

main: 7.12 h

It takes care of units and rounding. It works with python 3.6 or newer.

answered Dec 7, 2019 at 22:05

hans's user avatar

hanshans

1,00412 silver badges32 bronze badges

4

I liked Paul McGuire’s answer too and came up with a context manager form which suited my needs more.

import datetime as dt
import timeit

class TimingManager(object):
    """Context Manager used with the statement 'with' to time some execution.

    Example:

    with TimingManager() as t:
       # Code to time
    """

    clock = timeit.default_timer

    def __enter__(self):
        """
        """
        self.start = self.clock()
        self.log('n=> Start Timing: {}')

        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        """
        self.endlog()

        return False

    def log(self, s, elapsed=None):
        """Log current time and elapsed time if present.
        :param s: Text to display, use '{}' to format the text with
            the current time.
        :param elapsed: Elapsed time to display. Dafault: None, no display.
        """
        print s.format(self._secondsToStr(self.clock()))

        if(elapsed is not None):
            print 'Elapsed time: {}n'.format(elapsed)

    def endlog(self):
        """Log time for the end of execution with elapsed time.
        """
        self.log('=> End Timing: {}', self.now())

    def now(self):
        """Return current elapsed time as hh:mm:ss string.
        :return: String.
        """
        return str(dt.timedelta(seconds = self.clock() - self.start))

    def _secondsToStr(self, sec):
        """Convert timestamp to h:mm:ss string.
        :param sec: Timestamp.
        """
        return str(dt.datetime.fromtimestamp(sec))

Peter Mortensen's user avatar

answered Jan 29, 2015 at 15:42

Gall's user avatar

GallGall

1,5651 gold badge14 silver badges21 bronze badges

In IPython, «timeit» any script:

def foo():
    %run bar.py
timeit foo()

Peter Mortensen's user avatar

answered May 20, 2015 at 14:40

B.Kocis's user avatar

B.KocisB.Kocis

1,92420 silver badges19 bronze badges

1

Use line_profiler.

line_profiler will profile the time individual lines of code take to execute. The profiler is implemented in C via Cython in order to reduce the overhead of profiling.

from line_profiler import LineProfiler
import random

def do_stuff(numbers):
    s = sum(numbers)
    l = [numbers[i]/43 for i in range(len(numbers))]
    m = ['hello'+str(numbers[i]) for i in range(len(numbers))]

numbers = [random.randint(1,100) for i in range(1000)]
lp = LineProfiler()
lp_wrapper = lp(do_stuff)
lp_wrapper(numbers)
lp.print_stats()

The results will be:

Timer unit: 1e-06 s

Total time: 0.000649 s
File: <ipython-input-2-2e060b054fea>
Function: do_stuff at line 4

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     4                                           def do_stuff(numbers):
     5         1           10     10.0      1.5      s = sum(numbers)
     6         1          186    186.0     28.7      l = [numbers[i]/43 for i in range(len(numbers))]
     7         1          453    453.0     69.8      m = ['hello'+str(numbers[i]) for i in range(len(numbers))]

Peter Mortensen's user avatar

answered Mar 28, 2018 at 5:43

Yu Jiaao's user avatar

Yu JiaaoYu Jiaao

4,4045 gold badges42 silver badges57 bronze badges

1

I used a very simple function to time a part of code execution:

import time
def timing():
    start_time = time.time()
    return lambda x: print("[{:.2f}s] {}".format(time.time() - start_time, x))

And to use it, just call it before the code to measure to retrieve function timing, and then call the function after the code with comments. The time will appear in front of the comments. For example:

t = timing()
train = pd.read_csv('train.csv',
                        dtype={
                            'id': str,
                            'vendor_id': str,
                            'pickup_datetime': str,
                            'dropoff_datetime': str,
                            'passenger_count': int,
                            'pickup_longitude': np.float64,
                            'pickup_latitude': np.float64,
                            'dropoff_longitude': np.float64,
                            'dropoff_latitude': np.float64,
                            'store_and_fwd_flag': str,
                            'trip_duration': int,
                        },
                        parse_dates = ['pickup_datetime', 'dropoff_datetime'],
                   )
t("Loaded {} rows data from 'train'".format(len(train)))

Then the output will look like this:

[9.35s] Loaded 1458644 rows data from 'train'

Peter Mortensen's user avatar

answered Aug 7, 2018 at 5:42

Tao Wang's user avatar

Tao WangTao Wang

7048 silver badges5 bronze badges

0

I tried and found time difference using the following scripts.

import time

start_time = time.perf_counter()
[main code here]
print (time.perf_counter() - start_time, "seconds")

answered May 8, 2020 at 4:44

Hafez Ahmad's user avatar

Hafez AhmadHafez Ahmad

1752 silver badges6 bronze badges

1

You do this simply in Python. There is no need to make it complicated.

import time

start = time.localtime()
end = time.localtime()
"""Total execution time in minutes$ """
print(end.tm_min - start.tm_min)
"""Total execution time in seconds$ """
print(end.tm_sec - start.tm_sec)

swateek's user avatar

swateek

6,4698 gold badges36 silver badges48 bronze badges

answered Feb 16, 2019 at 5:18

Mitul Panchal's user avatar

1

Later answer, but I use the built-in timeit:

import timeit
code_to_test = """
a = range(100000)
b = []
for i in a:
    b.append(i*2)
"""
elapsed_time = timeit.timeit(code_to_test, number=500)
print(elapsed_time)
# 10.159821493085474

  • Wrap all your code, including any imports you may have, inside code_to_test.
  • number argument specifies the amount of times the code should repeat.
  • Demo

answered Feb 25, 2020 at 0:15

Pedro Lobito's user avatar

Pedro LobitoPedro Lobito

91.8k30 gold badges244 silver badges265 bronze badges

3

Timeit is a class in Python used to calculate the execution time of small blocks of code.

Default_timer is a method in this class which is used to measure the wall clock timing, not CPU execution time. Thus other process execution might interfere with this. Thus it is useful for small blocks of code.

A sample of the code is as follows:

from timeit import default_timer as timer

start= timer()

# Some logic

end = timer()

print("Time taken:", end-start)

Peter Mortensen's user avatar

answered Nov 16, 2017 at 2:16

Utkarsh Dhawan's user avatar

0

First, install humanfriendly package by opening Command Prompt (CMD) as administrator and type there —
pip install humanfriendly

Code:

from humanfriendly import format_timespan
import time
begin_time = time.time()
# Put your code here
end_time = time.time() - begin_time
print("Total execution time: ", format_timespan(end_time))

Output:

enter image description here

Georgy's user avatar

Georgy

11.8k7 gold badges65 silver badges72 bronze badges

answered Apr 16, 2020 at 10:40

Amar Kumar's user avatar

Amar KumarAmar Kumar

2,3042 gold badges25 silver badges33 bronze badges

Following this answer created a simple but convenient instrument.

import time
from datetime import timedelta

def start_time_measure(message=None):
    if message:
        print(message)
    return time.monotonic()

def end_time_measure(start_time, print_prefix=None):
    end_time = time.monotonic()
    if print_prefix:
        print(print_prefix + str(timedelta(seconds=end_time - start_time)))
    return end_time

Usage:

total_start_time = start_time_measure()    
start_time = start_time_measure('Doing something...')
# Do something
end_time_measure(start_time, 'Done in: ')
start_time = start_time_measure('Doing something else...')
# Do something else
end_time_measure(start_time, 'Done in: ')
end_time_measure(total_start_time, 'Total time: ')

The output:

Doing something...
Done in: 0:00:01.218000
Doing something else...
Done in: 0:00:01.313000
Total time: 0:00:02.672000

answered Nov 26, 2020 at 13:05

Nick Legend's user avatar

Nick LegendNick Legend

6881 gold badge5 silver badges19 bronze badges

This is Paul McGuire’s answer that works for me. Just in case someone was having trouble running that one.

import atexit
from time import clock

def reduce(function, iterable, initializer=None):
    it = iter(iterable)
    if initializer is None:
        value = next(it)
    else:
        value = initializer
    for element in it:
        value = function(value, element)
    return value

def secondsToStr(t):
    return "%d:%02d:%02d.%03d" % 
        reduce(lambda ll,b : divmod(ll[0],b) + ll[1:],
            [(t*1000,),1000,60,60])

line = "="*40
def log(s, elapsed=None):
    print (line)
    print (secondsToStr(clock()), '-', s)
    if elapsed:
        print ("Elapsed time:", elapsed)
    print (line)

def endlog():
    end = clock()
    elapsed = end-start
    log("End Program", secondsToStr(elapsed))

def now():
    return secondsToStr(clock())

def main():
    start = clock()
    atexit.register(endlog)
    log("Start Program")

Call timing.main() from your program after importing the file.

Peter Mortensen's user avatar

answered Apr 8, 2015 at 0:24

Saurabh Rana's user avatar

Saurabh RanaSaurabh Rana

3,2601 gold badge18 silver badges21 bronze badges

The time of a Python program’s execution measure could be inconsistent depending on:

  • Same program can be evaluated using different algorithms
  • Running time varies between algorithms
  • Running time varies between implementations
  • Running time varies between computers
  • Running time is not predictable based on small inputs

This is because the most effective way is using the «Order of Growth» and learn the Big «O» notation to do it properly.

Anyway, you can try to evaluate the performance of any Python program in specific machine counting steps per second using this simple algorithm:
adapt this to the program you want to evaluate

import time

now = time.time()
future = now + 10
step = 4 # Why 4 steps? Because until here already four operations executed
while time.time() < future:
    step += 3 # Why 3 again? Because a while loop executes one comparison and one plus equal statement
step += 4 # Why 3 more? Because one comparison starting while when time is over plus the final assignment of step + 1 and print statement
print(str(int(step / 10)) + " steps per second")

Peter Mortensen's user avatar

answered Jul 31, 2017 at 15:13

Manu's user avatar

ManuManu

1124 bronze badges

After reading this article, you’ll learn: –

  • How to calculate the program’s execution time in Python
  • Measure the total time elapsed to execute the code block in seconds, milliseconds, minutes, and hours
  • Also, get the execution time of functions and loops.

In this article, We will use the following four ways to measure the execution time in Python: –

  • time.time() function: measure the the total time elapsed to execute the script in seconds.
  • time.process_time(): measure the CPU execution time of a code
  • timeit module: measure the execution time of a small piece of a code including the single line of code as well as multiple lines of code
  • DateTime module: measure the execution time in the hours-minutes-seconds format.

To measure the code performance, we need to calculate the time taken by the script/program to execute. Measuring the execution time of a program or parts of it will depend on your operating system, Python version, and what you mean by ‘time’.

Before proceeding further, first, understand what time is.

Table of contents

  • Wall time vs. CPU time
  • How to Measure Execution Time in Python
    • Example: Get Program’s Execution Time in Seconds
    • Get Execution Time in Milliseconds
    • Get Execution Time in Minutes
  • Get Program’s CPU Execution Time using process_time()
  • timeit module to measure the execution time of a code
    • Example: Measure the execution time of a function
    • Measure the execution time of a single line of code
    • Measure the execution time of a multiple lines of code
  • DateTime Module to determine the script’s execution time
  • Conclusion

Wall time vs. CPU time

We often come across two terms to measure the execution time: Wall clock time and CPU time.

So it is essential to define and differentiate these two terms.

  • Wall time (also known as clock time or wall-clock time) is simply the total time elapsed during the measurement. It’s the time you can measure with a stopwatch. It is the difference between the time at which a program finished its execution and the time at which the program started. It also includes waiting time for resources.
  • CPU Time, on the other hand, refers to the time the CPU was busy processing the program’s instructions. The time spent waiting for other task to complete (like I/O operations) is not included in the CPU time. It does not include the waiting time for resources.

The difference between the Wall time and CPU time can occur from architecture and run-time dependency, e.g., programmed delays or waiting for system resources to become available.

For example, a program reports that it has used “CPU time 0m0.2s, Wall time 2m4s”. It means the program was active for 2 minutes and four seconds. Still, the computer’s processor spent only 0.2 seconds performing calculations for the program. May be program was waiting for some resources to become available.

At the beginning of each solution, I listed explicitly which kind of time each method measures.

So depending upon why you are measuring your program’s execution time, you can choose to calculate the Wall or CPU time.

The Python time module provides various time-related functions, such as getting the current time and suspending the calling thread’s execution for the given number of seconds. The below steps show how to use the time module to calculate the program’s execution time.

  1. Import time module

    The time module comes with Python’s standard library. First, Import it using the import statement.

  2. Store the start time

    Now, we need to get the start time before executing the first line of the program. To do this, we will use the time() function to get the current time and store it in a ‘start_time‘ variable before the first line of the program.
    The time() function of a time module is used to get the time in seconds since epoch. The handling of leap seconds is platform-dependent.

  3. Store the end time

    Next, we need to get the end time before executing the last line.
    Again, we will use the time() function to get the current time and store it in the ‘end_time‘ variable before the last line of the program.

  4. Calculate the execution time

    The difference between the end time and start time is the execution time. Get the execution time by subtracting the start time from the end time.

Example: Get Program’s Execution Time in Seconds

Use this solution in the following cases: –

  • Determine the execution time of a script
  • Measure the time taken between lines of code.

Note: This solution measures the Wall time, i.e., total elapsed time, not a CPU time.

import time

# get the start time
st = time.time()

# main program
# find sum to first 1 million numbers
sum_x = 0
for i in range(1000000):
    sum_x += i

# wait for 3 seconds
time.sleep(3)
print('Sum of first 1 million numbers is:', sum_x)

# get the end time
et = time.time()

# get the execution time
elapsed_time = et - st
print('Execution time:', elapsed_time, 'seconds')

Output:

Sum of first 1 million numbers is: 499999500000
Execution time: 3.125561475753784 seconds

Note: It will report more time if your computer is busy with other tasks. If your script was waiting for some resources, the execution time would increase because the waiting time will get added to the final result.

Get Execution Time in Milliseconds

Use the above example to get the execution time in seconds, then multiply it by 1000 to get the final result in milliseconds.

Example:

# get execution time in milliseconds
res = et - st
final_res = res * 1000
print('Execution time:', final_res, 'milliseconds')

Output:

Sum of first 1 million numbers is: 499999500000
Execution time: 3125.988006591797 milliseconds

Get Execution Time in Minutes

Use the above example to get the execution time in seconds, then divide it by 60 to get the final result in minutes.

Example:

# get execution time in minutes
res = et - st
final_res = res / 60
print('Execution time:', final_res, 'minutes')

Output:

Sum of first 1 million numbers is: 499999500000
Execution time: 0.05200800895690918 minutes

Do you want better formatting?

Use the strftime() to convert the time in a more readable format like (hh-mm-ss) hours-minutes-seconds.

import time

st = time.time()
# your code
sum_x = 0
for i in range(1000000):
    sum_x += i
time.sleep(3)
print('Sum:', sum_x)

elapsed_time = time.time() - st
print('Execution time:', time.strftime("%H:%M:%S", time.gmtime(elapsed_time)))

Output:

Sum: 499999500000
Execution time: 00:00:03

Get Program’s CPU Execution Time using process_time()

The time.time() will measure the wall clock time. If you want to measure the CPU execution time of a program use the time.process_time() instead of time.time().

Use this solution if you don’t want to include the waiting time for resources in the final result. Let’s see how to get the program’s CPU execution time.

import time

# get the start time
st = time.process_time()

# main program
# find sum to first 1 million numbers
sum_x = 0
for i in range(1000000):
    sum_x += i

# wait for 3 seconds
time.sleep(3)
print('Sum of first 1 million numbers is:', sum_x)

# get the end time
et = time.process_time()

# get execution time
res = et - st
print('CPU Execution time:', res, 'seconds')

Output:

Sum of first 1 million numbers is: 499999500000
CPU Execution time: 0.234375 seconds

Note:

Because we are calculating the CPU execution time of a program, as you can see, the program was active for more than 3 seconds. Still, those 3 seconds were not added in CPU time because the CPU was ideal, and the computer’s processor spent only 0.23 seconds performing calculations for the program.

timeit module to measure the execution time of a code

Python timeit module provides a simple way to time small piece of Python code. It has both a Command-Line Interface as well as a callable one. It avoids many common traps for measuring execution times.

timeit module is useful in the following cases: –

  • Determine the execution time of a small piece of code such as functions and loops
  • Measure the time taken between lines of code.

The timeit() function: –

The timeit.timeit() returns the time (in seconds) it took to execute the code number times.

timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None)

Note: This solution measures the Wall time, i.e., total elapsed time, not a CPU time.

The below steps show how to measure the execution time of a code using the timeit module.

  • First, create a Timer instance using the timeit() function
  • Next, Pass a code at the place of the stmt argument. stmt is the code for which we want to measure the time
  • Next, If you wish to execute a few statements before your actual code, pass them to the setup argument like import statements.
  • To set a timer value, we will use the default timer provided by Python.
  • Next, decide how many times you want to execute the code and pass it to the number argument. The default value of number is 1,000,000.
  • In the end, we will execute the timeit() function with the above values to measure the execution time of the code

Example: Measure the execution time of a function

Here we will calculate the execution time of an ‘addition()’ function. We will run the addition() function five-time to get the average execution time.

import timeit

# print addition of first 1 million numbers
def addition():
    print('Addition:', sum(range(1000000)))

# run same code 5 times to get measurable data
n = 5

# calculate total execution time
result = timeit.timeit(stmt='addition()', globals=globals(), number=n)

# calculate the execution time
# get the average execution time
print(f"Execution time is {result / n} seconds")

Output:

Addition: 499999500000
Addition: 499999500000
Addition: 499999500000
Addition: 499999500000
Addition: 499999500000

Execution time is 0.03770382 seconds

Note:

If you run time-consuming code with the default number value, it will take a lot of time. So assign less value to the number argument Or decide how many samples do you want to measure to get the accurate execution time of a code.

  • The timeit() functions disable the garbage collector, which results in accurate time capture.
  • Also, using the timeit() function, we can repeat the execution of the same code as many times as we want, which minimizes the influence of other tasks running on your operating system. Due to this, we can get the more accurate average execution time.

Measure the execution time of a single line of code

Run the %timeit command on a command-line or jupyter notebook to get the execution time of a single line of code.

Example: Use %timeit just before the line of code

%timeit [x for x in range(1000)]

# Output
2.08 µs ± 223 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Also, we can customize the command using the various options to enhance the profiling and capture a more accurate execution time.

  • Define the number of runs using the -r option. For example, %timeit -r10 your_code means run the code line 10 times.
  • Define the loops within each run using the -r and -n option.
  • If you ommit the options be default it is 7 runs with each run having 1 million loops

Example: Customize the time profile operation to 10 runs and 20 loops within each run.

# Customizing number of runs and loops in %timeit
%timeit -r10 -n20 [x for x in range(1000)]

# output
1.4 µs ± 12.34 ns per loop (mean ± std. dev. of 10 runs, 20 loops each)

Measure the execution time of a multiple lines of code

Using the %%timeit command, we can measure the execution time of multiple lines of code. The command options will remain the same.

Note: you need to replace the single percentage (%) with double percentage (%%) in the timeit command to get the execution time of multiple lines of a code

Example:

# Time profiling using %%timeit
%%timeit -r5 -n10
# find sum to first 1 million numbers
sum_x = 0
for i in range(1000000):
    sum_x += i

# Output
10.5 µs ± 226 ns per loop (mean ± std. dev. of 5 runs, 10 loops each)

DateTime Module to determine the script’s execution time

Also, you can use the Datetime module to measure the program’s running time. Use the below steps.
Import DateTime module

  • Next, store the start time using the datetime.now() function before the first line of a script
  • Next, save the end time before using the same function before the last line of a script
  • In the end, calculate the execution time by subtracting the start time from an end time

Note: This solution measures the Wall time, i.e., total elapsed time, not a CPU time.

Example:

import datetime
import time

# get the start datetime
st = datetime.datetime.now()

# main program
# find sum to first 1 million numbers
sum_x = 0
for i in range(1000000):
    sum_x += i

# wait for 3 seconds
time.sleep(3)
print('Sum of first 1 million numbers is:', sum_x)

# get the end datetime
et = datetime.datetime.now()

# get execution time
elapsed_time = et - st
print('Execution time:', elapsed_time, 'seconds')

Output:

Sum of first 1 million numbers is: 499999500000
Execution time: 0:00:03.115498 seconds

Conclusion

Python provides several functions to get the execution time of a code. Also, we learned the difference between Wall-clock time and CPU time to understand which execution time we need to measure.

Use the below functions to measure the program’s execution time in Python:

  • time.time(): Measure the the total time elapsed to execute the code in seconds.
  • timeit.timeit(): Simple way to time a small piece of Python code
  • %timeit and %%timeit: command to get the execution time of a single line of code and multiple lines of code.
  • datetime.datetime.now(): Get execution time in hours-minutes-seconds format

Also, use the time.process_time() function to get the program’s CPU execution time.

Чтобы измерить время выполнения программы, используйте функции time.clock() или time.time(). Документы Python утверждают, что эта функция должна использоваться для целей тестирования.

Пример

import time
t0= time.clock()
print("Hello")
t1 = time.clock() - t0
print("Time elapsed: ", t1 - t0) # CPU seconds elapsed (floating point)

Вывод

Это даст вывод —

Time elapsed:  0.0009403145040156798

Вы также можете использовать модуль timeit для правильного статистического анализа времени выполнения фрагмента кода. Он запускает фрагмент несколько раз, а затем сообщает, сколько времени занял самый короткий цикл. Вы можете использовать его следующим образом —

Пример

def f(x):
  return x * x
 
import timeit
timeit.repeat("for x in range(100): f(x)", "from __main__ import f", number=100000)

Вывод

Это даст вывод —

[2.0640320777893066, 2.0876040458679199, 2.0520210266113281]



отвечаю на ваши вопросы. Автор книг и разработчик.

Будет полезно знать

  • 👉 Как объединить два словаря Python?
  • 👉 Как поменять местами две переменные в Python
  • 👉 Как вывести текущую дату и время на Python?

Перейти к содержимому

Меню

Допустим, вам необходимо узнать, сколько времени занимает выполнение той или иной функции. Используя модуль time, вы можете рассчитать это время.

import time

startTime = time.time() # время начала замера

# здесь пишем код, время которого необходимо измерить

endTime = time.time() #время конца замера
totalTime = endTime - startTime #вычисляем затраченное время

print("Время, затраченное на выполнение данного кода = ", totalTime)

27 июня 2020 г. | Python

Хотя многие разработчики признают Python эффективным языком программирования, программы
на чистом Python могут работать медленнее, чем их аналоги на скомпилированных языках, таких
как C, Rust и Java. В этом руководстве вы узнаете, как использовать таймеры Python для
отслеживания скорости выполнения ваших программ.

В этом уроке вы узнаете, как использовать:

  • time.perf_counter() для измерения времени
  • Классы для сохранения состояния
  • Контекстные менеджеры для работы с блоком кода
  • Декораторы для настройки функций

Вы также получите базовые знания о том, как работают классы, контекстные менеджеры и декораторы.
Поскольку будут приведены примеры каждой концепции, вы сможете по желанию использовать одну
или несколько из них в своём коде, как для замера времени выполнения кода, так и для других применений.
Каждый метод содержит свои преимущества, и вы узнаете, какие из них использовать в зависимости от
ситуации. Кроме того, у вас будет рабочий таймер Python, который вы можете использовать для
мониторинга ваших программ!

Таймеры Python

Во-первых, познакомимся с некоторыми примерами кода, которые вы будете использовать
на протяжении всего урока. Позже вы добавите в этот код таймер Python, для мониторинга
его производительности. Вы также увидите некоторые из самых простых способов
измерения времени выполнения примера.

Функции таймера Python

Если вы посмотрите на встроенный модуль time в Python, то заметите несколько функций,
которые могут измерять время:

  • monotonic()
  • perf_counter()
  • process_time()
  • time()

Python 3.7 ввел несколько новых функций, таких как thread_time(), а также наносекундные версии всех
вышеперечисленных функций, именуемых с суффиксом _ns. Например, perf_counter_ns() — это
наносекундная версия perf_counter(). Подробности об этих функциях будет расказано позже.
А пока обратите внимание на то, что говорится в документации о perf_counter():

Возвращает значение (в долях секунд) счётчика производительности,
то есть часов с самым высоким доступным разрешением для измерения
короткого промежутка времени.

Во-первых, вы будете использовать perf_counter() для создания Python таймера. Позже вы
сравните это с другими функциями таймера Python и узнаете, почему perf_counter()
обычно является лучшим выбором.

Пример: Последовательность Фибоначчи

Чтобы лучше сравнить различные способы добавления таймера Python к своему коду, вы будете применять
разные функции таймера Python к одному и тому же примеру кода в данном руководстве. Если у вас уже есть
код, который вы хотели бы измерить, смело следуйте этим примерам.

Пример, который вы увидите в этом учебнике — это простая итеративная функция, которая вычисляет число
по номеру в последовательности Фибоначчи.

Числа Фибоначчи – это ряд чисел, в котором каждое следующее число равно сумме двух
предыдущих: 1, 1, 2, 3, 5, 8, 13, … . Иногда ряд начинают с нуля: 0, 1, 1, 2, 3, 5, … . В данном
случае мы будем придерживаться первого варианта.

Вычисление n-го числа ряда Фибоначчи с помощью цикла while:

def fibo(n):
    fib1 = 1
    fib2 = 1

    i = 0
    while i < n - 2:
        fib_sum = fib1 + fib2
        fib1 = fib2
        fib2 = fib_sum
        i = i + 1
    return fib2

Сохраним пример в файле с именем fibo.py. Код состоит из одной функции, которая вычисляет
и печатает n элемент последовательности.

Ваш первый таймер Python

Давайте добавим в пример простой Python-таймер с помощью time.perf_counter(). Опять же, это
счётчик производительности, который хорошо подходит для замеров времени выполнения
частей кода.

perf_counter() измеряет время в секундах с некоторого неопределенного момента времени,
что означает, что возвращаемое значение одного вызова функции бесполезно. Однако,
когда вы посмотрите на разницу между двумя вызовами perf_counter(), вы сможете выяснить,
сколько секунд прошло между двумя вызовами:

>>> import time
>>> time.perf_counter()
32311.48899951

>>> time.perf_counter()  # Несколько секунд спустя
32315.261320793

В этом примере вы сделали два вызова perf_counter() с интервалом почти 4 секунды. Вы можете
подтвердить это, рассчитав разницу между двумя выходами: 32315.26 — 32311.49 = 3.77.

Теперь добавим таймер Python к коду примера:

# series_number.py

import time

from fibo import fibo

def main():

    """Печать 1000 элемента последовательности Фибоначчи"""
    tic = time.perf_counter()
    result = fibo(1000)
    toc = time.perf_counter()
    print(f"Вычисление заняло {toc - tic:0.4f} секунд")
    print(result)

if __name__ == "__main__":
    main()

Обратите внимание, что perf_counter() вызывается как до, так и после вычисления значения
функции. Затем печатается время, необходимое для вычисления, вычисляя разницу
между двумя вызовами.

Примечание: в строке print(f"Вычисление заняло {toc - tic:0.4f} секунд") буква f перед строкой
указывает на то, что это f-строка, что является удобным способом форматирования текстовой
строки. : :0.4f — это спецификатор формата, который говорит, что число toc - tic должно быть
напечатано как десятичное число с четырьмя десятичными знаками.

f-строки доступны только в Python 3.6 и более поздних версиях. Для получения
дополнительной информации ознакомьтесь с статьей по
форматированию строк в Python.

Теперь, когда вы запустите пример, вы увидите потраченное время на вычисления:

$ python series_number.py
Вычисление заняло 0.0004 секунд

[ ... Полный текст результата вычислений ... ]

Мы рассмотрели основы тайминга Python кода. В оставшейся части руководства вы узнаете,
как можно обернуть Python-таймер в класс, менеджер контекста и декоратор, чтобы сделать его
более консистентным и удобным в использовании.

Python класс Timer

Посмотрите, как вы добавили таймер Python в приведённый выше пример. Обратите внимание,
что вам нужна хотя бы одна переменная (tic) для хранения состояния таймера Python перед
началом вычислений. Теперь создадим класс, который будет выполнять те же действия, что
и ручные вызовы perf_counter(), но более читабельно и консистентно.

В этом руководстве будет создадан и модифицирован класс Timer, который вы можете использовать
для определения таймингов кода несколькими различными способами.

Понимание классов в Python

Классы являются основными строительными блоками объектно-ориентированного программирования.
Класс — это шаблон, который вы можете использовать для создания объектов. Хотя Python не заставляет
вас программировать объектно-ориентированным способом, классы используются повсюду в языке.
Для быстрого доказательства давайте рассмотрим модуль time:

>>> import time
>>> type(time)
<class 'module'>

>>> time.__class__
<class 'module'>

type() возвращает тип объекта. Здесь вы можете видеть, что модули на самом деле являются
объектами, созданными из класса module. Специальный атрибут .__class__ используется для
получения доступа к классу, который определяет объект. На самом деле, почти всё в
Python — это класс:

>>> type(3)
<class 'int'>

>>> type(None)
<class 'NoneType'>

>>> type(print)
<class 'builtin_function_or_method'>

>>> type(type)
<class 'type'>

В Python классы отлично подходят, когда вам нужно смоделировать что-то, что должно
отслеживать определенное состояние. В общем случае класс — это набор свойств (называемых
атрибутами) и поведений (называемых методами).

Создание класса таймера Python

Классы хороши для отслеживания состояния. В классе Timer вы хотите отслеживать, когда
запускается таймер и сколько времени прошло с момента реализации. Для первой
реализации Timer создадим атрибут ._start_time, а также методы .start() и .stop().
Добавим следующий код в файл с именем timer.py:

# timer.py

import time

class TimerError(Exception):  #5
    """Пользовательское исключение, используемое для сообщения об ошибках при использовании класса Timer"""


class Timer: #9

    def __init__(self):
        self._start_time = None


    def start(self):
        """Запуск нового таймера"""

        if self._start_time is not None:
            raise TimerError(f"Таймер уже работает. Используйте .stop() чтобы его остановить")

        self._start_time = time.perf_counter()


    def stop(self):
        """Отстановить таймер и сообщить о времени вычисления"""

        if self._start_time is None:
            raise TimerError(f"Таймер не работает. Используйте .start() для его запуска")

        elapsed_time = time.perf_counter() - self._start_time
        self._start_time = None
        print(f"Вычисление заняло {elapsed_time:0.4f} секунд")

Здесь происходит несколько разных вещей, поэтому давайте пройдемся по коду шаг
за шагом.

В строке с комментарием #5 определяется класс TimerError. Обозначение (Exception) означает, что TimerError
наследует от другого класса с именемException. Python использует этот встроенный класс
для обработки ошибок. Вам не нужно добавлять какие-либо атрибуты или методы в TimerError.
Тем не менее, наличие пользовательской ошибки даст вам больше гибкости для решения
проблем внутри Timer.

Определение самого Timer начинается на строке #9. Когда вы впервые создаёте или создаёте
экземпляр объекта из класса, ваш код вызывает специальный метод .__init__(). В этой
первой версии таймера вы инициализируете только атрибут ._start_time, который будет
использоваться для отслеживания состояния класса таймера Python. У него будет значение
None, когда таймер ещё не наченал работу. После запуска таймера ._start_time отслеживает,
когда таймер был запущен.

Примечание: префикс подчеркивания ._start_time является соглашением Python. Он сигнализирует
о том, что ._start_time является внутренним атрибутом, которым не должны манипулировать
пользователи класса Timer.

Когда вызывается .start() для запуска нового таймера Python, сначала проверяется, что таймер
ещё не запущен. Затем сохраняется текущее значение perf_counter() в ._start_time. С другой
стороны, когда вызывается .stop(), вы сначала проверяете, работает ли таймер Python.
Если это так, то вы вычисляете истекшее время как разницу между текущим значением
perf_counter() и тем, которое было сохранено в ._start_time. Наконец, сбрасыватся ._start_time,
чтобы таймер мог быть перезапущен, и распечатать истекшее время.

Использование класса Timer:

>>> from timer import Timer
>>> t = Timer()
>>> t.start()

>>> t.stop()  # Несколько секунд спустя
Elapsed time: 3.8191 seconds

Сравните это с предыдущим примером, где вы использовали perf_counter() напрямую. Структура кода
довольно похожа, но теперь код стал более понятным, и это является одним из преимуществ
использования классов. Тщательно выбирая имена классов, методов и атрибутов, вы можете
сделать свой код очень информативным!

Использование класса Timer Python

Давайте применим таймер к series_number.py. Вам нужно всего лишь внести несколько
изменений в свой предыдущий код:

# series_number.py

from timer import Timer
from reader import feed

def main():
    """Печать 1000 элемента последовательности Фибоначчи"""
    t = Timer()
    t.start()
    result = fibo(1000)
    t.stop()

    print(result)

if __name__ == "__main__":
    main()

Обратите внимание, что код очень похож на то, что вы видели ранее. В дополнение к тому,
чтобы сделать код более читабельным, Timer заботится о печати прошедшего времени на
консоль, что делает логгирование затраченного времени более последовательным.
Когда вы запустите код, вы увидите примерно такой же вывод:

$ python series_number.py
Вычисление заняло 0.0004 секунд
# Функции Python Timer: три способа контролировать ваш код

[ ... Результат вычисления ... ]

Печать прошедшего времени из Timer может быть последовательной, но данный подход
не очень гибкий. В следующем разделе вы увидите, как настроить свой класс.

Добавление большего удобства и гибкости

До сих пор вы видели, что классы подходят для случаев, когда вы хотите инкапсулировать
состояние и обеспечивать согласованное поведение в вашем коде. В этом разделе вы
добавим больше удобств и гибкости вашему таймеру Python:

  • Используем адаптируемый текст и форматирование, сообщая о затраченном времени
  • Применим гибкое журналирование либо на экране, либо в файле журнала, либо в других частях вашей программы
  • Создать Python таймер, который может накапливаться за несколько вызовов
  • Собрать информативное представление таймера Python

Во-первых, давайте посмотрим, как вы можете настроить текст, используемый для отчёта о затраченном
времени. В предыдущем коде текст f"Вычисление заняло {toc - tic:0.4f} секунд" жёстко закодирован в .stop().
Вы можете добавить гибкость классам, используя переменные экземпляра. Их значения обычно
передаются в качестве аргументов .__init__() и сохраняются как собственные атрибуты. Для удобства
вы также можете предоставить разумные значения по умолчанию.

Чтобы добавить .text в качестве переменной экземпляра Timer, изменим код:

Обратите внимание, что текст по умолчанию "Вычисление заняло {toc - tic:0.4f} секунд" передаётся
как обычная строка, а не как f-строка. Вы не можете использовать f-строку здесь, потому что они
вычисляются немедленно, и когда вы создаете экземпляр Timer, ваш код ещё не вычислил
истекшее время.

Примечание: Если вы хотите использовать f-строку для указания .text, то вам нужно использовать
двойные фигурные скобки, чтобы избежать фигурных скобок, которые заменит фактическое
истекшее время.

В .stop() используется .text в качестве шаблона и .format() для заполнения шаблона:

def stop(self):
    """Остановите таймер и сообщите истекшее время"""
    if self._start_time is None:
        raise TimerError(f"Таймер не работает. Используйте .start(), чтобы запустить его")

    elapsed_time = time.perf_counter() - self._start_time
    self._start_time = None
    print(self.text.format(elapsed_time))

После обновления timer.py вы можете изменить текст следующим образом:

>>> from timer import Timer
>>> t = Timer(text="Вы ждёте {:.1f} секунд")
>>> t.start()

>>> t.stop()  # Несколько секунд спустя
Вы ждёте 4.1 секунд

Далее, предположим, что нужно не просто напечатать сообщение в консоль. Может быть, вы хотите
сохранить ваши измерения времени в базе данных. Вы можете сделать это, возвращая значение
elapsed_time из .stop(). Затем вызывающий код может либо игнорировать это возвращаемое значение,
либо сохранить его для дальнейшей обработки.

Возможно, вы хотите интегрировать Timer в свои процедуры логгирования. Для поддержки
логгирования или других выходов из Timer вам нужно изменить вызов print(), чтобы пользователь
мог предоставить свою собственную функцию логгирования. Это можно сделать аналогично
тому, как ранее настраивался текст:

def __init__(self, text="Вычисление заняло {:0.4f} секунд", logger=print):
    self._start_time = None
    self.text = text
    self.logger = logger

def stop(self):
    """Остановить таймер и сообщить истекшее время"""
    if self._start_time is None:
        raise TimerError(f"Таймер не работает. Используйте .start(), чтобы запустить его")

    elapsed_time = time.perf_counter() - self._start_time
    self._start_time = None

    if self.logger:
        self.logger(self.text.format(elapsed_time))

    return elapsed_time

Вместо непосредственного использования print() создана другая переменная экземпляра
self.logger, которая должна ссылаться на функцию, которая принимает строку в качестве
аргумента. В дополнение к print() вы можете использовать такие функции, как logging.info()
или .write() для файловых объектов. Также обратите внимание на тест if, который
позволяет полностью отключить печать, передав команду logger = None.

Вот два примера, которые показывают новую функциональность в действии:

>>> from timer import Timer
>>> import logging
>>> t = Timer(logger=logging.warning)
>>> t.start()

>>> t.stop()  # Спустя несколько секунд
WARNING:root:Elapsed time: 3.1610 seconds
3.1609658249999484

>>> t = Timer(logger=None)
>>> t.start()

>>> value = t.stop()  # Спустя несколько секунд
>>> value
4.710851433001153

Когда вы запускаете эти примеры в интерактивной оболочке, Python автоматически печатает
возвращаемое значение.

Третье улучшение, которое будет добавлено — это возможность накапливать измерения
времени. Это может понадобиться, когда вызывается медленная функция в цикле. Добавим
немного больше функциональности в виде именованных таймеров со словарем, который
отслеживает каждый таймер Python в коде.

Предположим, что файл series_number.py расширяется до сценария series_numbers.py,
который загружает и распечатывает несколько результатов работы функции fibo.
Ниже приведена одна из возможных реализаций:

# series_numbers.py

from timer import Timer
from fibo import fibo

def main():
    """Печать первых 10 элементов последовательности Фибоначчи"""
    t = Timer(text="Печать 10 первых элементов за {:0.2f} секунд")
    t.start()
    for num in range(10):
        result = fibo(num)
        print(result)
    t.stop()

if __name__ == "__main__":
    main()

Код перебирает числа от 0 до 9 и использует их в качестве аргументов смещения для fibo().
Когда вы запустите скрипт, вы увидите много информации, напечатанной на вашей консоли:

$ python series_numbers.py

[ ... Результаты вычислений ... ]

Одна тонкая проблема с этим кодом заключается в том, что вы измеряете не только время,
необходимое для вычисления элемента последовательности, но и время, которое Python
тратит на печать результатов на экран. Это может быть не так важно, поскольку время,
потраченное на печать, должно быть незначительным по сравнению со временем,
потраченным на вычисления. Тем не менее, было бы хорошо иметь возможность точно
определить время.

Есть несколько способов обойти это без изменения текущей реализации Timer. Однако
поддержка этого варианта использования будет весьма полезна и может быть выполнена
всего несколькими строками кода.

Во-первых, добавим словарь .timers в качестве переменной класса в Timer, что означает,
что все экземпляры Timer будут использовать его совместно. Реализуется это, определяя
словарь вне любых методов:

class Timer:
    timers = dict()

Переменные класса могут быть доступны либо непосредственно в классе, либо через
экземпляр класса:

>>> from timer import Timer
>>> Timer.timers
{}

>>> t = Timer()
>>> t.timers
{}

>>> Timer.timers is t.timers
True

В обоих случаях код возвращает один и тот же пустой словарь классов.

Затем добавим дополнительные имена к вашему таймеру Python. Вы можете использовать
имя для двух разных целей:

  1. Поиск истекшего времени в вашем коде
  2. Накопление таймеров с тем же именем

Чтобы добавить имена к вашему таймеру Python, вам нужно внести ещё два изменения
в timer.py. Во-первых, Timer должен принять имя в качестве параметра. Во-вторых, истекшее
время должно быть добавлено к .timers, когда таймер останавливается:

class Timer:
    timers = dict()

    def __init__(
        self,
        name=None,
        text="Вычисление заняло {:0.4f} секунд",
        logger=print,
    ):
        self._start_time = None
        self.name = name
        self.text = text
        self.logger = logger

        # Добавить новые именованные таймеры в словарь таймеров
        if name:
            self.timers.setdefault(name, 0)

    # Другие методы без изменений

    def stop(self):
        """Остановить таймер и сообщить истекшее время"""
        if self._start_time is None:
            raise TimerError(f"Таймер не работает. Используйте .start(), чтобы запустить его")

        elapsed_time = time.perf_counter() - self._start_time
        self._start_time = None

        if self.logger:
            self.logger(self.text.format(elapsed_time))
        if self.name:
            self.timers[self.name] += elapsed_time

        return elapsed_time

Обратите внимание, что используется .setdefault() при добавлении нового таймера Python
в .timers. Это отличная функция, которая устанавливает значение только в том случае, если
имя ещё не определено в словаре. Если имя уже используется в .timers, то значение
остается без изменений. Это позволяет накапливать несколько таймеров:

>>> from timer import Timer
>>> t = Timer("accumulate")
>>> t.start()

>>> t.stop()  # Несколько секунд спустя
Вычисление заняло 3.7036 секунд
3.703554293999332

>>> t.start()

>>> t.stop()  # Несколько секунд спустя
Вычисление заняло 2.3449 секунд
2.3448921170001995

>>> Timer.timers
{'accumulate': 6.0484464109995315}

Теперь вернёмся к series_numbers.py и убедиться, что измеряется только время,
потраченное на вычисления:

# series_numbers.py

from timer import Timer
from fibo import fibo

def main():
    """Печать 10 первых чисел последовательности Фибоначчи"""
    t = Timer("download", logger=None)
    for num in range(10):
        t.start()
        result = fibo(num)
        t.stop()
        print(result)

    cum_time = Timer.timers["download"]
    print(f"Вычисление 10 чисел последовательности заняло {cum_time:0.2f} секунд")

if __name__ == "__main__":
    main()

Повторный запуск сценария верёт такой же результат, как и раньше, хотя сейчас измеряется
только фактическое время вычислений:

$ python series_numbers.py

[ ... Результаты вычислений ... ]
Вычисление 10 чисел последовательности заняло 0.0065 секунд

Последнее улучшение, которое внесём в Timer — это сделать его более информативным, когда
вы работаете с ним в интерактивном режиме. Попробуем следующее:

>>> from timer import Timer
>>> t = Timer()
>>> t
<timer.Timer object at 0x7f0578804320>

Последняя строка является способом, которым Python представляет объекты по умолчанию. Хотя
вы можете почерпнуть из него некоторую информацию, она обычно не очень полезна. Вместо этого
было бы неплохо увидеть такие вещи, как имя Timer или как он будет сообщать о времени.

В Python 3.7 классы данных (датаклассы) были добавлены в стандартную библиотеку. Они обеспечивают несколько
удобств для ваших классов, включая более информативную строку представления.

Примечание: классы данных включены в Python только для версии 3.7 и выше. Однако в PyPI для Python 3.6 имеется бэкпорт.
Вы можете установить его используя pip:
$ python -m pip install dataclasses

Конвертируем таймер Python в класс данных, используя декоратор @dataclass. Вы узнаете больше о
декораторах позже в этом уроке. Сейчас можно представить это как о нотации, которая сообщает
Python, что Timer является классом данных:

from dataclasses import dataclass, field

from typing import Any, ClassVar


@dataclass
class Timer:

    timers: ClassVar = dict()
    name: Any = None
    text: Any = "Вычисление заняло {:0.4f} секунд"
    logger: Any = print
    _start_time: Any = field(default=None, init=False, repr=False)


    def __post_init__(self):
        """Инициализация: добавить таймер к dict таймеров"""
        if self.name:
            self.timers.setdefault(self.name, 0)

    # Остальная часть кода не изменилась

Этот код заменяет предыдущий метод .__init__(). Обратите внимание, как классы данных используют
синтаксис, который выглядит аналогично синтаксису переменных класса, который вы видели ранее
для определения всех переменных. Фактически, .__init__() создается автоматически для классов
данных на основе аннотированных переменных в определении класса.

Вы должны аннотировать свои переменные, чтобы использовать класс данных. Вы можете использовать
это, чтобы добавить подсказки к вашему коду. Если вы не хотите использовать подсказки типов, вместо
этого вы можете аннотировать все переменные с помощью Any, как вы делали выше. Вскоре вы увидите,
как добавить фактические подсказки типов в ваш класс данных.

Вот несколько заметок о классе данных Timer:

  • Строка 6: декоратор @dataclass определяет Timer как класс данных.
  • Строка 9: Специальная аннотация ClassVar необходима для классов данных, чтобы указать, что .timers является переменной класса.
  • Строки с 10 по 12: .name, .text и .logger будут определены как атрибуты на Timer, значения которых могут быть указаны при создании экземпляров Timer. Все они имеют заданные значения по умолчанию.
  • Строка 13: напомним, что ._start_time — это специальный атрибут, который используется для отслеживания состояния таймера Python, но должен быть скрыт от пользователя. Используя dataclasses.field(), вы говорите, что ._start_time следует удалить из .__init__() и представления Timer.
  • Строки с 16 по 19: Используется специальный метод .__post_init__() для любой инициализации, которую вам нужно выполнить, кроме установки атрибутов экземпляра. Здесь он используется для добавления именованных таймеров в .timers.

Новый класс данных Timer работает так же, как ваш предыдущий обычный класс, за исключением
того, что теперь он имеет хорошее представление:

>>> from timer import Timer
>>> t = Timer()
>>> t
Timer(name=None, text='Elapsed time: {:0.4f} seconds',
      logger=<built-in function print>)

>>> t.start()

>>> t.stop()  # Несколько секунд спустя
Elapsed time: 6.7197 seconds
6.719705373998295

Теперь у вас есть довольно аккуратная версия Timer, которая последовательна, гибка, удобна и информативна!
Многие из улучшений, которые вы видели в этом разделе, могут быть применены и к другим типам классов в ваших проектах.

Прежде чем закончить этот раздел, давайте взглянем на полный исходный код Timerв его нынешнем виде.
Вы заметите добавление подсказок типа к коду для дополнительной документации:

# timer.py

from dataclasses import dataclass, field
import time
from typing import Callable, ClassVar, Dict, Optional

class TimerError(Exception):
    """Пользовательское исключение, используемое для сообщения об ошибках при использовании класса Timer"""

@dataclass
class Timer:
    timers: ClassVar[Dict[str, float]] = dict()
    name: Optional[str] = None
    text: str = "Вычисление заняло {:0.4f} секунд"
    logger: Optional[Callable[[str], None]] = print
    _start_time: Optional[float] = field(default=None, init=False, repr=False)

    def __post_init__(self) -> None:
        """Добавить таймер к dict таймеров после инициализации"""
        if self.name is not None:
            self.timers.setdefault(self.name, 0)

    def start(self) -> None:
        """Начать новый таймер"""
        if self._start_time is not None:
            raise TimerError(f"Таймер работает. Используйте .stop(), чтобы остановить его")

        self._start_time = time.perf_counter()

    def stop(self) -> float:
        """Остановить таймер и сообщить истекшее время"""
        if self._start_time is None:
            raise TimerError(f"Таймер не работает. Используйте .start(), чтобы запустить его")

        # Рассчитать прошедшее время
        elapsed_time = time.perf_counter() - self._start_time
        self._start_time = None

        # Сообщить о прошедшем времени
        if self.logger:
            self.logger(self.text.format(elapsed_time))
        if self.name:
            self.timers[self.name] += elapsed_time

        return elapsed_time

Использование класса для создания таймера, Python предлагает несколько преимуществ:

  • Удобочитаемость: ваш код будет читаться более естественно, если вы тщательно выберете имена классов и методов
  • Согласованность: ваш код будет легче использовать, если вы инкапсулируете свойства и поведение в атрибуты и методы
  • Гибкость: ваш код будет многократно использоваться, если вы будете использовать атрибуты со значениями по умолчанию вместо жестко закодированных значений

Класс очень гибкий, и вы можете использовать его практически в любой ситуации, когда вы хотите отслеживать время,
необходимое для выполнения кода. Тем не менее, в следующих разделах вы узнаете об использовании менеджеров контекста
и декораторов, которые будут более удобными для замеров блоков кода и функций.

Менеджер контекста Python Timer

Python класс Timer прошёл долгий путь! По сравнению с первым созданным таймером Python код стал достаточно
мощным. Тем не менее, для использования таймера все еще есть немного стандартного кода:

  1. Во-первых, создать экземпляр класса.
  2. Вызвать .start() перед тем блоком кода, который вы хотите синхронизировать.
  3. Вызовать .stop() после блока кода.

К счастью, в Python есть уникальная конструкция для вызова функций до и после
блока кода — менеджер контекста.
В этом разделе вы узнаете, что такое контекстные менеджеры и как вы можете создавать свои собственные.
Затем вы увидите, как расширить Timer, чтобы он мог работать и в качестве менеджера контекста. Наконец, вы увидите, как использование
Timer в качестве менеджера контекста который упростит ваш код.

Понимание контекстных менеджеров в Python

Менеджеры контекста были частью Python в течение долгого времени. Они были представлены PEP 343 в 2005 году и
впервые реализованы в Python 2.5. Вы можете распознать контекстные менеджеры в коде с помощью ключевого слова with:

with EXPRESSION as VARIABLE:
    BLOCK

EXPRESSION — это некоторое выражение Python, которое возвращает менеджер контекста.
Менеджер контекста необязательно связан с именем VARIABLE. BLOCK— это любой обычный блок кода Python.
Менеджер контекста гарантирует, что ваша программа вызывает некоторый код перед BLOCK, а другой — после выполнения BLOCK.
Последнее произойдет, даже если BLOCK вызовет исключение.

Наиболее распространённое использование контекстных менеджеров, вероятно, обработка различных ресурсов,
такие как файлы, блокировки и соединения с базой данных. Затем менеджер контекста используется для освобождения
и очистки ресурса после его использования. В следующем примере раскрывается фундаментальная структура timer.py
путём печати только строк, содержащих двоеточие. Что ещё более важно, он показывает общую идиому для открытия файла в Python:

>>> with open("timer.py") as fp:
...     print("".join(ln for ln in fp if ":" in ln))
...
class TimerError(Exception):
class Timer:
    timers: ClassVar[Dict[str, float]] = dict()
    name: Optional[str] = None
    text: str = "Elapsed time: {:0.4f} seconds"
    logger: Optional[Callable[[str], None]] = print
    _start_time: Optional[float] = field(default=None, init=False, repr=False)

    def __post_init__(self) -> None:
        if self.name is not None:

    def start(self) -> None:
        if self._start_time is not None:

    def stop(self) -> float:
        if self._start_time is None:
        if self.logger:
        if self.name:

Обратите внимание, что fp, указатель файла, никогда не закрывается явно, потому что использовался open()
в качестве менеджера контекста. Можно подтвердить, что fp закрылся автоматически:

>>> fp.closed
True

В этом примере open(“timer.py”) — это выражение, которое возвращает менеджер контекста.
Менеджер контекста связан с именем fp. Менеджер контекста действует во время выполнения print().
Данный однострочный кодовый блок выполняется в контексте fp.

Что это значит, что fp является контекстным менеджером? Технически это означает, что fp реализует
протокол менеджера контекста. В основе языка Python лежит много разных протоколов. Вы можете думать о
протоколе как о контракте, в котором указано, какие конкретные методы должен реализовывать ваш код.

Протокол менеджера контекста состоит из двух методов:

  • Вызов .__enter__() при входе в контекст, связанный с менеджером контекста.
  • Вызов .__exit__() при выходе из контекста, связанного с менеджером контекста.

Другими словами, чтобы создать менеджер контекста самостоятельно, вам нужно написать класс,
который реализует.__enter__() и .__exit__(). Ни больше ни меньше. Давайте познкомимся с Hello, World! примером менеджера контекста:

# greeter.py

class Greeter:
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print(f"Привет {self.name}")
        return self

    def __exit__(self, exc_type, exc_value, exc_tb):
        print(f"Увидимся позже, {self.name}")

Greeter — менеджер контекста, потому что он реализует протокол менеджера контекста. Вы можете использовать его так:

>>> from greeter import Greeter
>>> with Greeter("Вася"):
...     print("Что-нибудь сделать ...")
...
Привет Вася
Что-нибудь сделать ...
Увидимся позже, Вася

Во-первых, обратите внимание, как .__enter__() вызывается до того, как вы что-то делаете,
а .__exit__() вызывается после. В этом упрощенном примере мы не ссылаемся на менеджер контекста.
В таких случаях вам не нужно указывать менеджеру контекста имя с as.

Затем обратите внимание, как .__enter__() возвращает self. Возвращаемое значение .__enter__() — это то,
с чем связано as. Обычно нужно вернуть self из .__enter__() При создании менеджеров контекста.
Вы можете использовать это возвращаемое значение следующим образом:

>>> from greeter import Greeter
>>> with Greeter("Оля") as grt:
...     print(f"{grt.name} что-то делает ...")
...
Привет Оля
Оля что-то делает ...
Увидимся позже, Оля

Наконец, .__exit__() принимает три аргумента: exc_type, exc_value и exc_tb. Они используются
для обработки ошибок в менеджере контекста и отражают возвращаемые значения sys.exc_info().
Если во время выполнения блока возникает исключение, то ваш код вызывает .__exit__() с типом исключения,
экземпляром исключения и объектом трассировки. Часто вы можете игнорировать их в вашем менеджере контекста,
и в этом случае .__exit__() вызывается перед повторным вызовом исключения:

>>> from greeter import Greeter
>>> with Greeter("Петя") as grt:
...     print(f"{grt.age} не существует")
...
Привет Петя
Увидимся позже, Петя
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: 'Greeter' object has no attribute 'age'

Вы можете увидеть, что «Увидимся позже, Петя» печатается, даже если в коде есть ошибка.

Теперь вы знаете, что такое контекстные менеджеры и как вы можете создать свой собственный.
Если вы хотите погрузиться глубже, то посмотрите
contextlib в стандартной библиотеке.
Она включает в себя удобные способы определения новых контекстных менеджеров, а также готовые
контекстные менеджеры, которые можно использовать для закрытия объектов,
устранения ошибок или даже бездействия!

Создание менеджера контекста Python Timer

Вы увдели, как работают контекстные менеджеры в целом, но как они могут помочь с Timer кодом?
Если вы можете запускать определенные функции до и после блока кода, вы можете упростить работу таймера Python.
До сих пор вам нужно было явно вызывать .start() и .stop() при синхронизации вашего кода,
но менеджер контекста может сделать это автоматически.

Опять же, чтобы Timer работал в качестве менеджера контекста, он должен придерживаться протокола менеджера контекста.
Другими словами, он должен реализовывать .__enter__() и .__exit__() для запуска и остановки таймера Python.
Все необходимые функции уже доступны, поэтому вам не нужно писать много нового кода.
Просто добавьте следующие методы в класс Timer:

def __enter__(self):
    """Запустить новый таймер в качестве менеджера контекста"""
    self.start()
    return self

def __exit__(self, *exc_info):
    """Остановить таймер контекстного менеджера"""
    self.stop()

Таймер теперь контекстный менеджер. Важной частью реализации является то, что .__enter__() вызывает .start() для
запуска таймера Python при вводе контекста, и .__exit__() использует .stop() для остановки таймера Python,
когда код покидает контекст. Проверим это:

>>> from timer import Timer
>>> import time
>>> with Timer():
...     time.sleep(0.7)
...
Elapsed time: 0.7012 seconds

Вы также должны отметить ещё две тонкие детали:

  1. __enter__() возвращает self, экземпляр Timer, который позволяет пользователю связать экземпляр Timer с переменной, используя as. Например, with Timer() as t: создаст переменную t, указывающую на объект Timer.
  2. __exit__() ожидает тройку аргументов с информацией о любом исключении, которое произошло во время выполнения контекста. В вашем коде эти аргументы упакованы в кортеж с именем exc_info и затем игнорируются, что означает, что Timer не будет пытаться обрабатывать какие-либо исключения.

.__exit__() в этом случае не обрабатывает ошибки. Тем не менее, одна из замечательных особенностей контекстных менеджеров заключается в том, что они гарантированно вызывают .__exit__(), независимо от того, как выходит контекст. В следующем примере намеренно создадим ошибку путем деления на ноль:

>>> from timer import Timer
>>> with Timer():
...     for num in range(-3, 3):
...         print(f"1 / {num} = {1 / num:.3f}")
...
1 / -3 = -0.333
1 / -2 = -0.500
1 / -1 = -1.000
Elapsed time: 0.0001 seconds
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
ZeroDivisionError: division by zero

Обратите внимание, что Timer выводит истекшее время, даже если код не работает. Можно проверять и подавлять ошибки в __exit__().
Смотрите документацию для получения дополнительной информации.

Использование менеджера контекста Python Timer

Давайте посмотрим, как использовать менеджер контекста Timer для определения времени
вычисления числа Фибоначчи. Вспомните, как вы использовали Timer ранее:

# series_number.py

from timer import Timer
from reader import feed

def main():
    """Печать 1000 элемента последовательности Фибоначчи"""
    t = Timer()
    t.start()
    result = fibo(1000)
    t.stop()

    print(result)

if __name__ == "__main__":
    main()

Теперь замерим вызов fibo(1000). Воспользуемся менеджером контекста,
чтобы сделать код короче, проще и более читабельным:

# series_number.py

from timer import Timer
from fibo import fibo

def main():
    """Печать 1000 элемента последовательности Фибоначчи"""
    with Timer():
        result = fibo(1000)

    print(tutorial)

if __name__ == "__main__":
    main()

Код делает практически то же, что и код выше. Основное отличие состоит в том, что вы не
определяете постороннюю переменную t, которая поддерживает чистоту вашего пространства имён.

Запуск скрипта должен дать знакомый результат:

$ python series_number.py
Elapsed time: 0.071 seconds

[ ... Результаты вычисления ... ]

Есть несколько преимуществ для добавления возможностей менеджера контекста к вашему классу таймера Python:

  • Незначительные усилия: вам нужно только одну дополнительную строку кода, чтобы рассчитать время выполнения блока кода.
  • Удобочитаемость: вызов менеджера контекста удобочитаем, и вы можете более четко визуализировать измеряемый блок кода.

Использование Timer в качестве менеджера контекста почти так же гибко, как и непосредственное использование .start() и .stop(),
хотя в нем меньше стандартного кода. В следующем разделе вы увидите, как можно использовать Timer в качестве декоратора.
Это облегчит мониторинг времени выполнения полных функций.

Python Timer декоратор

Ваш класс Timer теперь очень универсален. Однако есть один вариант использования, где он может быть ещё более упорядоченным.
Скажем, вы хотите отслеживать время, проведенное внутри одной данной функции в вашей кодовой базе. Используя контекстный менеджер, у вас есть два основных варианта:

1. Использовать Timer каждый раз, когда вы вызываете функцию:

with Timer("some_name"):
    do_something()

Если вы вызовете do_something() во многих местах, это станет громоздко и сложно в обслуживании.

2. Обернём код функцией содержащей внутри контекстный менеджер:

def do_something():
    with Timer("some_name"):
        ...

Timer нужно добавить только в одном месте, но это добавляет уровень отступа ко всему определению do_something().

Лучшее решение — использовать Timer в качестве декоратора. Декораторы — это мощные конструкции, которые вы используете для изменения поведения функций и классов. В этом разделе вы узнаете немного о том, как работают декораторы, как можно расширить Timer, чтобы стать декоратором, и как это упростит функции синхронизации.

Понимание декораторов в Python

Декоратор — это функция, которая оборачивает другую функцию, чтобы изменить её поведение.
Эта техника возможна, потому что функции являются объектами первого класса в Python.
Другими словами, функции можно назначать переменным и использовать в качестве аргументов для других функций,
как и любой другой объект. Это дает вам большую гибкость и является основой для некоторых из более мощных функций Python.

В качестве первого примера создадим декоратор, который ничего не делает:

def turn_off(func):
    return lambda *args, **kwargs: None

Во-первых, обратите внимание, что turn_off() — это обычная функция. Что делает её декоратором, так это то,
что она принимает функцию в качестве единственного аргумента и возвращает функцию.
Вы можете использовать её, чтобы изменить другие функции:

>>> print("Hello")
Hello

>>> print = turn_off(print)
>>> print("Hush")
>>> # Ничего не печатается

Строка print = turn_off(print) декорирует инструкцию print с помощью декоратора turn_off(). По сути, он заменяет print() на lambda *args, **kwargs: None не возвращается turn_off(). Инструкция lambda представляет анонимную функцию, которая ничего не делает, кроме возврата None.

Чтобы определить более интересные декораторы, вам нужно знать о внутренних функциях. Внутренняя функция — это функция, определенная внутри другой функции. Одним из распространенных применений внутренних функций является создание функций фабрик:

def create_multiplier(factor):
    def multiplier(num):
        return factor * num
    return multiplier

multiplier() является внутренней функцией, определенной внутри create_multiplier(). Обратите внимание, что у вас есть доступ к factor внутри multiplier(), в то время как multiplier() не определен вне create_multiplier():

>>> multiplier
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'multiplier' is not defined

Вместо этого create_multiplier() используется для создания новых функций умножения, каждая из которых основана на различном factor:

>>> double = create_multiplier(factor=2)
>>> double(3)
6

>>> quadruple = create_multiplier(factor=4)
>>> quadruple(7)
28

Точно так же вы можете использовать внутренние функции для создания декораторов. Помните, декоратор — это функция, которая возвращает функцию:

def triple(func): #1

    def wrapper_triple(*args, **kwargs): #2
        print(f"Tripled {func.__name__!r}")
        value = func(*args, **kwargs)
     return value * 3

return wrapper_triple #6

triple() является декоратором, потому что это функция, которая ожидает функцию как единственный аргумент, func(), и возвращает другую функцию, wrapper_triple(). Обратите внимание на структуру самого triple():

  • Строка #1 запускает определение triple() и ожидает функцию в качестве аргумента.
  • Строка #2 определяет внутреннюю функцию wrapper_triple().
  • Строка #6 возвращает wrapper_triple().

Шаблон распространен для определения декораторов. Интересные части — это те, что происходят внутри внутренней функции:

  • Строка 2 запускает определение wrapper_triple(). Эта функция заменит ту, которую выполняет функция triple(). Параметры *args и **kwargs, которые собирают позиционные и ключевые аргументы, которые вы передаете функции. Это дает вам гибкость в использовании triple() для любой функции.
  • В строке 3 выводится имя оформленной функции, и обратите внимание, что к ней применен triple().
  • Строка 4 вызывает func(), функцию, которая была оформлена с помощью triple(). Он передает все аргументы, передаваемые в wrapper_triple().
  • Строка 5 утраивает возвращаемое значение func() и возвращает его.

Давайте проверим это! knock() — это функция, которая возвращает слово World. Посмотрите, что произойдет, если оно утроится:

>>> def knock():
...     return "World! "
...
>>> knock = triple(knock)
>>> result = knock()
Tripled 'knock'

>>> result
'World! World! World! '

Умножение текстовой строки на число является формой повторения, поэтому World повторяется три раза. Декорирование происходит при knock = triple(knock).

Код кажется немного неуклюжим, чтобы продолжать повторять knock. Вместо этого в PEP 318 введен более удобный синтаксис для применения декораторов. Следующее определение knock() делает то же самое, что и выше:

>>> @triple
... def knock():
...     return "World! "
...
>>> result = knock()
Tripled 'knock'

>>> result
'World! World! World! '

Символ @ используется для применения декораторов. В этом случае @triple означает, что triple() применяется к функции, определенной сразу после нее.

@functools.wraps — один из немногих декораторов, определенных в стандартной библиотеке. Это очень полезно при определении ваших собственных декораторов. Поскольку декораторы эффективно заменяют одну функцию другой, они создают тонкую проблему с вашими функциями:

>>> knock
<function triple.<locals>.wrapper_triple at 0x7fa3bfe5dd90>

@triple декорирует knock(), который затем заменяется внутренней функцией wrapper_triple(), как подтверждает вывод выше. Это также заменит имя, docstring и другие метаданные. Часто это не будет иметь большого эффекта, но может затруднить интроспекцию.

Иногда декорированные функции должны иметь правильные метаданные. @functools.wraps исправляют именно эту проблему:

import functools

def triple(func):
    @functools.wraps(func)
    def wrapper_triple(*args, **kwargs):
        print(f"Tripled {func.__name__!r}")
        value = func(*args, **kwargs)
        return value * 3
    return wrapper_triple

С новым определением @triple метаданные сохраняются:

>>> @triple
... def knock():
...     return "World! "
...
>>> knock
<function knock at 0x7fa3bfe5df28>

Обратите внимание, что knock() теперь сохраняет свое собственное имя, даже после того, как был декорирован.
Это хорошая форма, чтобы использовать @functools.wraps всякий раз, когда вы определяете декоратор. Схема,
которую вы можете использовать для большинства ваших декораторов, выглядит следующим образом:

import functools

def decorator(func):
    @functools.wraps(func)
    def wrapper_decorator(*args, **kwargs):
        # Сделать что-нибудь раньше
        value = func(*args, **kwargs)
        # Сделать что-нибудь после
        return value
    return wrapper_decorator

Создание декоратора Timer Python

В этом разделе вы узнаете, как расширить свой таймер Python, чтобы вы также могли использовать его в качестве декоратора. Однако в качестве первого упражнения давайте создадим Python декоратор Timer с нуля.

Основываясь на приведенной выше схеме, вам нужно только решить, что делать до и после вызова декорированной функции. Это похоже на соображения о том, что делать при входе и выходе из контекстного менеджера. Вы хотите запустить таймер Python перед вызовом декорированной функции и остановить таймер Python после завершения вызова. Декоратор @timer может быть определен следующим образом:

import functools
import time

def timer(func):
    @functools.wraps(func)
    def wrapper_timer(*args, **kwargs):
        tic = time.perf_counter()
        value = func(*args, **kwargs)
        toc = time.perf_counter()
        elapsed_time = toc - tic
        print(f"Elapsed time: {elapsed_time:0.4f} seconds")
        return value
    return wrapper_timer

Обратите внимание, насколько wrapper_timer() напоминает ранний шаблон, установленный вами для замеров кода Python. Вы можете применить @timer следующим образом:

>>> @timer
... def thousandth_element():
...     result = fibo(1000)
...     print(result)
...
>>> thousandth_element()
# Python Timer Functions: Вычислить 1000 элемент последовательности

[ ... Результат вычисления ... ]
Elapsed time: 0.5414 seconds

Напомним, что вы также можете применить декоратор к ранее определенной функции:

>>> fibo = timer(fibo)

Поскольку @ применяется при определении функций, в этих случаях необходимо использовать более простую форму.
Одно из преимуществ использования декоратора заключается в том, что вам нужно применить его только один раз,
и он будет каждый раз определять время выполнения функции:

>>> result = fibo(1000)
Elapsed time: 0.5512 seconds

@timer делает свою работу. Тем не менее, в некотором смысле вы вернулись к исходной точке, поскольку @timer не
обладает никакой гибкостью или удобством Timer. Можете ли вы также заставить свой класс Timer действовать как декоратор?

До сих пор вы использовали декораторы как функции, применяемые к другим функциям, но это не совсем правильно.
Декораторы должны быть вызываемыми. В Python есть много вызываемых типов. Вы можете сделать свои собственные объекты
вызываемыми, определив специальный объект метод .__call__() в своем классе. Аналогично ведут себя следующие функции и классы:

>>> def square(num):
...     return num ** 2
...
>>> square(4)
16

>>> class Squarer:
...     def __call__(self, num):
...         return num ** 2
...
>>> square = Squarer()
>>> square(4)
16

Здесь square-это экземпляр, который может быть вызван и может содержать квадрат числа, точно так же, как функция square() в первом примере.

Это дает вам возможность добавить возможности декоратора к существующему классу таймера:

def __call__(self, func):
    """Поддержка использования Timer в качестве декоратора"""
    @functools.wraps(func)
    def wrapper_timer(*args, **kwargs):
        with self:
            return func(*args, **kwargs)

    return wrapper_timer

.__call__() использует тот факт, что Timer уже является контекстным менеджером, чтобы воспользоваться преимуществами, которые вы уже определили там. Убедитесь, что вы также импортируете functools в верхней части timer.py.

Теперь вы можете использовать Timer в качестве декоратора:

>>> @Timer(text="Вычисление 1000 элемента за {:.4f} секунд")
... def thousandth_element():
...     result = fibo(1000)
...     print(result)
...
>>> thousandth_element()

[ ... Результат вычисления ... ]
Вычисление 1000 элемента за 0.004 секунд

Прежде чем завершить этот раздел, знайте, что есть более простой способ превратить ваш таймер Python в декоратор. Вы уже видели некоторые сходства между контекстными менеджерами и декораторами. Они оба обычно используются для выполнения чего-то до и после выполнения некоторого заданного кода.

Основываясь на этих сходствах, в стандартной библиотеке есть класс mixin, называемый ContextDecorator. Вы можете добавить способности декоратора в свои классы контекстного менеджера просто унаследовав ContextDecorator:

from contextlib import ContextDecorator

class Timer(ContextDecorator):
    # Реализация Timer не изменилась

Когда вы используете ContextDecorator таким образом, нет необходимости реализовывать его.__call__() самостоятельно, чтобы вы могли безопасно удалить его из класса Timer.

Использование декоратора таймера Python

Давайте повторим series_number.py, используя таймер Python в качестве декоратора:

# series_number.py

from timer import Timer
from fibo import fibo


@Timer()
def main():
    """Печать 1000 элемента последовательности Фибоначчи"""
    result = fibo(1000)
    print(result)

if __name__ == "__main__":
    main()

Если вы сравните эту реализацию с оригинальной реализацией без какого-либо времени, то заметите, что единственными различиями
являются импорт Timer в строке 3 и применение @Timer() в строке 6. Существенным преимуществом использования декораторов
является то, что они обычно просты в применении, как вы видите.

Тем не менее, декоратор по-прежнему относится ко всей функции. Это означает, что ваш код учитывает время, необходимое для печати результата. Давайте запустим сценарий в последний раз:

$ python series_number.py

[ ... Результат вычисления ... ]
Elapsed time: 0.0069 seconds

Расположение выходных данных прошедшего времени является предательским признаком того, что ваш код также учитывает время, необходимое для печати времени. Как вы видите здесь, ваш код печатает прошедшее время после вычислений.

При использовании таймера в качестве декоратора вы увидите те же преимущества, что и при использовании контекстных менеджеров:

  • Малое усилие: вам нужна только одна дополнительная строка кода, чтобы определить время выполнения функции.
  • Читабельность: когда вы добавляете декоратор, вы можете более четко отметить, что ваш код будет определять время выполнения функции.
  • Согласованность: вам нужно добавить декоратор только тогда, когда функция определена. Ваш код будет последовательно отсчитывать время при каждом вызове.

Однако декораторы не так гибки, как контекстные менеджеры. Вы можете применять их только для выполнения функций. Можно добавить декораторы к уже определенным функциям, но это немного неуклюже и менее распространено.

Код Timer Python

Вы можете использовать код самостоятельно, сохранив его в файле с именем timer.py и импортировать его в вашу программу. Запустим новый таймер в качестве менеджера контекста:

Кроме этого codetiming.Timer работает точно так же, как timer.Timer. Подводя итог, можно использовать Timer тремя различными способами:

  1. В виде класса:
t = Timer(name="class")
t.start()
# Сделать что-нибудь
t.stop()
  1. В виде контекстного менеджера:
with Timer(name="context manager"):
   # Сделать что-нибудь

  1. В виде декоратора:
@Timer(name="decorator")
def stuff():
   # Сделать что-нибудь

Этот вид таймера Python в основном полезен для мониторинга времени, которое ваш код тратит на отдельные ключевые
блоки кода или функции. В следующем разделе вы получите краткий обзор альтернатив,
которые можно использовать, если вы хотите оптимизировать свой код.

Другие функции таймеры в Python

Существует множество вариантов замеров выполнения вашего кода Python. В этом уроке вы узнаете, как создать гибкий и удобный класс, который можно использовать несколькими различными способами. Быстрый поиск по PyPI показывает, что уже существует множество проектов, предлагающих решения тайминга Python.

В этом разделе вы сначала узнаете больше о различных функциях, доступных в стандартной библиотеке для измерения времени, и о том, почему perf_counter() предпочтительнее. Затем вы увидите альтернативы оптимизации вашего кода, для которых таймер не очень хорошо подходит.

Использование альтернативных функций таймеров в Python

Вы использовали perf_counter() на протяжении всего этого урока для выполнения фактических измерений времени, но библиотека time Python поставляется с несколькими другими функциями, которые также измеряют время. Вот некоторые альтернативы:

  • time()
  • perf_counter_ns()
  • monotonic()
  • process_time()

Одна из причин, почему существует несколько функций, заключается в том, что Python представляет время как float.
Числа с плавающей запятой по своей природе неточны. Возможно, вы уже видели подобные результаты раньше:

>>> 0.1 + 0.1 + 0.1
0.30000000000000004

>>> 0.1 + 0.1 + 0.1 == 0.3
False

Float Python следует стандарту IEEE 754 для арифметики с плавающей запятой, который пытается представить все числа
с плавающей запятой в 64 битах. Поскольку существует бесконечно много чисел с плавающей запятой, вы не можете выразить их в виде конечного числа битов.

IEEE 754 предписывает систему, в которой плотность чисел, которые вы можете представить, изменяется.
Чем ближе вы к 1, тем больше чисел вы можете представить. Для больших чисел есть больше пространства между числами,
которые вы можете выразить. Это имеет некоторые последствия, когда вы используете float для представления времени.

Рассмотрим time(). Основная цель этой функции-представить реальное время прямо сейчас. Он делает это как число секунд,
прошедших с данного момента времени, называемого эпохой. Число, возвращаемое функцией time(), довольно велико,
а это означает, что доступных чисел становится меньше, и разрешение страдает. В частности, time() не
способен измерять наносекундные различия:

>>> import time
>>> t = time.time()
>>> t
1564342757.0654016

>>> t + 1e-9
1564342757.0654016

>>> t == t + 1e-9
True

Наносекунда — это одна миллиардная секунды. Обратите внимание, что добавление наносекунды к t не влияет на результат.
perf_counter(), с другой стороны, использует некоторый неопределенный момент времени в качестве своей эпохи,
что позволяет ему работать с меньшими числами и, следовательно, получать лучшее разрешение:

>>> import time
>>> p = time.perf_counter()
>>> p
11370.015653846

>>> p + 1e-9
11370.015653847

>>> p == p + 1e-9
False

Здесь вы видите, что добавление наносекундного числа на самом деле влияет на результат.

Проблемы с представлением времени в виде float хорошо известны, поэтому Python 3.7 ввел новую опцию.
Каждая функция измерения времени теперь имеет соответствующую функцию _ns, которая возвращает число
наносекунд в виде int вместо числа секунд в виде float. Например, time() теперь имеет наносекундный аналог time_ns():

>>> import time
>>> time.time_ns()
1564342792866601283

Целые числа в Python не ограничены, поэтому это позволяет time_ns() давать разрешение наносекунды на всю вечность. Аналогично, perf_counter_ns() — это наносекундный вариант perf_counter():

>>> import time
>>> time.perf_counter()
13580.153084446

>>> time.perf_counter_ns()
13580765666638

Поскольку perf_counter() уже обеспечивает наносекундное разрешение, у использования perf_counter() меньше преимуществ.

Примечание: perf_counter_ns() доступен только в Python 3.7 и более поздних версиях. В этом уроке вы использовали perf_counter() в своем классе Timer. Таким образом, таймер можно использовать и в более старых версиях Python.
Для получения дополнительной информации о функциях _ns в time ознакомьтесь с новыми классными функциями в Python 3.

Есть две функции во time, которые не измеряют время, проведенное во сне (sleeping). Это process_time() и thread_time(), которые полезны в некоторых настройках. Однако для таймера обычно требуется измерить полное затраченное время. Последняя функция в приведенном выше monotonic(). Название намекает на то, что эта функция является монотонным таймером, который является таймером Python, который никогда не может двигаться назад.

Все эти функции монотонны, за исключением time(), который может вернуться назад, если системное время отрегулировано. В некоторых системах monotonic() — это та же функция, что и perf_counter(), и вы можете использовать их взаимозаменяемо. Однако это не всегда так. Вы можете использовать time.get_clock_info() для получения дополнительной информации о функции таймера Python. Используя Python 3.7 В Linux я получаю следующую информацию:

>>> import time
>>> time.get_clock_info("monotonic")
namespace(adjustable=False, implementation='clock_gettime(CLOCK_MONOTONIC)',
          monotonic=True, resolution=1e-09)

>>> time.get_clock_info("perf_counter")
namespace(adjustable=False, implementation='clock_gettime(CLOCK_MONOTONIC)',
          monotonic=True, resolution=1e-09)

Результаты могут быть разными в вашей системе.

PIP 418 описывает некоторые обоснования введения этих функций. Она включает в себя следующие краткие описания:

  • time.monotonic(): тайм-аут и планирование, на которые не влияют обновления системных часов
  • time.perf_counter(): бенчмаркинг, самые точные часы за короткий период
  • time.process_time(): профилирование, процессорное время процесса

Как вы можете видеть, обычно это лучший выбор для вас, чтобы использовать perf_counter() для вашего таймера Python.

Оценка времени работы со временем timeit

Допустим, нужно выжать из кода последний бит производительности и задаетесь вопросом о наиболее эффективном
способе преобразования списка в множество. Вы хотите сравнить, использование set() и литерал
множества {…}. Для этого вы можете использовать свой таймер Python:

Этот тест, по-видимому, указывает на то, что литерал множества может быть немного быстрее.
Однако эти результаты довольно неопределенные, и если вы повторно запустите код, можно получить
совершенно другие результаты. Это потому, что вы только один раз пробуете код. Например,
вам может не повезти, и вы можете запустить сценарий как раз в тот момент, когда ваш
компьютер будет занят другими задачами.

Лучше всего воспользоваться стандартной библиотекой. Она предназначен именно для измерения времени
выполнения небольших фрагментов кода. Для этого импортируем и вызовем timeit.timeit() из
Python как обычную функцию в интерфейсе командной строки. Вы можете рассчитать эти два варианта следующим образом:

$ python -m timeit --setup "nums = [7, 6, 1, 4, 1, 8, 0, 6]" "set(nums)"
2000000 loops, best of 5: 163 nsec per loop

$ python -m timeit --setup "nums = [7, 6, 1, 4, 1, 8, 0, 6]" "{*nums}"
2000000 loops, best of 5: 121 nsec per loop

timeit он автоматически вызывает ваш код много раз, чтобы усреднить шумные измерения. Результаты timeit подтверждают, что литерал set работает быстрее, чем set().

Примечание: будьте осторожны, когда вы используете timeit на коде, который может загружать файлы или получать доступ к базам данных. Поскольку время от времени он автоматически вызывает вашу программу несколько раз, вы можете непреднамеренно в конечном итоге заспамить сервер запросами!

Наконец, интерактивная оболочка IPython и Jupyter notebook имеют дополнительную поддержку этой функции с помощью команды %timeit magic:

In [1]: numbers = [7, 6, 1, 4, 1, 8, 0, 6]

In [2]: %timeit set(numbers)
171 ns ± 0.748 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [3]: %timeit {*numbers}
147 ns ± 2.62 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Опять же, измерения показывают, что использование литерала множества происходит быстрее.

Поиск узких мест в коде с помощью профилирования

timeit отлично подходит для бенчмаркинга конкретного фрагмента кода. Однако было бы очень громоздко использовать его для проверки всех частей вашей программы и определения того, какие разделы занимают больше всего времени. Вместо этого можно использовать профилировщик.

cProfile — это профилировщик, к которому вы можете получить доступ в любое время из стандартной библиотеки. Вы можете использовать его несколькими способами, хотя обычно проще всего использовать его в качестве инструмента командной строки:

$ python -m cProfile -o series_number.prof series_number.py

Команда выполняет series_number.py с включенным профилированием. Вы сохраняете выходные данные из cProfile в series_number.prof как указано в параметре -o. Выходные данные находятся в двоичном формате, который нуждается в специальной программе, чтобы понять его. Опять же, Python содержит эту опцию прямо в стандартной библиотеке! Запуск модуля pstats на вашем компьютере с .prof файлом, открывает интерактивный браузер статистики профиля:

$ python -m pstats series_number.prof
Welcome to the profile statistics browser.
series_number.prof% help

Documented commands (type help <topic>):
========================================
EOF  add  callees  callers  help  quit  read  reverse  sort  stats  strip

Чтобы использовать pstats, вы вводите команды в командной строке. Здесь вы можете увидеть интегрированную справочную систему. Обычно используются команды sort и stats. Чтобы получить более очищенный вывод, может быть полезна strip:

series_number.prof% strip
series_number.prof% sort cumtime
series_number.prof% stats 10
Sun Jun 28 23:02:00 2020    series_number.prof

         165 function calls (164 primitive calls) in 0.002 seconds

   Ordered by: cumulative time
   List reduced from 85 to 10 due to restriction <10>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
      2/1    0.000    0.000    0.002    0.002 {built-in method builtins.exec}
        1    0.000    0.000    0.002    0.002 series_number.py:3(<module>)
        1    0.000    0.000    0.001    0.001 series_number.py:7(main)
        1    0.000    0.000    0.001    0.001 <frozen importlib._bootstrap>:986(_find_and_load)
        1    0.000    0.000    0.001    0.001 <frozen importlib._bootstrap>:956(_find_and_load_unlocked)
        1    0.001    0.001    0.001    0.001 fibo.py:1(fibo)
        2    0.001    0.000    0.001    0.000 {built-in method builtins.print}
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:650(_load_unlocked)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:777(exec_module)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap>:890(_find_spec)

Этот вывод показывает, что общее время выполнения составило 0.002 секунды. В нем также перечислены десять функций, на которые ваш код потратил большую часть своего времени. Здесь вы отсортированы по кумулятивному времени (cumtime), что означает, что ваш код считает время, когда данная функция вызвала другую функцию.

Вы можете видеть, что ваш код проводит практически все свое время внутри модуля series_number, и в частности, внутри fibo(). Хотя это может быть полезным подтверждением того, что вы уже знаете, часто гораздо интереснее узнать, где ваш код на самом деле проводит время.

Столбец общее время (tottime) показывает, сколько времени ваш код провел внутри функции, исключая время в подфункциях. Вы можете видеть, что ни одна из вышеперечисленных функций на самом деле не тратит на это никакого времени. Чтобы найти, где код провел большую часть своего времени, выполните другую команду sort:

Welcome to the profile statistics browser.
series_number.prof% sort tottime
series_number.prof% stats 10
Sun Jun 28 23:21:52 2020    series_number.prof

         165 function calls (164 primitive calls) in 0.002 seconds

   Ordered by: internal time
   List reduced from 85 to 10 due to restriction <10>

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.001    0.001    0.001    0.001 C:Python38workfibo.py:1(fibo)
        2    0.001    0.000    0.001    0.000 {built-in method builtins.print}
        3    0.000    0.000    0.000    0.000 {built-in method nt.stat}
        1    0.000    0.000    0.000    0.000 {built-in method io.open_code}
        1    0.000    0.000    0.000    0.000 {method 'read' of '_io.BufferedReader' objects}
        1    0.000    0.000    0.001    0.001 series_number.py:7(main)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:969(get_data)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:578(_compile_bytecode)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:849(get_code)
        1    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:1431(find_spec)

Теперь видно, что series_number.py фактически большую часть своего времени проводит работая с модулем fibo.py. Последнее является одной из зависимостей скрипта, который используется для вычисления.

Вы можете использовать статистику, чтобы получить некоторое представление о том, где ваш код тратит большую часть своего времени, и посмотреть, сможете ли вы оптимизировать любые узкие места, которые вы найдете. Вы также можете использовать этот инструмент, чтобы лучше понять структуру вашего кода. Например, вызываемые и вызывающие команды покажут вам, какие функции вызывают и вызываются данной функцией.

Вы также можете исследовать некоторые функции. Давайте посмотрим, сколько накладных расходов вызывает timer, фильтруя результаты с помощью фразы timer:

series_number.prof% stats timer
         1393801 function calls (1389027 primitive calls) in 0.586 seconds

 Ordered by: internal time
 List reduced from 1443 to 8 due to restriction <'timer'>

 ncalls tottime percall cumtime percall filename:lineno(function)
      1   0.000   0.000   0.000   0.000 timer.py:13(Timer)
      1   0.000   0.000   0.000   0.000 timer.py:35(stop)
      1   0.000   0.000   0.003   0.003 timer.py:3(<module>)
      1   0.000   0.000   0.000   0.000 timer.py:28(start)
      1   0.000   0.000   0.000   0.000 timer.py:9(TimerError)
      1   0.000   0.000   0.000   0.000 timer.py:23(__post_init__)
      1   0.000   0.000   0.000   0.000 timer.py:57(__exit__)
      1   0.000   0.000   0.000   0.000 timer.py:52(__enter__)

К счастью, таймер вызывает только минимальные накладные расходы. Используйте quit, чтобы выйти из браузера pstats, когда вы закончите исследование.

Для получения более мощного интерфейса для анализа данных профиля, запустите программу KCacheGrind. Он использует свой собственный формат данных, но вы можете конвертировать данные из профиля с помощью pyprof2calltree:

$ pyprof2calltree -k -i series_number.prof

Команда преобразует series_number.prof, затем открывая программу для анализа данных.

Последний вариант, который вы увидите здесь для замеров вашего кода — это line_profiler.cProfile может сказать вам, какие функции ваш код тратит больше всего времени, но он не даст вам понять, какие строки внутри этой функции являются самыми медленными. Вот где line_profiler может вам помочь.

Примечание: Вы также можете профилировать потребление памяти вашего кода. Это выходит за рамки данного руководства. Однако вы можете взглянуть на memory-profiler, если вам нужно контролировать потребление памяти вашими программами.

Обратите внимание, что line_profiler требует времени и добавляет изрядную часть накладных расходов к вашей среде выполнения. Более стандартный рабочий процесс заключается в том, чтобы сначала использовать cProfile для определения того, какие функции нужно просмотреть, а затем запустить line_profiler для этих функций.line_profiler не является частью стандартной библиотеки, поэтому вы должны сначала следовать инструкциям по установке, чтобы настроить его.

Перед запуском профилировщика необходимо указать ему, какие функции следует профилировать. Это выполняется, добавлением декоратора @profile в свой исходный код. Например, для профилирования Timer.stop() вы добавляете следующее в timer.py:

@profile
def stop(self) -> float:
   # Остальная часть кода не изменилась

Обратите внимание, что вы нигде не импортируете profile. Вместо этого он автоматически добавляется в глобальное пространство имен при запуске профилировщика. Вы должны удалить строку, когда закончите профилирование. В противном случае вы получите NameError.

Затем запустим профилировщик, используя kernprof, который является частью пакета line_profiler:

$ kernprof -l series_number.py

Команда автоматически сохраняет данные профилировщика в файле series_number.py.lprof. Вы можете увидеть эти результаты, использу модуль line_profiler:

$ python -m line_profiler series_number.py.lprof
Timer unit: 1e-06 s

Total time: 1.6e-05 s
File: /home/user/timer.py
Function: stop at line 35

# Hits Time PrHit %Time Line Contents
=====================================
35                      @profile
36                      def stop(self) -> float:
37                          """Остановить таймер и сообщить истекшее время"""
38  1   1.0   1.0   6.2     if self._start_time is None:
39                              raise TimerError(f"Timer is not running. ...")
40
41                          # Calculate elapsed time
42  1   2.0   2.0  12.5     elapsed_time = time.perf_counter() - self._start_time
43  1   0.0   0.0   0.0     self._start_time = None
44
45                          # Report elapsed time
46  1   0.0   0.0   0.0     if self.logger:
47  1  11.0  11.0  68.8         self.logger(self.text.format(elapsed_time))
48  1   1.0   1.0   6.2     if self.name:
49  1   1.0   1.0   6.2         self.timers[self.name] += elapsed_time
50
51  1   0.0   0.0   0.0     return elapsed_time

Во-первых, обратите внимание, что единица времени в этом отчете составляет микросекунда (1e-06 с). Обычно наиболее доступное число для просмотра — это %Time, которое показывает процент от общего времени, которое ваш код проводит внутри функции в каждой строке. В этом примере вы можете видеть, что ваш код тратит почти 70% времени на строке 47, которая является строкой, которая форматирует и печатает результат таймера.

Вывод

В этом руководстве вы увидели несколько разных подходов к добавлению таймера Python в свой код:

  • Вы использовали класс, чтобы сохранить состояние и добавить удобный интерфейс. Классы очень гибкие, и использование Timer напрямую дает вам полный контроль над тем, как и когда вызывать таймер.
  • Вы использовали менеджер контекста для добавления функций в блок кода и, при необходимости, для последующей очистки. Менеджеры контекста просты в использовании, а добавление с помощью Timer() может помочь вам более четко различать код визуально.
  • Вы использовали декоратор, чтобы добавить поведение к функции. Декораторы лаконичны и убедительны, а использование @Timer() — это быстрый способ отслеживать время выполнения вашего кода.

Вы также поняли, почему при тестировании кода предпочитаете time.perf_counter(), а не time.time(), а также
какие другие альтернативы полезны при оптимизации кода.

Теперь вы можете добавить функции Timer Python в свой собственный код!
Отслеживание скорости выполнения вашей программы в журналах поможет вам отслеживать ваши сценарии.

Понравилась статья? Поделить с друзьями:
  • Как внести изменение реквизитов в договоре
  • Как изучать компании перед инвестированием
  • Как внести изменения в реквизиты контракта
  • Как использовать одноклассники для бизнеса
  • Как внести реквизиты карты сотрудника в 1с