精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

手把手教你用裝飾器擴展 Python 計時器

開發(fā) 前端
在本文中,云朵君將和大家一起了解裝飾器的工作原理,如何將我們之前定義的定時器類 Timer 擴展為裝飾器,以及如何簡化計時功能。最后對 Python 定時器系列文章做個小結。

這是我們手把手教你實現(xiàn) Python 定時器的第三篇文章。前兩篇:分別是手把手教你實現(xiàn)一個 Python 計時器?,和用上下文管理器擴展 Python 計時器?,使得我們的 Timer 類方便用、美觀實用。

但我們并不滿足于此,仍然有一個用例可以進一步簡化它。假設我們需要跟蹤代碼庫中一個給定函數(shù)所花費的時間。使用上下文管理器,基本上有兩種不同的選擇:

1. 每次調用函數(shù)時使用 ?Timer:

with Timer("some_name"):
do_something()

當我們在一個py文件里多次調用函數(shù) do_something(),那么這將會變得非常繁瑣并且難以維護。

2. 將代碼包裝在上下文管理器中的函數(shù)中:

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

Timer? 只需要在一個地方添加,但這會為do_something()的整個定義增加一個縮進級別。

更好的解決方案是使用 Timer 作為裝飾器。裝飾器是用于修改函數(shù)和類行為的強大構造。

理解 Python 中的裝飾器

裝飾器是包裝另一個函數(shù)以修改其行為的函數(shù)。你可能會有疑問,這怎么實現(xiàn)呢?其實函數(shù)是 Python 中的first-class 對象,換句話說,函數(shù)可以以變量的形式傳遞給其他函數(shù)的參數(shù),就像任何其他常規(guī)對象一樣。因此此處有較大的靈活性,也是 Python 幾個最強大功能的基礎。

我們首先創(chuàng)建第一個示例,一個什么都不做的裝飾器:

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

首先注意這個turn_off()?只是一個常規(guī)函數(shù)。之所以成為裝飾器,是因為它將一個函數(shù)作為其唯一參數(shù)并返回另一個函數(shù)。我們可以使用turn_off()來修改其他函數(shù),例如:

>>> print("Hello")
Hello

>>> print = turn_off(print)
>>> print("Hush")
>>> # Nothing is printed

代碼行 print = turn_off(print)? 用 turn_off()? 裝飾器裝飾了 print? 語句。實際上,它將函數(shù) print()? 替換為匿名函數(shù) lambda *args, **kwargs: None? 并返回 turn_off()。匿名函數(shù) lambda 除了返回 None 之外什么都不做。

要定義更多豐富的裝飾器,需要了解內部函數(shù)。內部函數(shù)是在另一個函數(shù)內部定義的函數(shù),它的一種常見用途是創(chuàng)建函數(shù)工廠:

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

multiplier()? 是一個內部函數(shù),在 create_multiplier()? 內部定義。注意可以訪問 multiplier()? 內部的因子,而 multiplier()?未在 create_multiplier() 外部定義:

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

相反,可以使用create_multiplier()創(chuàng)建新的 multiplier 函數(shù),每個函數(shù)都基于不同的參數(shù)factor:

double = create_multiplier(factor=2)
double(3)
6
quadruple = create_multiplier(factor=4)
quadruple(7)
28

同樣,可以使用內部函數(shù)來創(chuàng)建裝飾器。裝飾器是一個返回函數(shù)的函數(shù):

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

triple() ?是一個裝飾器,因為它是一個期望函數(shù) func()? 作為其唯一參數(shù)并返回另一個函數(shù) wrapper_triple()? 的函數(shù)。注意 triple() 本身的結構:

  • 第 1 行開始了triple() 的定義,并期望一個函數(shù)作為參數(shù)。
  • 第 2 到 5 行定義了內部函數(shù)wrapper_triple()。
  • 第 6 行返回wrapper_triple()。

這是種定義裝飾器的一般模式(注意內部函數(shù)的部分):

  • 第 2 行開始wrapper_triple()? 的定義。此函數(shù)將替換triple()? 修飾的任何函數(shù)。參數(shù)是*args? 和**kwargs?,用于收集傳遞給函數(shù)的任何位置參數(shù)和關鍵字參數(shù)。我們可以靈活地在任何函數(shù)上使用triple()。
  • 第 3 行打印出修飾函數(shù)的名稱,并指出已對其應用了triple()。
  • 第 4 行調用func()?,triple() ?修飾的函數(shù)。它傳遞傳遞給wrapper_triple() 的所有參數(shù)。
  • 第 5 行將func() 的返回值增加三倍并將其返回。

接下來的代碼中,knock() 是一個返回單詞 Penny 的函數(shù),將其傳給triple() 函數(shù),并看看輸出結果是什么。

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

>>> result
'Penny! Penny! Penny! '

我們都知道,文本字符串與數(shù)字相乘,是字符串的一種重復形式,因此字符串 'Penny' 重復了 3 次。可以認為,裝飾發(fā)生在knock = triple(knock)。

上述方法雖然實現(xiàn)了裝飾器的功能,但似乎有點笨拙。PEP 318 引入了一種更方便的語法來應用裝飾器。下面的 knock() 定義與上面的定義相同,但裝飾器用法不同。

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

>>> result
'Penny! Penny! Penny! '

@? 符號用于應用裝飾器,@triple? 表示 triple() 應用于緊隨其后定義的函數(shù)。

Python 標準庫中定義的裝飾器方法之一是:@functools.wraps。這在定義你自己的裝飾器時非常有用。前面說過,裝飾器是用另一個函數(shù)替換了一個函數(shù),會給你的函數(shù)帶來一個微妙的變化:

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

@triple? 裝飾了 knock()?,然后被 wrapper_triple()? 內部函數(shù)替換,被裝飾的函數(shù)的名字會變成裝飾器函數(shù),除了名稱,還有文檔字符串和其他元數(shù)據(jù)都將會被替換。但有時,我們并不總是想將被修飾的函數(shù)的所有信息都被修改了。此時 @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 的這個新定義保留元數(shù)據(jù):

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

注意knock()? 即使在被裝飾之后,也同樣保留了它的原有函數(shù)名稱。當定義裝飾器時,使用 @functools.wraps 是一種不錯的選擇,可以為大多數(shù)裝飾器使用的如下模板:

import functools

def decorator(func):
@functools.wraps(func)
def wrapper_decorator(*args, **kwargs):
# Do something before
value = func(*args, **kwargs)
# Do something after
return value
return wrapper_decorator

創(chuàng)建 Python 定時器裝飾器

在本節(jié)中,云朵君將和大家一起學習如何擴展 Python 計時器,并以裝飾器的形式使用它。接下來我們從頭開始創(chuàng)建 Python 計時器裝飾器。

根據(jù)上面的模板,我們只需要決定在調用裝飾函數(shù)之前和之后要做什么。這與進入和退出上下文管理器時的注意事項類似。在調用修飾函數(shù)之前啟動 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

可以按如下方式應用 @timer:

@timer
def download_data():
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)

download_data()
# Python Timer Functions: Three Ways to Monitor Your Code
[ ... ]
Elapsed time: 0.5414 seconds

回想一下,還可以將裝飾器應用于先前定義的下載數(shù)據(jù)的函數(shù):

requests.get = requests.get(source_url, headers=headers)

使用裝飾器的一個優(yōu)點是只需要應用一次,并且每次都會對函數(shù)計時:

data = requests.get(0)
Elapsed time: 0.5512 seconds

雖然@timer? 順利完成了對目標函數(shù)的定時。但從某種意義上說,你又回到了原點,因為該裝飾器 @timer? 失去了前面定義的類 ?Timer? 的靈活性或便利性。換句話說,我們需要將 ?Timer? 類表現(xiàn)得像一個裝飾器。

現(xiàn)在我們似乎已經將裝飾器用作應用于其他函數(shù)的函數(shù),但其實不然,因為裝飾器必須是可調用的。Python中有許多可調用的類型,可以通過在其類中定義特殊的.__call__()方法來使自己的對象可調用。以下函數(shù)和類的行為類似:

def square(num):
return num ** 2

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

square = Squarer()
square(4)
16

這里,square ?是一個可調用的實例,可以對數(shù)字求平方,就像square()第一個示例中的函數(shù)一樣。

我們現(xiàn)在向現(xiàn)有Timer類添加裝飾器功能,首先需要 import functools。

# timer.py
import functools
# ...
@dataclass
class Timer:
# The rest of the code is unchanged
def __call__(self, func):
"""Support using Timer as a decorator"""
@functools.wraps(func)
def wrapper_timer(*args, **kwargs):
with self:
return func(*args, **kwargs)
return wrapper_timer

在之前定義的上下文管理器 Timer ,給我們帶來了不少便利。而這里使用的裝飾器,似乎更加方便。

@Timer(text="Downloaded the tutorial in {:.2f} seconds")
def download_data():
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)

download_data()
# Python Timer Functions: Three Ways to Monitor Your Code
[ ... ]
Downloaded the tutorial in 0.72 seconds

有一種更直接的方法可以將 Python 計時器變成裝飾器。其實上下文管理器和裝飾器之間的一些相似之處:它們通常都用于在執(zhí)行某些給定代碼之前和之后執(zhí)行某些操作。

基于這些相似之處,在 python 標準庫中定義了一個名為 ContextDecorator? 的 mixin? 類,它可以簡單地通過繼承 ContextDecorator 來為上下文管理器類添加裝飾器函數(shù)。

from contextlib import ContextDecorator
# ...
@dataclass
class Timer(ContextDecorator):
# Implementation of Timer is unchanged

當以這種方式使用 ContextDecorator? 時,無需自己實現(xiàn) .__call__(),因此我們可以大膽地將其從 Timer 類中刪除。

使用 Python 定時器裝飾器

接下來,再最后一次重改 download_data.py 示例,使用 Python 計時器作為裝飾器:

# download_data.py
import requests
from timer import Timer
@Timer()
def 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()

我們與之前的寫法進行比較,唯一的區(qū)別是第 3 行的 Timer 的導入和第 4 行的 @Timer()  的應用。使用裝飾器的一個顯著優(yōu)勢是它們通常很容易調用。

但是,裝飾器仍然適用于整個函數(shù)。這意味著代碼除了記錄了下載數(shù)據(jù)所需的時間外,還考慮了保存數(shù)據(jù)所需的時間。運行腳本:

$ python download_data.py
# Python Timer Functions: Three Ways to Monitor Your Code
[ ... ]
Elapsed time: 0.69 seconds

從上面打印出來的結果可以看到,代碼記錄了下載數(shù)據(jù)和保持數(shù)據(jù)一共所需的時間。

當使用 Timer 作為裝飾器時,會看到與使用上下文管理器類似的優(yōu)勢:

  • 省時省力:只需要一行額外的代碼即可為函數(shù)的執(zhí)行計時。
  • 可讀性:當添加裝飾器時,可以更清楚地注意到代碼會對函數(shù)計時。
  • 一致性:只需要在定義函數(shù)時添加裝飾器即可。每次調用時,代碼都會始終如一地計時。

然而,裝飾器不如上下文管理器靈活,只能將它們應用于完整函數(shù)。

Python 計時器代碼

這里展開下面的代碼塊以查看 Python 計時器timer.py的完整源代碼。

上下滑動查看更多源碼
# timer.py
import time
from contextlib import ContextDecorator
from dataclasses import dataclass, field
from typing import Any, Callable, ClassVar, Dict, Optional

class TimerError(Exception):
"""A custom exception used to report errors in use of Timer class"""

@dataclass
class Timer(ContextDecorator):
"""Time your code using a class, context manager, or decorator"""

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:
"""Initialization: add timer to dict of timers"""
if self.name:
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

def __enter__(self) -> "Timer":
"""Start a new timer as a context manager"""
self.start()
return self

def __exit__(self, *exc_info: Any) -> None:
"""Stop the context manager timer"""
self.stop()

可以自己使用代碼,方法是將其保存到一個名為的文件中timer.py并將其導入:

from timer import Timer

PyPI 上也提供了 Timer,因此更簡單的選擇是使用 pip 安裝它:

pip install codetiming

注意,PyPI 上的包名稱是codetiming?,安裝包和導入時都需要使用此名稱Timer:

from codetiming import Timer

除了名稱和一些附加功能之外,codetiming.Timer? 與 timer.Timer? 完全一樣。總而言之,可以通過三種不同的方式使用 Timer:

1. 作為一個類:

t = Timer(name="class")
t.start()
# Do something
t.stop()

2. 作為上下文管理器:

with Timer(name="context manager"):
# Do something

3. 作為裝飾器:

@Timer(name="decorator")
def stuff():
# Do something

這種 Python 計時器主要用于監(jiān)控代碼在單個關鍵代碼塊或函數(shù)上所花費的時間。

Python定時器裝飾器已經學習完畢了,接下來是總結了一些其他的 Python 定時器函數(shù),如果你對其不太感興趣,可以直接跳到最后。

其他 Python 定時器函數(shù)

使用 Python 對代碼進行計時有很多選擇。這里我們學習了如何創(chuàng)建一個靈活方便的類,可以通過多種不同的方式使用該類。對 PyPI 的快速搜索發(fā)現(xiàn),已經有許多項目提供 Python 計時器解決方案。

在本節(jié)中,我們首先了解有關標準庫中用于測量時間的不同函數(shù)的更多信息,包括為什么 perf_counter() 更好,然后探索優(yōu)化代碼的替代方案。

使用替代 Python 計時器函數(shù)

在本文之前,包括前面介紹python定時器的文章中,我們一直在使用 perf_counter() 來進行實際的時間測量,但是 Python 的時間庫附帶了幾個其他也可以測量時間的函數(shù)。這里有一些:

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

擁有多個函數(shù)的一個原因是 Python 將時間表示為浮點數(shù)。浮點數(shù)本質上是不準確的。之前可能已經看到過這樣的結果:

>>> 0.1 + 0.1 + 0.1
0.30000000000000004

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

Python 的 Float 遵循 IEEE 754 浮點算術標準[5],該標準以 64 位表示所有浮點數(shù)。因為浮點數(shù)有無限多位數(shù),即不能用有限的位數(shù)來表達它們。

考慮time()?這個函數(shù)的主要目的,是它表示的是現(xiàn)在的實際時間。它以自給定時間點(稱為紀元)以來的秒數(shù)來表示函數(shù)。time()?返回的數(shù)字很大,這意味著可用的數(shù)字較少,因而分辨率會受到影響。簡而言之, time()無法測量納秒級差異:

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

>>> t + 1e-9
1564342757.0654016

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

一納秒是十億分之一秒。上面代碼中,將納秒添加到參數(shù) t ,他并不會影響結果。與 time() 不同的是,perf_counter() 使用一些未定義的時間點作為它的紀元,它可以使用更小的數(shù)字,從而獲得更好的分辨率:

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

>>> p + 1e-9
11370.015653847

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

眾所周知,將時間表示為浮點數(shù)是非常具有挑戰(zhàn)的一件事,因此 Python 3.7 引入了一個新選項:每個時間測量函數(shù)現(xiàn)在都有一個相應的 ?_ns? 函數(shù),它以 ?int? 形式返回納秒數(shù),而不是以浮點數(shù)形式返回秒數(shù)。例如,time()? 現(xiàn)在有一個名為 time_ns() 的納秒對應項:

import time
time.time_ns()
1564342792866601283

整數(shù)在 Python 中是無界的,因此 time_ns()? 可以為所有永恒提供納秒級分辨率。同樣,perf_counter_ns() ?是 perf_counter() 的納秒版本:

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

>>> time.perf_counter_ns()
13580765666638

我們注意到,因為 perf_counter() ?已經提供納秒級分辨率,所以使用 perf_counter_ns() 的優(yōu)勢較少。

注意: perf_counter_ns() ?僅在 Python 3.7 及更高版本中可用。在 Timer 類中使用了 perf_counter()。這樣,也可以在較舊的 Python 版本上使用 Timer。

有兩個函數(shù)time?不測量time.sleep時間:process_time()?和thread_time()。?通常希望Timer?能夠測量代碼所花費的全部時間,因此這兩個函數(shù)并不常用。而函數(shù) ?monotonic(),顧名思義,它是一個單調計時器,一個永遠不會向后移動的 Python 計時器。

除了 time()? 之外,所有這些函數(shù)都是單調的,如果調整了系統(tǒng)時間,它也隨之倒退。在某些系統(tǒng)上,monotonic()? 與 perf_counter()? 的功能相同,可以互換使用。我們可以使用 time.get_clock_info() 獲取有關 Python 計時器函數(shù)的更多信息:

>>> 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)

注意,不同系統(tǒng)上的結果可能會有所不同。

PEP 418 描述了引入這些功能的一些基本原理。它包括以下簡短描述:

  • time.monotonic():  超時和調度,不受系統(tǒng)時鐘更新影響
  • time.perf_counter():基準測試,短期內最精確的時鐘
  • time.process_time():分析進程的CPU時間

估計運行時間timeit

在實際工作中,通常會想優(yōu)化代碼進一步提升代碼性能,例如想知道將列表轉換為集合的最有效方法。下面我們使用函數(shù) set()? 和直接花括號定義集合 {...} 進行比較,看看這兩種方法哪個性能更優(yōu),此時需要使用 Python 計時器來比較兩者的運行速度。

>>> from timer import Timer
>>> numbers = [7, 6, 1, 4, 1, 8, 0, 6]
>>> with Timer(text="{:.8f}"):
... set(numbers)
...
{0, 1, 4, 6, 7, 8}
0.00007373

>>> with Timer(text="{:.8f}"):
... {*numbers}
...
{0, 1, 4, 6, 7, 8}
0.00006204

該測試結果表明直接花括號定義集合可能會稍微快一些,但其實這些結果非常不確定。如果重新運行代碼,可能會得到截然不同的結果。因為這會受計算機的性能和計算機運行狀態(tài)所影響:例如當計算機忙于其他任務時,就會影響我們程序的結果。

更好的方法是多次重復運行相同過程,并獲取平均耗時,就能夠更加精確地測量目標程序的性能大小。因此可以使用 timeit 標準庫,它旨在精確測量小代碼片段的執(zhí)行時間。雖然可以從 Python 導入和調用 timeit.timeit() 作為常規(guī)函數(shù),但使用命令行界面通常更方便。可以按如下方式對這兩種變體進行計時:

$ 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? 的結果證實 {*nums}? 量比 set(nums) 快。

注意:在下載文件或訪問數(shù)據(jù)庫的代碼上使用 timeit? 時要小心。由于 timeit 會自動多次調用程序,因此可能會無意中向服務器發(fā)送請求!

最后,IPython 交互式 shell? 和 Jupyter Notebook? 使用 %timeit 魔術命令對此功能提供了額外支持:

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)

同樣,測量結果表明直接花括號定義集合更快。在 Jupyter Notebooks? 中,還可以使用 %%timeit cell-magic 來測量運行整個單元格的時間。

使用 Profiler 查找代碼中的Bottlenecks

timeit 非常適合對特定代碼片段進行基準測試。但使用它來檢查程序的所有部分并找出哪些部分花費的時間最多會非常麻煩。此時我們想到可以使用分析器。

cProfile 是一個分析器,可以隨時從標準庫中訪問它。可以通過多種方式使用它,盡管將其用作命令行工具通常是最直接的:

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

此命令在打開分析器的情況下運行 download_data.py。將 cProfile 的輸出保存在 download_data.prof 中,由 -o 選項指定。輸出數(shù)據(jù)是二進制格式,需要專門的程序才能理解。同樣,Python 在標準庫中有一個選項 pstats!它可以在 .prof? 文件上運行 pstats 模塊會打開一個交互式配置文件統(tǒng)計瀏覽器。

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

...

要使用 pstats,請在提示符下鍵入命令。通常你會使用 sort? 和 stats? 命令,strip 可以獲得更清晰的輸出:

download_data.prof% strip
download_data.prof% sort cumtime
download_data.prof% stats 10
...

此輸出顯示總運行時間為 0.586 秒。它還列出了代碼花費最多時間的十個函數(shù)。這里按累積時間 ( cumtime) 排序,這意味著當給定函數(shù)調用另一個函數(shù)時,代碼會計算時間。

總時間 ( tottime?) 列表示代碼在函數(shù)中花費了多少時間,不包括在子函數(shù)中的時間。要查找代碼花費最多時間的位置,需要發(fā)出另一個sort命令:

download_data.prof% sort tottime
download_data.prof% stats 10
...

可以使用 pstats?了解代碼大部分時間花在哪里,然后嘗試優(yōu)化我們發(fā)現(xiàn)的任何瓶頸。還可以使用該工具更好地理解代碼的結構。例如,被調用者和調用者命令將顯示給定函數(shù)調用和調用的函數(shù)。

還可以研究某些函數(shù)。通過使用短語 timer? 過濾結果來檢查 Timer 導致的開銷:

download_data.prof% stats timer
...

完成調查后,使用 quit? 離開 pstats 瀏覽器。

如需更加深入了解更強大的配置文件數(shù)據(jù)接口,可以查看 KCacheGrind[8]。它使用自己的數(shù)據(jù)格式,也可以使用 pyprof2calltree[9] 從 cProfile 轉換數(shù)據(jù):

$ pyprof2calltree -k -i download_data.prof

該命令將轉換 download_data.prof? 并打開 KCacheGrind 來分析數(shù)據(jù)。

這里為代碼計時的最后一個選項是 line_profiler[10]。cProfile? 可以告訴我們代碼在哪些函數(shù)中花費的時間最多,但它不會深入顯示該函數(shù)中的哪些行最慢,此時就需要 line_profiler 。

注意:還可以分析代碼的內存消耗。這超出了本教程的范圍,如果你需要監(jiān)控程序的內存消耗,可以查看 memory-profiler[11] 。

行分析需要時間,并且會為我們的運行時增加相當多的開銷。正常的工作流程是首先使用 cProfile? 來確定要調查的函數(shù),然后在這些函數(shù)上運行 line_profiler?。line_profiler 不是標準庫的一部分,因此應該首先按照安裝說明[12]進行設置。

在運行分析器之前,需要告訴它要分析哪些函數(shù)。可以通過在源代碼中添加 @profile? 裝飾器來實現(xiàn)。例如,要分析 Timer.stop()?,在 timer.py 中添加以下內容:

@profile
def stop(self) -> float:
# 其余部分不變

注意,不需要導入profile配置文件,它會在運行分析器時自動添加到全局命名空間中。不過,我們需要在完成分析后刪除該行。否則,會拋出一個 NameError 異常。

接下來,使用 kernprof 運行分析器,它是 line_profiler 包的一部分:

$ kernprof -l download_data.py

此命令自動將探查器數(shù)據(jù)保存在名為 download_data.py.lprof? 的文件中。可以使用 line_profiler 查看這些結果:

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

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

# Hits Time PrHit %Time Line Contents
=====================================
...

首先,注意本報告中的時間單位是微秒(1e-06 s?)。通常,最容易查看的數(shù)字是 %Time,它告訴我們代碼在每一行的函數(shù)中花費的總時間的百分比。

總結

在本文中,我們嘗試了幾種不同的方法來將 Python 計時器添加到代碼中:

  • 使用了一個類來保持狀態(tài)并添加一個用戶友好的界面。類非常靈活,直接使用 Timer 可以讓您完全控制如何以及何時調用計時器。
  • 使用上下文管理器向代碼塊添加功能,并在必要時進行清理。上下文管理器使用起來很簡單,使用with Timer() 添加可以幫助您在視覺上更清楚地區(qū)分您的代碼。
  • 使用裝飾器向函數(shù)添加行為。裝飾器簡潔而引人注目,使用@Timer() 是監(jiān)控代碼運行時的快速方法。

我們還了解了為什么在對代碼進行基準測試時應該更喜歡time.perf_counter()?而不是 time.time(),以及在優(yōu)化代碼時還有哪些其他有用的替代方法。

現(xiàn)在我們可以在自己的代碼中添加Python計時器函數(shù)了!在日志中跟蹤程序的運行速度將有助于監(jiān)視腳本。

責任編輯:華軒 來源: 數(shù)據(jù)STUDIO
相關推薦

2022-06-28 15:29:56

Python編程語言計時器

2021-08-09 13:31:25

PythonExcel代碼

2022-10-19 14:30:59

2021-05-10 06:48:11

Python騰訊招聘

2021-12-11 20:20:19

Python算法線性

2021-02-02 13:31:35

Pycharm系統(tǒng)技巧Python

2021-02-04 09:00:57

SQLDjango原生

2021-02-06 14:55:05

大數(shù)據(jù)pandas數(shù)據(jù)分析

2011-03-28 16:14:38

jQuery

2022-08-04 10:39:23

Jenkins集成CD

2009-04-22 09:17:19

LINQSQL基礎

2021-05-17 21:30:06

Python求均值中值

2021-07-12 09:03:50

Python任務管理器cmd命令

2021-01-08 10:32:24

Charts折線圖數(shù)據(jù)可視化

2021-01-21 09:10:29

ECharts柱狀圖大數(shù)據(jù)

2020-03-08 22:06:16

Python數(shù)據(jù)IP

2012-01-11 13:40:35

移動應用云服務

2021-08-02 23:15:20

Pandas數(shù)據(jù)采集

2021-03-02 09:05:13

Python

2021-03-23 09:05:52

PythonExcelVlookup
點贊
收藏

51CTO技術棧公眾號

人妻无码中文字幕| xxxx日本黄色| 手机av免费在线| 国产成人午夜电影网| 欧美精品生活片| 91精品又粗又猛又爽| 成人欧美一区二区三区的电影| 中文字幕不卡在线播放| 91精品视频在线| 九九热国产视频| 国产探花在线精品一区二区| 欧美精品九九99久久| www.一区二区.com| 无码国精品一区二区免费蜜桃| 日韩精品乱码免费| 久久91亚洲精品中文字幕奶水| www.自拍偷拍| 一本一道久久a久久| 色婷婷久久久综合中文字幕| 特级西西444| 全部免费毛片在线播放网站| 激情偷乱视频一区二区三区| 91爱视频在线| 日韩高清dvd碟片| 久久99国内| 精品免费视频.| 三年中国国语在线播放免费| 黄色的视频在线观看| 久久精品日产第一区二区三区高清版| 亚洲va欧美va国产综合剧情 | 99国产精品久久久久99打野战| 国产精品日韩欧美一区| 久久精品夜夜夜夜夜久久| 国产制服丝袜在线| 一区二区视频| 欧美久久久久久久久久| 日本三级免费观看| 欧美xxxx性xxxxx高清| 九九在线精品| 欧美男gay| 制服丝袜亚洲色图| 国产一级不卡毛片| 僵尸再翻生在线观看免费国语| 亚洲欧美自拍偷拍| 亚洲国产精品一区二区第一页| 三级视频在线看| 国产成人免费在线视频| 成人黄色在线免费| 中文字幕第99页| 日韩中文字幕av电影| 欧美一区二区三区免费观看| 久久久久久蜜桃| 一区二区在线影院| 日韩有码在线播放| 91视频免费看片| 欧美午夜精品一区二区三区电影| 337p日本欧洲亚洲大胆精品| 欧美一级片在线免费观看| 国产精品3区| 91精品国产乱码久久蜜臀| 五月天婷婷亚洲| 欧洲精品久久久久毛片完整版| 在线观看亚洲精品视频| 国产真人无码作爱视频免费| 色8久久影院午夜场| 色狠狠色狠狠综合| 一区二区三区国产免费| 全球最大av网站久久| 欧美性高清videossexo| 国产又猛又黄的视频| abab456成人免费网址| 欧美三级中文字幕在线观看| 亚洲欧美日韩三级| 嫩呦国产一区二区三区av| 日韩欧美国产综合| 制服丝袜av在线| 亚洲精品456| 亚洲天堂影视av| 亚洲欧美综合7777色婷婷| 国产精品久久久久无码av| 免费91在线视频| 国产网址在线观看| 久久狠狠婷婷| 国产日韩欧美另类| 午夜精品在线播放| 91免费视频大全| 日本一区二区三区四区在线观看 | 女同性恋一区二区| 特级毛片在线| 日韩欧美精品中文字幕| 艹b视频在线观看| 人人爱人人干婷婷丁香亚洲| 日韩av综合中文字幕| 制服 丝袜 综合 日韩 欧美| 亚洲成人tv| 久久久久亚洲精品国产| 国产精品午夜一区二区| 国产精品99久久久久久似苏梦涵| 久久精品二区| 九七久久人人| 欧美日韩一区二区在线| 国产成人在线综合| 欧美成人午夜77777| 综合久久五月天| 四虎成人精品永久免费av| 日韩不卡一二三区| 国产精品乱子乱xxxx| 阿v免费在线观看| 亚洲一区二区在线免费观看视频| 精品久久久久久久无码| 无人区乱码一区二区三区| 精品无人区乱码1区2区3区在线 | 亚洲成精国产精品女| 在线观看高清免费视频| 国产精品久久久久av蜜臀| 色噜噜国产精品视频一区二区 | 亚洲一区二区三区无码久久| 久久久久久久久久久9不雅视频| 2021久久精品国产99国产精品| 国产精品呻吟久久| 久久久久99精品一区| 大伊香蕉精品视频在线| 免费视频观看成人| 亚洲女同性videos| 日韩男人的天堂| 国产麻豆视频一区| 日韩一区二区电影在线观看| 神马久久午夜| 亚洲爱爱爱爱爱| 成人免费毛片xxx| 欧美a级一区二区| 久久久久久欧美精品色一二三四 | 91福利视频在线观看| 国产女人爽到高潮a毛片| 国产欧美一区二区精品性色超碰| 黄色片网址在线观看| 精品中文字幕一区二区三区| 最近2019中文字幕在线高清| 蜜臀尤物一区二区三区直播| 91论坛在线播放| 国产曰肥老太婆无遮挡| 玖玖玖电影综合影院| 日韩亚洲综合在线| 中文字幕资源网| 日本一区二区三级电影在线观看| 国产亚洲综合视频| 牛牛视频精品一区二区不卡| 久久久久女教师免费一区| 国产后入清纯学生妹| 最新国产成人在线观看| www.久久av.com| 999国产精品视频| 91精品国产综合久久香蕉| 91精品专区| 欧美精品色一区二区三区| 成人性视频免费看| 青青草国产精品亚洲专区无| 欧洲精品久久| 二吊插入一穴一区二区| 亚洲深夜福利网站| 中文字幕乱伦视频| 国产精品私人影院| www.com操| 欧美国产美女| 亚洲一区二区在线播放| 四虎亚洲成人| 亚洲第一黄色网| 国产一级生活片| 91视频www| 激情六月丁香婷婷| 国产成人一区| 国产精品露脸自拍| 成人在线免费看片| 精品少妇一区二区三区免费观看| 国产成人在线观看网站| 91色.com| 亚洲中文字幕无码不卡电影| 欧美日中文字幕| 国产精品久久久久久久久久久久久 | 男女在线观看视频| 日韩成人av一区| 波多野结衣视频观看| 日韩一区欧美小说| 亚洲午夜久久久久久久久| 国产欧美欧美| 亚洲午夜精品一区二区三区| 国产精品99久久免费| 国内精品中文字幕| 精品电影在线| 欧美一区二区美女| 久久久国产高清| 中文av一区二区| 在线观看你懂的视频| 国产九九精品| 在线播放 亚洲| 欧美大奶一区二区| 国产免费久久av| а√在线中文网新版地址在线| 国产亚洲激情视频在线| 亚洲av少妇一区二区在线观看| 欧美性xxxxx极品娇小| 亚洲少妇xxx| 91丨九色porny丨蝌蚪| 亚洲自拍第三页| 久久天天综合| 超级碰在线观看| 北条麻妃国产九九九精品小说 | 国产成人久久777777| 欧美在线视屏| 日本午夜精品一区二区| 96sao在线精品免费视频| 国产精品免费看久久久香蕉| 91破解版在线观看| 日韩在线观看你懂的| 天天操天天干天天爽| 欧美一区二区在线视频| 无码久久精品国产亚洲av影片| 亚洲大型综合色站| 最新一区二区三区| 亚洲国产精品成人综合色在线婷婷 | 乱h高h女3p含苞待放| 国产日韩欧美制服另类| 这里只有精品在线观看视频 | 天天躁日日躁狠狠躁av| 麻豆精品视频在线观看视频| 久久女同性恋中文字幕| 天天爽天天爽夜夜爽| 亚洲黄页一区| 国产卡一卡二在线| 日韩精品一区二区三区免费观影 | 亚洲 中文字幕 日韩 无码| 欧美日韩国产一区精品一区| 一区二区av| 精品国内自产拍在线观看视频| 国产一区喷水| 欧美不卡在线观看| 成人亲热视频网站| 欧美综合社区国产| 国产美女精品视频| 97成人超碰| 国产精品www| videos性欧美另类高清| 午夜欧美大片免费观看| 国产又色又爽又黄刺激在线视频| xxx一区二区| 尤物网在线观看| 正在播放国产一区| 国产高清自拍视频在线观看| 亚洲女成人图区| 中国黄色一级视频| 欧美三级欧美一级| 色婷婷久久综合中文久久蜜桃av| 一本久久精品一区二区| 免费黄色网址在线| 黑人巨大精品欧美一区二区一视频| 久久久精品99| 亚洲一卡二卡三卡四卡无卡久久| 一区视频免费观看| 亚洲日本乱码在线观看| 国产十六处破外女视频| 亚洲自拍另类综合| 久久丫精品久久丫| 一区二区三区.www| 国产乡下妇女做爰| 黄网动漫久久久| 啦啦啦免费高清视频在线观看| 疯狂做受xxxx欧美肥白少妇| 国产高清中文字幕| 欧美在线看片a免费观看| 这里只有精品免费视频| 欧美疯狂做受xxxx富婆| 国产精品久久久久久久免费看| 日韩午夜在线观看视频| 国产 日韩 欧美 综合| 亚洲第一福利网| 四虎在线免费看| 中国日韩欧美久久久久久久久 | 欧美激情性做爰免费视频| 任你弄在线视频免费观看| 97视频免费观看| 日韩av一级| 91在线精品观看| 欧美人妖视频| 亚洲乱码一区二区三区| 欧美福利电影在线观看| 两根大肉大捧一进一出好爽视频| 国产精品羞羞答答在线| 欧美日韩中文字幕精品| 成 人片 黄 色 大 片| 亚洲精品久久久久久下一站 | 性欧美video另类hd尤物| 午夜精品久久17c| 日本免费一区二区三区四区| 国产综合香蕉五月婷在线| 7m精品国产导航在线| 蜜桃麻豆91| 亚洲五月综合| a√天堂在线观看| 久久精品国产99国产| 国内自拍偷拍视频| 国产欧美精品一区二区色综合| 老女人性淫交视频| 欧美午夜www高清视频| 97人妻精品一区二区三区| 亚洲精品91美女久久久久久久| 在线观看免费版| 午夜精品久久久99热福利| 国产精品亚洲成在人线| 黄色91av| 91成人精品| 黑鬼大战白妞高潮喷白浆| 国产精品中文字幕日韩精品| 日本精品在线观看视频| 亚洲国产成人porn| 国产一区二区在线视频观看| 日韩精品中文字幕在线观看| 成人影院在线观看| 国产成人一区二区三区小说| 亚洲精品一区二区三区在线| 午夜一区二区三区| 亚洲黄网站黄| 一区二区三区人妻| 国产精品理论在线观看| 精品人妻一区二区三区免费看| 欧美r级在线观看| 免费在线观看av片| 国产成人在线一区| 亚洲视频国产| avove在线观看| 另类成人小视频在线| 国产精品亚洲无码| 午夜欧美大尺度福利影院在线看| av在线亚洲天堂| 久久天堂av综合合色| 国产一区二区三区四区五区3d| 久久香蕉综合色| 日韩一级欧洲| 久久免费精品国产| 亚洲综合男人的天堂| 99国产精品99| 另类天堂视频在线观看| 亚洲mmav| 欧美日韩另类综合| 久久电影一区| 短视频在线观看| 欧美日韩国产在线| 欧洲天堂在线观看| 国产91精品不卡视频| 希岛爱理av免费一区二区| 无罩大乳的熟妇正在播放| 99天天综合性| 男人的天堂一区| 日韩精品视频在线| 91精品论坛| 欧美影视一区二区| 蜜桃成人365av| 亚洲va欧美va人人爽| 国产免费的av| 中文字幕亚洲图片| 欧美爱爱视频| 一区二区不卡在线观看| 精品在线一区二区| 日韩a级片在线观看| 欧美r级在线观看| 日韩av影片| 日韩少妇中文字幕| 久久99精品久久只有精品| 国产suv一区二区三区| 精品久久久久一区| 玖玖在线播放| 日韩av不卡在线播放| 美国毛片一区二区| 国产波霸爆乳一区二区| 亚洲国产成人av在线| 网友自拍亚洲| 懂色av一区二区三区四区五区| 国产宾馆实践打屁股91| 久久久久国产精品夜夜夜夜夜| 亚洲激情电影中文字幕| 中日韩脚交footjobhd| 亚洲午夜精品一区二区 | 午夜性福利视频| 欧美日韩另类视频| 91caoporm在线视频| 超碰97在线资源| 亚洲狠狠婷婷| 在线观看天堂av| 精品久久人人做人人爱| 韩国成人漫画| avove在线观看| 91捆绑美女网站| 国产视频在线一区| 久久久久久91| 色999国产精品| 红桃视频一区二区三区免费| 图片区小说区国产精品视频| av每日在线更新| 国产精品乱子乱xxxx| 麻豆国产欧美一区二区三区| 国产精品99re|