ホームページ  >  記事  >  バックエンド開発  >  コンテキストマネージャーを使用して Python タイマーを拡張する方法

コンテキストマネージャーを使用して Python タイマーを拡張する方法

王林
王林転載
2023-04-12 20:43:061690ブラウズ

上記では、最初の Python タイマー クラスを作成し、その後、Timer クラスを徐々に拡張しました。そのコードも比較的豊富で強力です。これで満足することはできません。タイマーを使用するためにコードをテンプレート化する必要があります。

  • まず、クラスをインスタンス化します。
  • 次に、前に .start()# を呼び出します。時間を計測するコード ブロック
  • ##最後に、コード ブロックの後で .stop() を呼び出します

コンテキストマネージャーを使用して Python タイマーを拡張する方法##Python タイマー コンテキスト マネージャー

Python には、コード ブロックの前後で関数を呼び出すための独自の構造、コンテキスト マネージャーがあります。

Python のコンテキスト マネージャーについて理解する

コンテキスト マネージャーは、長い間 Python の重要な部分でした。 2005 年に PEP 343 によって導入され、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]] = {}
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:

open() をコンテキスト マネージャーとして使用すると、ファイル ポインター fp は明示的に閉じられないことに注意してください。fp が自動的に閉じられたことを確認できます。 :

fp.closed
True

この例では、open("timer.py") はコンテキスト マネージャーを返す式です。このコンテキスト マネージャーは fp という名前にバインドされています。コンテキスト マネージャーは、print() の実行中に有効です。この単一行のコード ブロックは、 fp のコンテキストで実行されます。

fp はコンテキスト マネージャーとして何を意味しますか?技術的に言えば、fp はコンテキスト マネージャー プロトコルを実装します。 Python 言語の基礎となるプロトコルは数多くあります。プロトコルは、コードがどのような特定のメソッドを実装する必要があるかを規定する契約であると考えてください。

コンテキスト マネージャー プロトコルは 2 つのメソッドで構成されます。

コンテキスト マネージャーに関連付けられたコンテキストに入るときに .__enter__() を呼び出します。
  1. コンテキスト マネージャーに関連付けられたコンテキストを終了するときに .__exit__() を呼び出します。
  2. つまり、独自のコンテキスト マネージャーを作成するには、 .__enter__() と .__exit__() を実装するクラスを作成する必要があります。 Hello, World! コンテキスト マネージャーの例を試してください:
# studio.py
class Studio:
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}")

Studio はコンテキスト マネージャー プロトコルを実装するコンテキスト マネージャーであり、次のように使用されます:

from studio import Studio
with Studio("云朵君"):
print("正在忙 ...")
你好 云朵君
正在忙 ...
一会儿见, 云朵君

まず、.__enter__() に注意してください。何かを実行する前にそれが呼び出され、何かを実行した後に .__exit__() が呼び出される方法。この例では、コンテキスト マネージャーは参照されないため、コンテキスト マネージャーの名前に as を使用する必要はありません。

次に、self.__enter__() の戻り値には制約があることに注意してください。コンテキスト マネージャーを作成するときは、通常、 .__enter__() から self を返します。戻り値は次のように使用できます:

from greeter import Greeter
with Greeter("云朵君") as grt:
print(f"{grt.name} 正在忙 ...")
你好 云朵君
云朵君 正在忙 ...
一会儿见, 云朵君

__exit__ 関数を作成するときは、それに注意する必要があります。この関数には次の 3 つのパラメータが必要です:

exc_type: 例外の種類
  • exc_val: 例外値
  • exc_tb: 例外エラー スタック情報
  • ##これら 3 つのパラメーターは、コンテキスト マネージャーでのエラー処理に使用され、sys で始まります。 exc_info()は戻り値を返します。メイン ロジック コードが例外を報告しない場合、これら 3 つのパラメーターはすべて None になります。
ブロックの実行中に例外が発生した場合、コードは例外タイプ、例外インスタンス、トレースバック オブジェクト (exc_type、exc_value、exc_tb) を指定して .__exit__() を呼び出します。通常、これらはコンテキスト マネージャーで無視され、例外が発生する前に .__exit__() が呼び出されます。

from greeter import Greeter
with Greeter("云朵君") as grt:
print(f"{grt.age} does not exist")
你好 云朵君
一会儿见, 云朵君
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AttributeError: 'Greeter' object has no attribute 'age'

ご覧のとおり、コードにエラーがあっても、「また会いましょう」はまだ印刷されています。ユンドゥオさん」。

contextlib の理解と使用

これで、コンテキスト マネージャーとは何か、および独自のコンテキスト マネージャーを作成する方法について予備的に理解できました。上の例では、コンテキスト マネージャーを構築するためだけにクラスを作成しました。単純な関数を実装したいだけの場合、クラスの作成は少し複雑すぎます。このとき、関数を 1 つ書くだけでコンテキスト マネージャーを実装できたらいいのに、と考えました。

Python はこれをすでに考えています。コード プロトコルに従って関数コンテンツを実装する限り、この関数オブジェクトをコンテキスト マネージャーに変えることができます。

我们按照 contextlib 的协议来自己实现一个上下文管理器,为了更加直观我们换个用例,创建一个我们常用且熟悉的打开文件(with open)的上下文管理器。

import contextlib

@contextlib.contextmanager
def open_func(file_name):
# __enter__方法
print('open file:', file_name, 'in __enter__')
file_handler = open(file_name, 'r')
 
# 【重点】:yield
yield file_handler

# __exit__方法
print('close file:', file_name, 'in __exit__')
file_handler.close()
return

with open_func('test.txt') as file_in:
for line in file_in:
print(line)

在被装饰函数里,必须是一个生成器(带有yield),而 yield 之前的代码,就相当于__enter__里的内容。yield 之后的代码,就相当于__exit__ 里的内容。

上面这段代码只能实现上下文管理器的第一个目的(管理资源),并不能实现第二个目的(处理异常)。

如果要处理异常,可以改成下面这个样子。

import contextlib

@contextlib.contextmanager
def open_func(file_name):
# __enter__方法
print('open file:', file_name, 'in __enter__')
file_handler = open(file_name, 'r')

try:
yield file_handler
except Exception as exc:
# deal with exception
print('the exception was thrown')
finally:
print('close file:', file_name, 'in __exit__')
file_handler.close()
return

with open_func('test.txt') as file_in:
for line in file_in:
1/0
print(line)

Python 标准库中的 contextlib包括定义新上下文管理器的便捷方法,以及可用于关闭对象、抑制错误甚至什么都不做的现成上下文管理器!

创建 Python 计时器上下文管理器

了解了上下文管理器的一般工作方式后,要想知道它们是如何帮助处理时序代码呢?假设如果可以在代码块之前和之后运行某些函数,那么就可以简化 Python 计时器的工作方式。其实,上下文管理器可以自动为计时时显式调用 .start() 和.stop()。

同样,要让 Timer 作为上下文管理器工作,它需要遵守上下文管理器协议,换句话说,它必须实现 .__enter__() 和 .__exit__() 方法来启动和停止 Python 计时器。从目前的代码中可以看出,所有必要的功能其实都已经可用,因此只需将以下方法添加到之前编写的的 Timer 类中即可:

# timer.py
@dataclass
class Timer:
# 其他代码保持不变

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

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

Timer 现在就是一个上下文管理器。实现的重要部分是在进入上下文时, .__enter__() 调用 .start() 启动 Python 计时器,而在代码离开上下文时, .__exit__() 使用 .stop() 停止 Python 计时器。

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

此处注意两个更微妙的细节:

  • .__enter__()​ 返回self​,Timer 实例,它允许用户使用as​ 将Timer ​实例绑定到变量。例如,使用with Timer() as t:​ 将创建指向Timer ​对象的变量t。
  • .__exit__()​ 需要三个参数,其中包含有关上下文执行期间发生的任何异常的信息。代码中,这些参数被打包到一个名为exc_info 的元组中,然后被忽略,此时 Timer 不会尝试任何异常处理。

在这种情况下不会处理任何异常。上下文管理器的一大特点是,无论上下文如何退出,都会确保调用.__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 也会打印出经过的时间。

使用 Python 定时器上下文管理器

现在我们将一起学习如何使用 Timer 上下文管理器来计时 "下载数据" 程序。回想一下之前是如何使用 Timer 的:

# download_data.py
import requests
from timer import Timer
def 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) 
t.stop()
with open('dataset/datasets.zip', 'wb') as f:
f.write(res.content)

if __name__ == "__main__":
main()

我们正在对 requests.get() 的调用进行记时监控。使用上下文管理器可以使代码更短、更简单、更易读:

# download_data.py
import requests
from timer import 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'}
with Timer():
res = requests.get(source_url, headers=headers)

with open('dataset/datasets.zip', 'wb') as f:
f.write(res.content)

if __name__ == "__main__":
main()

此代码实际上与上面的代码相同。主要区别在于没有定义无关变量t,在命名空间上无多余的东西。

写在最后

将上下文管理器功能添加到 Python 计时器类有几个优点:

  • 省时省力:只需要一行额外的代码即可为代码块的执行计时。
  • 可读性高:调用上下文管理器是可读的,你可以更清楚地可视化你正在计时的代码块。

使用 Timer 作为上下文管理器几乎与直接使用 .start() 和 .stop() 一样灵活,同时它的样板代码更少。在该系列下一篇文章中,云朵君将和大家一起学习如何将 Timer 也用作装饰器,并用于代码中,从而更加容易地监控代码完整运行过程,我们一起期待吧!

以上がコンテキストマネージャーを使用して Python タイマーを拡張する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事は51cto.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。