目次
愚かな解決策
関数のパッケージ化
General Closure
構文シュガー
評価デコレータ
パラメータを使用したデコレータ
智能装饰器
ホームページ バックエンド開発 Python チュートリアル Pythonデコレーターの詳しい使い方紹介(コード例)

Pythonデコレーターの詳しい使い方紹介(コード例)

Feb 25, 2019 am 10:33 AM
decorator python 修飾子 デコレータ

この記事では、Python デコレータの詳細な使用方法 (コード例) を紹介します。一定の参考価値があります。必要な友人は参照してください。お役に立てば幸いです。

Python では、通常、デコレータは関数を修飾してパブリック関数を実装し、コードの再利用を実現するために使用されます。関数定義の前に @xxxx を追加すると、関数は特定の動作を挿入します。これは驚くべきことです。ただし、これは単なる糖衣構文です。

シナリオ

データを別の方法で処理するために使用される機能する関数がいくつかあると仮定します。

def work_bar(data):
    pass


def work_foo(data):
    pass
ログイン後にコピー

これを関数呼び出しの前後に実行したいと考えています。ログを出力したいのですがどうすればいいですか?

愚かな解決策

logging.info('begin call work_bar')
work_bar(1)
logging.info('call work_bar done')
ログイン後にコピー

複数のコード呼び出しがある場合はどうなりますか?考えるだけで怖いです!

関数のパッケージ化

愚かな解決策は、コードの冗長性を高めすぎることであり、各関数呼び出しを再度記述する必要がありますロギング。冗長ロジックのこの部分は、新しい関数にカプセル化できます:

def smart_work_bar(data):
    logging.info('begin call: work_bar')
    work_bar(data)
    logging.info('call doen: work_bar')
ログイン後にコピー

このようにして、smart_work_bar を毎回呼び出すことができます:

smart_work_bar(1)

# ...

smart_work_bar(some_data)
ログイン後にコピー

General Closure

完璧に見えます...しかし、work_foo にも同じニーズがある場合、smart_work_foo を再度実装する必要がありますか?これは明らかに非科学的です!

心配しないでください。クロージャを使用できます。

def log_call(func):
    def proxy(*args, **kwargs):
        logging.info('begin call: {name}'.format(name=func.func_name))
        result = func(*args, **kwargs)
        logging.info('call done: {name}'.format(name=func.func_name))
        return result
    return proxy
ログイン後にコピー

この関数は、関数オブジェクト (プロキシ関数) をパラメータとして受け取り、プロキシ関数を返します。プロキシ関数を呼び出すと、最初にログが出力され、次にプロキシ関数が呼び出され、呼び出しが完了してからログが出力され、最後に呼び出し結果が返されます。このようにして、一般化の目的は達成されるのではないでしょうか? ——任意のプロキシ関数 funclog_call を簡単に処理できます。

smart_work_bar = log_call(work_bar)
smart_work_foo = log_call(work_foo)

smart_work_bar(1)
smart_work_foo(1)

# ...

smart_work_bar(some_data)
smart_work_foo(some_data)
ログイン後にコピー

1 で、log_call はパラメータ work_bar を受け取り、プロキシ関数 proxy を返し、それを割り当てます。 smart_work_barまで。 4 行目では、プロキシ関数 proxy である smart_work_bar を呼び出し、最初にログを出力し、次に func を呼び出します。 work_bar、最後にログを出力します。プロキシ関数では、func が渡された work_bar オブジェクトと密接に関連していることに注意してください。これは クロージャ です。

繰り返しますが、プロキシ関数名を上書きできます。新しい名前の前に smart_ を付けるのは、まだ少し面倒です:

work_bar = log_call(work_bar)
work_foo = log_call(work_foo)

work_bar(1)
work_foo(1)
ログイン後にコピー

構文シュガー

まず次のコードを見てください。

def work_bar(data):
    pass
work_bar = log_call(work_bar)


def work_foo(data):
    pass
work_foo = log_call(work_foo)
ログイン後にコピー

コードは冗長ではありませんが、まだ十分に直観的ではありません。このとき、構文シュガーが登場します~~~

@log_call
def work_bar(data):
    pass
ログイン後にコピー

ですので、1 つのことに注意してください (強調 )。ここでの @log_call の関数は次のとおりです。 Pythonコンパイラはコードwork_bar = log_call(work_bar)を挿入します。

評価デコレータ

まずはデコレータ eval_now が何をするのか推測してみましょう。

def eval_now(func):
    return func()
ログイン後にコピー

奇妙に思えます。プロキシ関数が定義されていません。これはデコレータとみなされますか?

@eval_now
def foo():
    return 1

print foo
ログイン後にコピー

このコードは、関数を呼び出して評価するための 1 を出力します。それで何の役に立つの? foo = 1 と直接書くことはできないのでしょうか?この簡単な例では、このように書くことももちろん可能です。より複雑な例を見てみましょう - ログ オブジェクトの初期化:

# some other code before...

# log format
formatter = logging.Formatter(
    '[%(asctime)s] %(process)5d %(levelname) 8s - %(message)s',
    '%Y-%m-%d %H:%M:%S',
)

# stdout handler
stdout_handler = logging.StreamHandler(sys.stdout)
stdout_handler.setFormatter(formatter)
stdout_handler.setLevel(logging.DEBUG)

# stderr handler
stderr_handler = logging.StreamHandler(sys.stderr)
stderr_handler.setFormatter(formatter)
stderr_handler.setLevel(logging.ERROR)

# logger object
logger = logging.Logger(__name__)
logger.setLevel(logging.DEBUG)
logger.addHandler(stdout_handler)
logger.addHandler(stderr_handler)

# again some other code after...
ログイン後にコピー

Use eval_now:

# some other code before...

@eval_now
def logger():
    # log format
    formatter = logging.Formatter(
        '[%(asctime)s] %(process)5d %(levelname) 8s - %(message)s',
        '%Y-%m-%d %H:%M:%S',
    )

    # stdout handler
    stdout_handler = logging.StreamHandler(sys.stdout)
    stdout_handler.setFormatter(formatter)
    stdout_handler.setLevel(logging.DEBUG)

    # stderr handler
    stderr_handler = logging.StreamHandler(sys.stderr)
    stderr_handler.setFormatter(formatter)
    stderr_handler.setLevel(logging.ERROR)

    # logger object
    logger = logging.Logger(__name__)
    logger.setLevel(logging.DEBUG)
    logger.addHandler(stdout_handler)
    logger.addHandler(stderr_handler)

    return logger

# again some other code after...
ログイン後にコピー

2 つのコードの目的は同じですが、後者の方が明らかに明瞭で、コード ブロックのスタイルを持っています。さらに重要なのは、一時変数 (formatter など) が外部名前空間 (グローバルなど) を汚染するのを避けるために、関数呼び出しはローカル名前空間で初期化されることです。

パラメータを使用したデコレータ

遅い関数呼び出しを記録するためのデコレータを定義します:

def log_slow_call(func):
    def proxy(*args, **kwargs):
        start_ts = time.time()
        result = func(*args, **kwargs)
        end_ts = time.time()

        seconds = start_ts - end_ts
        if seconds > 1:
        logging.warn('slow call: {name} in {seconds}s'.format(
            name=func.func_name,
            seconds=seconds,
        ))

        return result

    return proxy
ログイン後にコピー

35 行は、関数呼び出しの前後で現在時刻を取得し、7 行で呼び出し時間を計算しており、1 秒以上かかる場合は警告ログが出力されます。

@log_slow_call
def sleep_seconds(seconds):
    time.sleep(seconds)

sleep_seconds(0.1)  # 没有日志输出

sleep_seconds(2)    # 输出警告日志
ログイン後にコピー

ただし、しきい値の設定は常に状況に依存し、機能ごとに異なる値が設定される場合があります。しきい値をパラメータ化する方法があれば良いでしょう:

def log_slow_call(func, threshold=1):
    def proxy(*args, **kwargs):
        start_ts = time.time()
        result = func(*args, **kwargs)
        end_ts = time.time()

        seconds = start_ts - end_ts
        if seconds > threshold:
        logging.warn('slow call: {name} in {seconds}s'.format(
            name=func.func_name,
            seconds=seconds,
        ))

        return result

    return proxy
ログイン後にコピー

ただし、 @xxxx 構文シュガーは常に、パラメータとして装飾された関数を使用してデコレータを呼び出します。 しきい値 パラメータを渡す可能性はありません。どうやってするの? ——クロージャを使用して、threshold パラメータをカプセル化します:

def log_slow_call(threshold=1):
    def decorator(func):
        def proxy(*args, **kwargs):
            start_ts = time.time()
            result = func(*args, **kwargs)
            end_ts = time.time()

            seconds = start_ts - end_ts
            if seconds > threshold:
            logging.warn('slow call: {name} in {seconds}s'.format(
                name=func.func_name,
                seconds=seconds,
            ))

            return result

        return proxy

    return decorator


@log_slow_call(threshold=0.5)
def sleep_seconds(seconds):
    time.sleep(seconds)
ログイン後にコピー

このように、log_slow_call(threshold=0.5) は戻り関数 decorator## を呼び出します。 #、関数にはクロージャ変数 threshold があり、値は 0.5 です。 decorator模様替えsleep_秒

デフォルトのしきい値を使用すると、関数呼び出しを省略できません:

@log_slow_call()
def sleep_seconds(seconds):
    time.sleep(seconds)
ログイン後にコピー
乙女座は最初の行のかっこのペアに満足していない可能性があるため、次のように改善できます:

def log_slow_call(func=None, threshold=1):
    def decorator(func):
        def proxy(*args, **kwargs):
            start_ts = time.time()
            result = func(*args, **kwargs)
            end_ts = time.time()

            seconds = start_ts - end_ts
            if seconds > threshold:
            logging.warn('slow call: {name} in {seconds}s'.format(
                name=func.func_name,
                seconds=seconds,
            ))

            return result

        return proxy

    if func is None:
        return decorator
    else:
        return decorator(func)
ログイン後にコピー
This この書き込み方法は、使用法

A デフォルトしきい値 (呼び出しなし) と使用法 B カスタムしきい値 (呼び出しあり) という 2 つの異なる使用法と互換性があります。

# Case A
@log_slow_call
def sleep_seconds(seconds):
    time.sleep(seconds)


# Case B
@log_slow_call(threshold=0.5)
def sleep_seconds(seconds):
    time.sleep(seconds)
ログイン後にコピー
Usage

A、何が起こるかというと log_slow_call(sleep_seconds) です。つまり、func パラメータは空ではありません。これは直接的なものです。調整decoratorラップして返します (しきい値はデフォルトです)。

用法B中,先发生的是log_slow_call(threshold=0.5)func参数为空,直接返回新的装饰器decorator,关联闭包变量threshold,值为0.5;然后,decorator再装饰函数sleep_seconds,即decorator(sleep_seconds)。注意到,此时threshold关联的值是0.5,完成定制化。

你可能注意到了,这里最好使用关键字参数这种调用方式——使用位置参数会很丑陋:

# Case B-
@log_slow_call(None, 0.5)
def sleep_seconds(seconds):
    time.sleep(seconds)
ログイン後にコピー

当然了,函数调用尽量使用关键字参数是一种极佳实践,含义清晰,在参数很多的情况下更是如此。

智能装饰器

上节介绍的写法,嵌套层次较多,如果每个类似的装饰器都用这种方法实现,还是比较费劲的(脑子不够用),也比较容易出错。

假设有一个智能装饰器smart_decorator,修饰装饰器log_slow_call,便可获得同样的能力。这样,log_slow_call定义将变得更清晰,实现起来也更省力啦:

@smart_decorator
def log_slow_call(func, threshold=1):
    def proxy(*args, **kwargs):
        start_ts = time.time()
        result = func(*args, **kwargs)
        end_ts = time.time()

        seconds = start_ts - end_ts
        if seconds > threshold:
        logging.warn('slow call: {name} in {seconds}s'.format(
            name=func.func_name,
            seconds=seconds,
        ))

        return result

    return proxy
ログイン後にコピー

脑洞开完,smart_decorator如何实现呢?其实也简单:

def smart_decorator(decorator):

    def decorator_proxy(func=None, **kwargs):
        if func is not None:
            return decorator(func=func, **kwargs)

        def decorator_proxy(func):
            return decorator(func=func, **kwargs)

        return decorator_proxy

    return decorator_proxy
ログイン後にコピー

smart_decorator实现了以后,设想就成立了!这时,log_slow_call,就是decorator_proxy(外层),关联的闭包变量decorator是本节最开始定义的log_slow_call(为了避免歧义,称为real_log_slow_call)。log_slow_call支持以下各种用法:

# Case A
@log_slow_call
def sleep_seconds(seconds):
    time.sleep(seconds)
ログイン後にコピー

用法A中,执行的是decorator_proxy(sleep_seconds)(外层),func非空,kwargs为空;直接执行decorator(func=func, **kwargs),即real_log_slow_call(sleep_seconds),结果是关联默认参数的proxy

# Case B
# Same to Case A
@log_slow_call()
def sleep_seconds(seconds):
    time.sleep(seconds)
ログイン後にコピー

用法B中,先执行decorator_proxy()funckwargs均为空,返回decorator_proxy对象(内层);再执行decorator_proxy(sleep_seconds)(内层);最后执行decorator(func, **kwargs),等价于real_log_slow_call(sleep_seconds),效果与用法A一致。

# Case C
@log_slow_call(threshold=0.5)
def sleep_seconds(seconds):
    time.sleep(seconds)
ログイン後にコピー

用法C中,先执行decorator_proxy(threshold=0.5)func为空但kwargs非空,返回decorator_proxy对象(内层);再执行decorator_proxy(sleep_seconds)(内层);最后执行decorator(sleep_seconds, **kwargs),等价于real_log_slow_call(sleep_seconds, threshold=0.5),阈值实现自定义!

以上がPythonデコレーターの詳しい使い方紹介(コード例)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Windows 8でコードを実行できます Windows 8でコードを実行できます Apr 15, 2025 pm 07:24 PM

VSコードはWindows 8で実行できますが、エクスペリエンスは大きくない場合があります。まず、システムが最新のパッチに更新されていることを確認してから、システムアーキテクチャに一致するVSコードインストールパッケージをダウンロードして、プロンプトとしてインストールします。インストール後、一部の拡張機能はWindows 8と互換性があり、代替拡張機能を探すか、仮想マシンで新しいWindowsシステムを使用する必要があることに注意してください。必要な拡張機能をインストールして、適切に動作するかどうかを確認します。 Windows 8ではVSコードは実行可能ですが、開発エクスペリエンスとセキュリティを向上させるために、新しいWindowsシステムにアップグレードすることをお勧めします。

VSCODE拡張機能は悪意がありますか? VSCODE拡張機能は悪意がありますか? Apr 15, 2025 pm 07:57 PM

VSコード拡張機能は、悪意のあるコードの隠れ、脆弱性の活用、合法的な拡張機能としての自慰行為など、悪意のあるリスクを引き起こします。悪意のある拡張機能を識別する方法には、パブリッシャーのチェック、コメントの読み取り、コードのチェック、およびインストールに注意してください。セキュリティ対策には、セキュリティ認識、良好な習慣、定期的な更新、ウイルス対策ソフトウェアも含まれます。

ターミナルVSCODEでプログラムを実行する方法 ターミナルVSCODEでプログラムを実行する方法 Apr 15, 2025 pm 06:42 PM

VSコードでは、次の手順を通じて端末でプログラムを実行できます。コードを準備し、統合端子を開き、コードディレクトリが端末作業ディレクトリと一致していることを確認します。プログラミング言語(pythonのpython your_file_name.pyなど)に従って実行コマンドを選択して、それが正常に実行されるかどうかを確認し、エラーを解決します。デバッガーを使用して、デバッグ効率を向上させます。

PHPとPythonの選択:ガイド PHPとPythonの選択:ガイド Apr 18, 2025 am 12:24 AM

PHPはWeb開発と迅速なプロトタイピングに適しており、Pythonはデータサイエンスと機械学習に適しています。 1.PHPは、単純な構文と迅速な開発に適した動的なWeb開発に使用されます。 2。Pythonには簡潔な構文があり、複数のフィールドに適しており、強力なライブラリエコシステムがあります。

PHPおよびPython:さまざまなパラダイムが説明されています PHPおよびPython:さまざまなパラダイムが説明されています Apr 18, 2025 am 12:26 AM

PHPは主に手順プログラミングですが、オブジェクト指向プログラミング(OOP)もサポートしています。 Pythonは、OOP、機能、手続き上のプログラミングなど、さまざまなパラダイムをサポートしています。 PHPはWeb開発に適しており、Pythonはデータ分析や機械学習などのさまざまなアプリケーションに適しています。

Visual StudioコードはPythonで使用できますか Visual StudioコードはPythonで使用できますか Apr 15, 2025 pm 08:18 PM

VSコードはPythonの書き込みに使用でき、Pythonアプリケーションを開発するための理想的なツールになる多くの機能を提供できます。ユーザーは以下を可能にします。Python拡張機能をインストールして、コードの完了、構文の強調表示、デバッグなどの関数を取得できます。デバッガーを使用して、コードを段階的に追跡し、エラーを見つけて修正します。バージョンコントロールのためにGitを統合します。コードフォーマットツールを使用して、コードの一貫性を維持します。糸くずツールを使用して、事前に潜在的な問題を発見します。

vscodeはMacに使用できますか vscodeはMacに使用できますか Apr 15, 2025 pm 07:36 PM

VSコードはMacで利用できます。強力な拡張機能、GIT統合、ターミナル、デバッガーがあり、豊富なセットアップオプションも提供しています。ただし、特に大規模なプロジェクトまたは非常に専門的な開発の場合、コードと機能的な制限がある場合があります。

vscodeはipynbを実行できます vscodeはipynbを実行できます Apr 15, 2025 pm 07:30 PM

VSコードでJupyterノートブックを実行するための鍵は、Python環境が適切に構成されていることを確認し、コードの実行順序がセルの順序と一致していることを理解し、パフォーマンスに影響を与える可能性のある大きなファイルまたは外部ライブラリに注意することです。 VSコードで提供されるコードの完了とデバッグ機能は、コーディング効率を大幅に改善し、エラーを減らすことができます。

See all articles