程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

Take you to implement a timer in Python

編輯:Python

Catalog

Python timer

Python Timer Functions

Example

first Python timer

One Python Timer class

understand Python Class in

establish Python Timer class

Use Python Timer class

Add more convenience and flexibility

Timer improvement

summary

Although many data workers believe that Python Is an effective programming language , But pure Python Procedure ratio C、Rust and Java The corresponding program in the compiled language runs slower , To better monitor and optimize Python Program , Yunduo Jun will learn how to use  Python Timer to monitor the speed of the program , In order to improve code performance .

In order to better grasp Python Application of timer , We will also add the following information about Python class 、 Context manager and decorator Background knowledge of . Due to space limitation , The context manager and decorator are used to optimize Python timer , I will learn about it in the following articles , Not within the scope of this article .

Python timer

First , We add a to a piece of code Python timer To monitor its performance .

Python Timer Functions

Python Built in time[1] There are several functions in the module that can measure time :

monotonic()

perf_counter()

process_time()

time()

Python 3.7 Several new functions are introduced , Such as thread_time()[2], And all the above functions nanosecond edition , With _ns Suffix naming . for example ,perf_counter_ns() yes perf_counter() Nanosecond version of .

perf_counter() Returns the value of the performance counter ( In seconds ), That is, a clock with the highest available resolution to measure short duration .

First , Use perf_counter() Create a Python timer . Will put it with other Python Timer function for comparison , have a look  perf_counter()  The advantages of .

Example

Create a script , Define a short function : Download a set of data from Tsinghua cloud .

import requestsdef main():    source_url = 'https://cloud.tsinghua.edu.cn/d/e1ccfff39ad541908bae/files/?p=%2Fall_six_datasets.zip&dl=1'    headers = {'User-Agent': 'Mozilla/5.0'}    res = requests.get(source_url, headers=headers)    with open('dataset/datasets.zip', 'wb') as f:        f.write(res.content)if __name__=="__main__":    main()

We can use Python Timer to monitor the performance of the script .

first Python timer

Now use the function time.perf_counter() Function to create a timer , This is a very suitable counter for timing the performance of some code .

perf_counter() Measure time from an unspecified time ( In seconds ), This means that the return value of a single call to the function is useless . But when looking at perf_counter() The difference between two calls , You can calculate how many seconds have passed between calls .

>>> import time>>> time.perf_counter()394.540232282>>> time.perf_counter()  #  After a few seconds 413.31714087

In this example , Two calls  perf_counter()  Close to each other 19 second . This can be confirmed by calculating the difference between the two outputs :413.31714087 - 394.540232282 = 18.78.

Now we can put Python Timers are added to the sample code :

# download_data.pyimport requestsimport timedef main():    tic = time.perf_counter()    source_url = 'https://cloud.tsinghua.edu.cn/d/e1ccfff39ad541908bae/files/?p=%2Fall_six_datasets.zip&dl=1'    headers = {'User-Agent': 'Mozilla/5.0'}    res = requests.get(source_url, headers=headers)    with open('dataset/datasets.zip', 'wb') as f:        f.write(res.content)    toc = time.perf_counter()    print(f" This procedure takes time : {toc - tic:0.4f} seconds")if __name__=="__main__":    main()

Be careful perf_counter() Calculate the difference between the two calls to print the time spent running the whole program .

print() Function  f  The preceding string indicates that this is a  f-string , This is a convenient way to format text strings .:0.4f Is a format specifier , Representation number ,toc - tic Should be printed as a decimal number with four decimal places .

Run the program to see the elapsed time :

This procedure takes time : 0.026 seconds

It's that simple . Next, let's learn how to make Python The timer is wrapped in a class 、 A context manager and a decorator , This makes it more consistent and convenient to use timers .

One Python Timer class

Here we need at least one variable to store Python The state of the timer . Next we create a manual call with  perf_counter()  The same class , But more readable and consistent .

Create and update Timer class , Use this class to time your code in many different ways .

$ python -m pip install codetiming understand Python Class in

Class class Is the main building block of object-oriented programming . Class is essentially a template , You can use it to create object .

stay Python in , When you need to model things that need to track a specific state , Class is very useful . Generally speaking , A class is a collection of attributes , be called attribute , And behavior , be called Method .

establish Python Timer class

Class is useful for tracking state . stay Timer Class , Want to track when the timer starts and how much time has elapsed . about Timer The first implementation of the class , Will add a ._start_time Properties and .start() and .stop() Method . Add the following code to the file named  timer.py  In the file of :

# timer.pyimport timeclass TimerError(Exception):    """ A custom exception , Used for reporting Timer Class error """class Timer:    def __init__(self):        self._start_time = None    def start(self):        """Start a new timer"""        if self._start_time is not None:            raise TimerError(f"Timer is running. Use .stop() to stop it")        self._start_time = time.perf_counter()    def stop(self):        """Stop the timer, and report the elapsed time"""        if self._start_time is None:            raise TimerError(f"Timer is not running. Use .start() to start it")        elapsed_time = time.perf_counter() - self._start_time        self._start_time = None        print(f"Elapsed time: {elapsed_time:0.4f} seconds")

Here we need to spend some time looking at the code carefully , You will find something different .

So we define one TimerError Python class . The (Exception) Symbolic representation TimerError  Inherit From another name Exception Parent class of . Use this built-in class for error handling . There is no need to TimerError Add any properties or methods , But custom errors can be handled more flexibly Timer Internal problems .

Next customize Timer class . When created from a class or Instantiation When an object , The code calls special methods .__init__() Initialize instance . The first one defined here Timer In the version , Just initialize ._start_time attribute , Will use it to track Python The state of the timer , When the timer is not running, its value is None. After the timer runs , Use it to track the start time of the timer .

Be careful : ._start_time The first underline of (_) The prefix is Python Appointment . It said ._start_time yes Timer The user of a class should not manipulate the internal properties of .

When calling .start() Start a new Python Timer time , First, check whether the timer is running . And then perf_counter() The current value of is stored in ._start_time in .

On the other hand , When calling .stop() when , First check Python Whether the timer is running . If it is , Then the running time is calculated as perf_counter() The current value of is stored in ._start_time The difference between the values in . Last , Reset ._start_time, To restart the timer , And print the running time .

Here are the USES Timer Method :

from timer import Timert = Timer()t.start()#  After a few seconds t.stop()

Elapsed time: 3.8191 seconds

Use this example directly with the previous perf_counter() Compare with the example of . The structure of the code is similar , But now the code is clearer , This is one of the benefits of using classes . By carefully selecting classes 、 Method and property names , Can make your code very descriptive !

Use Python Timer class

Now? Timer Class download_data.py. Just make some changes to the previous code :

# download_data.pyimport requestsfrom timer import Timerdef main():    t = Timer()    t.start()    source_url = 'https://cloud.tsinghua.edu.cn/d/e1ccfff39ad541908bae/files/?p=%2Fall_six_datasets.zip&dl=1'    headers = {'User-Agent': 'Mozilla/5.0'}    res = requests.get(source_url, headers=headers)    with open('dataset/datasets.zip', 'wb') as f:        f.write(res.content)    t.stop()if __name__=="__main__":    main()

Be careful , This code is very similar to the code used before . In addition to making the code more readable ,Timer It is also responsible for printing the elapsed time to the console , Make the time record more consistent . When running code , The output is almost the same :

Elapsed time: 0.502 seconds
...

Print elapsed time Timer May be consistent , But this method seems not very flexible . Let's add something more flexible to the code .

Add more convenience and flexibility

up to now , We have learned that classes are suitable for situations where we want to encapsulate state and ensure code consistency . In this section , We are going to give Python Timers add more convenience and flexibility , How to do it ?

When reporting elapsed time , Use Adjustable text and formatting

take logging Print to console 、 Write to a log file or other part of the program

Create one that can be called multiple times Can accumulate Of Python timer

structure Python The timer Information representation

First , Customize the text used to report time . In the previous code , Text  f"Elapsed time: {elapsed_time:0.4f} seconds"  Hard coded into  .stop()  in . If you want to make class code more flexible ,  You can use instance variables , Its value is usually passed as a parameter to .__init__() And store it to  self  attribute . For convenience , We can also provide reasonable default values .

You want to add .text by Timer Instance variables , You can do the following timer.py

# timer.pydef __init__(self, text="Elapsed time: {:0.4f} seconds"):    self._start_time = None    self.text = text

Be careful , Default text "Elapsed time: {:0.4f} seconds" Is given as a regular string , instead of f-string. Not available here f-string, because f-string Will calculate immediately , When you instantiate Timer when , Your code hasn't figured out how much time it will take .

Be careful :  If you want to use f-string To specify the .text, You need to use double curly braces to escape the curly braces that will be replaced by the actual elapsed time .

Such as :f"Finished {task} in {{:0.4f}} seconds". If task The value of is "reading", So this f-string Will be counted as "Finished reading in {:0.4f} seconds".

stay .stop() in ,.text Use as a template and use .format() Method to populate the template :

# timer.pydef stop(self):    """Stop the timer, and report the elapsed time"""    if self._start_time is None:        raise TimerError(f"Timer is not running. Use .start() to start it")    elapsed_time = time.perf_counter() - self._start_time    self._start_time = None    print(self.text.format(elapsed_time))

Update here to timer.py after , You can change the text as follows :

from timer import Timert = Timer(text="You waited {:.1f} seconds")t.start()#  After a few seconds t.stop()

You waited 4.1 seconds

Next , We don't just want to print messages to the console , You also want to save the time measurement results , This makes it easy to store them in a database . You can do this by .stop() return elapsed_time To achieve this . then , The calling code can choose to ignore the return value or save it for later processing .

If you want to Timer Integration into logs logging in . To support logging or other output of timers , Need to change to print() Call to , So that users can provide their own logging functions . This can be done with text similar to what you customized before :

# timer.py# ...class Timer:    def __init__(        self,        text="Elapsed time: {:0.4f} seconds",        logger=print    ):        self._start_time = None        self.text = text        self.logger = logger    #  Other methods remain unchanged     def stop(self):        """Stop the timer, and report the elapsed time"""        if self._start_time is None:            raise TimerError(f"Timer is not running. Use .start() to start it")        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

Not directly print(), Instead, create another instance variable  self.logger, Reference a function that accepts a string as a parameter . besides , You can also use... For file objects logging.info() or .write() Such as function . Also pay attention to if in , It allows you to pass logger=None To turn off printing completely .

Here are two examples , It shows the practical application of the new function :

from timer import Timerimport loggingt = Timer(logger=logging.warning)t.start()#  After a few seconds t.stop()  # A few seconds later

WARNING:root:Elapsed time: 3.1610 seconds  
3.1609658249999484

t = Timer(logger=None)t.start()#  After a few seconds value = t.stop()value

4.710851433001153

The next third improvement is The ability to accumulate time measurements . for example , When a slow function is called in a loop , I hope to add more functions in the form of named timers , And use a dictionary to track every... In the code Python timer .

We expand download_data.py Script .

# download_data.pyimport requestsfrom timer import Timerdef main():    t = Timer()    t.start()    source_url = 'https://cloud.tsinghua.edu.cn/d/e1ccfff39ad541908bae/files/?p=%2Fall_six_datasets.zip&dl=1'    headers = {'User-Agent': 'Mozilla/5.0'}    for i in range(10):        res = requests.get(source_url, headers=headers)        with open('dataset/datasets.zip', 'wb') as f:            f.write(res.content)    t.stop()if __name__=="__main__":    main()

A subtle problem with this code is , Not only does it take time to download data , Also measure Python Time spent storing data to disk . This may not be important , Sometimes the time taken for both can be neglected . But I still hope there is a way to accurately time every step , It will be better .

There are several ways to do this without changing Timer Solve this problem with the current implementation , And it only needs a few lines of code to realize .

First , Will introduce a named .timers As a dictionary of Timer Class variables , here Timer All instances of will share it . Implement it by defining it outside of any method :

class Timer:    timers = {}

Class variables can be accessed directly on the class , You can also access... Through an instance of a class :

>>> from timer import Timer>>> Timer.timers{}>>> t = Timer()>>> t.timers{}>>> Timer.timers is t.timersTrue

In both cases , The code returns the same empty class Dictionary .

Next to Python Add an optional name to the timer . This name can be used for two different purposes :

In the code lookup Time passed

Add up Timer with the same name

Want to Python Timer add name , Need to be right  timer.py  Make changes . First ,Timer Accept name Parameters . second , When the timer stops , The runtime should be added to  .timers  in :

# timer.py# ...class Timer:    timers = {}    def __init__(        self,        name=None,        text="Elapsed time: {:0.4f} seconds",        logger=print,    ):        self._start_time = None        self.name = name        self.text = text        self.logger = logger        #  Add a new named timer to the timer Dictionary         if name:            self.timers.setdefault(name, 0)    #  Other methods remain unchanged     def stop(self):        """Stop the timer, and report the elapsed time"""        if self._start_time is None:            raise TimerError(f"Timer is not running. Use .start() to start it")        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

Be careful , In the .timers Add a new Python Timer time , Used .setdefault() Method . It is not defined in the dictionary name Set the value in the case of , If name Already in .timers Use in , Then the value will remain unchanged , At this time, several timers can be accumulated :

>>> from timer import Timer>>> t = Timer("accumulate")>>> t.start()>>> t.stop()  # A few seconds laterElapsed time: 3.7036 seconds3.703554293999332>>> t.start()>>> t.stop()  # A few seconds laterElapsed time: 2.3449 seconds2.3448921170001995>>> Timer.timers{'accumulate': 6.0484464109995315}

You can now revisit download_data.py And ensure that only the time spent downloading data is measured :

# download_data.pyimport requestsfrom timer import Timerdef main():    t = Timer("download", logger=None)    source_url = 'https://cloud.tsinghua.edu.cn/d/e1ccfff39ad541908bae/files/?p=%2Fall_six_datasets.zip&dl=1'    headers = {'User-Agent': 'Mozilla/5.0'}    for i in range(10):        t.start()        res = requests.get(source_url, headers=headers)        t.stop()        with open('dataset/datasets.zip', 'wb') as f:            f.write(res.content)    download_time = Timer.timers["download"]    print(f"Downloaded 10 dataset in {download_time:0.2f} seconds")if __name__=="__main__":    main()

Now you have a very concise version ,Timer It is consistent 、 flexible 、 Convenient and informative ! You can also apply many of the improvements made in this section to other types of classes in your project .

Timer improvement

The last improvement Timer, Make it more informative when used interactively . The following operation is to instantiate a timer class , And view its information :

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

The last line is Python The default way to represent an object . The information we see from this result , Not very clear , We will improve it next .

Here is an introduction  dataclasses  class , This class is only contained in Python 3.7 And later .

pip install dataclasses

have access to @dataclass The decorator will Python The timer is converted to a data class

# timer.pyimport timefrom dataclasses import dataclass, fieldfrom typing import Any, ClassVar# [email protected] Timer:    timers: ClassVar = {}    name: Any = None    text: Any = "Elapsed time: {:0.4f} seconds"    logger: Any = print    _start_time: Any = field(default=None, init=False, repr=False)    def __post_init__(self):        """Initialization: add timer to dict of timers"""        if self.name:            self.timers.setdefault(self.name, 0)    #  The rest of the code remains the same

This code replaces the previous  .__init__()  Method . Notice how the data class uses a syntax similar to the class variable syntax you saw earlier for defining all variables . in fact ,.__init__() It is automatically created for the data class according to the annotation variables in the class definition .

If you need to annotate variables to use data classes . You can use this annotation to add type hints to your code . If you don't want to use type hints , Then you can use Any To annotate all variables . We will soon learn how to add actual type hints to our data classes .

The following is about Timer Some considerations for data classes :

The first 6 That's ok :@dataclass The decorator will  Timer  Defined as a data class .

The first 8 That's ok : Data classes require special ClassVar Comment to specify  .timers  It's a class variable .

The first 9 To 11 That's ok :.name.text  and  .logger  Will be defined as Timer Properties on , You can create Timer Instance to specify its value . They all have given default values .

The first 12 That's ok : Think about it  ._start_time  It's a special property , For tracking Python The state of the timer , But it should be hidden from users . Use  dataclasses.field(), ._start_time  It should be from  .__init__()  and Timer Deleted from the representation of .

In addition to setting instance properties , You can use special  .__post_init__()  Method to initialize . Use it here to add a named timer to  .timers.

new Timer The data class uses the same functions as the previous regular class , But it now has a very good Information representation

from timer import Timert = Timer()tTimer(name=None, text='Elapsed time: {:0.4f} seconds', logger=<built-in function print>)t.start()#  After a few seconds t.stop()

Elapsed time: 6.7197 seconds
6.719705373998295

summary

Now we have a very simple Timer edition , It is consistent 、 flexible 、 Convenient and informative ! We can also apply many of the improvements made in this article to other types of classes in the project .

Now let's access the current full source code Timer. You'll notice that type hints have been added to the code to get additional documentation :

# timer.pyfrom dataclasses import dataclass, fieldimport timefrom typing import Callable, ClassVar, Dict, Optionalclass TimerError(Exception):    """A custom exception used to report errors in use of Timer class"""@dataclassclass Timer:    timers: ClassVar[Dict[str, float]] = {}    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:        """Add timer to dict of timers after initialization"""        if self.name is not None:            self.timers.setdefault(self.name, 0)    def start(self) -> None:        """Start a new timer"""        if self._start_time is not None:            raise TimerError(f"Timer is running. Use .stop() to stop it")        self._start_time = time.perf_counter()    def stop(self) -> float:        """Stop the timer, and report the elapsed time"""        if self._start_time is None:            raise TimerError(f"Timer is not running. Use .start() to start it")        # Calculate elapsed time        elapsed_time = time.perf_counter() - self._start_time        self._start_time = None        # Report elapsed time        if self.logger:            self.logger(self.text.format(elapsed_time))        if self.name:            self.timers[self.name] += elapsed_time        return elapsed_time

Sum up :  Use class to create Python Timers have several advantages :

Readability : Carefully select class and method names , Your code will read more naturally .

Uniformity : Encapsulate properties and behaviors into properties and methods , Your code will be easier to use .

flexibility : Use attributes that have default values instead of hard coded values , Your code will be reusable .

This class is very flexible , It can be used in almost any situation where you need to monitor code runtime . however , In the next section , Yunduo Jun will learn how to use the context manager and decorator , This makes it easier to time code blocks and functions .

The above is what you can do with your hands Python Implementation of a timer details , More about Python For information about timers, please pay attention to other relevant articles on the software development network !



  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved