ホームページ バックエンド開発 Python チュートリアル Python デコレータを理解するには、この記事を読むだけで十分です

Python デコレータを理解するには、この記事を読むだけで十分です

Feb 17, 2017 am 11:07 AM

Python デコレータについて話す前に、少し汚いですが、デコレータのトピックに非常に関連した例を示したいと思います。

誰もが持っている下着の主な機能は恥を隠すことですが、冬には風や寒さを防ぐことができません。どうすればよいでしょうか?そこで考えられたのが、下着を厚く長くすることで、恥を隠すだけでなく、暖かさも兼ね備えたパンツに変身した後です。 、まだ恥を隠す機能はありますが、本質的には本物の下着ではなくなりました。そこで賢い人たちはズボンを発明し、下着に影響を与えずにズボンを直接下着の上に置きました。このようにして、ズボンを着用しても赤ちゃんは寒くなくなります。デコレーターはここで言うズボンのようなもので、下着の機能に影響を与えることなく体に暖かさを与えます。

デコレータについて話す前に、Python の関数は Java や C++ とは異なることを理解しておく必要があります。たとえば、Python の関数は通常の変数と同様に、別の関数にパラメータとして渡すことができます。トピック。デコレータは本質的には、コードを変更せずに他の関数やクラスが機能を追加できるようにする Python 関数またはクラスです。デコレータの戻り値も関数/クラス オブジェクトです。これは、ログ挿入、パフォーマンス テスト、トランザクション処理、キャッシュ、権限の検証など、横断的な要件を持つシナリオでよく使用されます。デコレーターは、そのような問題を解決する優れた設計です。デコレータを使用すると、関数自体とは関係のない大量の類似コードをデコレータに抽出し、再利用し続けることができます。一言で言えば、デコレーターの目的は、既存のオブジェクトに機能を追加することです。

まずは簡単な例を見てみましょう。ただし、実際のコードはこれよりもはるかに複雑である可能性があります:

def foo():
    print("foo")def bar(func):
    func()bar(foo)
ログイン後にコピー

ここで、関数の実行ログを記録したいという新しい要件があるので、ログ コードを次のように追加します。コード:

def foo():
    print('i am foo')
ログイン後にコピー

関数 bar() と bar2() にも同様の要件がある場合、どうすればよいでしょうか。 bar 関数に別のログを書きますか?これにより、同じようなコードが大量に作成されます。コードの繰り返しを減らすために、ログを処理するための新しい関数を再定義します。ログが処理された後、実際のビジネス コードが実行されます。これには論理的な問題はありません。関数は実装されていますが、この関数を呼び出すと、実際のビジネス ロジックの foo 関数を呼び出すのではなく、元のコード構造を破壊する use_logging 関数に置き換える必要があります。毎回それを呼び出します。元の foo 関数はパラメータとして use_logging 関数に渡されるので、より良い方法はありますか?もちろん、それはあります。答えはデコレータです。

シンプルなデコレーター

def foo():
    print('i am foo')
    logging.info("foo is running")
ログイン後にコピー

use_logging はデコレーターで、実際のビジネスロジックを実行する関数 func をラップする普通の関数です。foo は use_logging によって装飾されており、use_logging はこの関数の名前を返します。ラッパーです。この例では、関数の出入りをクロスセクションと呼びます。このプログラミング方法はアスペクト指向プログラミングと呼ばれます。

@ 糖鎖構文

Python にしばらく触れたことがある人なら、@ 記号に精通しているはずです。はい、@ 記号は、関数の先頭に配置されます。定義により、再割り当ての最後のステップが省略されます。

def use_logging(func):
    logging.warn("%s is running" % func.__name__)
    func()def foo():
    print('i am foo')use_logging(foo)
ログイン後にコピー

上に示したように、 @ を使用すると、文 foo = use_logging(foo) を省略し、直接 foo() を呼び出して目的の結果を得ることができます。 foo() 関数を変更する必要はまったくなく、定義されている場所にデコレーターを追加するだけで、呼び出しは以前と同じであることがわかりましたか。他の同様の関数がある場合は、呼び出しを続けることができます。デコレータを使用すると、関数を繰り返し変更したり、新しいパッケージを追加したりすることなく、関数を修飾できます。これにより、プログラムの再利用性が向上し、可読性が向上します。

Python でデコレーターが非常に便利な理由は、Python 関数は通常のオブジェクトと同様にパラメーターとして他の関数に渡すことができ、他の変数に代入でき、戻り値として使用でき、別の関数で定義できるためです。内部。

*args、**kwargs

ビジネス ロジック関数 foo にパラメータが必要な場合はどうなるのかと疑問に思う人もいるかもしれません。例:

def use_logging(func):

    def wrapper():
        logging.warn("%s is running" % func.__name__)
        return func()   # 把 foo 当做参数传递进来时,执行func()就相当于执行foo()
    return wrapperdef foo():
    print('i am foo')foo = use_logging(foo)  # 因为装饰器 use_logging(foo) 返回的时函数对象 wrapper,这条语句相当于  foo = wrapperfoo()                   # 执行foo()就相当于执行 wrapper()
ログイン後にコピー

ラッパー関数を定義するときにパラメーターを指定できます:

def wrapper(name):
        logging.warn("%s is running" % func.__name__)
        return func(name)
    return wrapper
ログイン後にコピー

这样 foo 函数定义的参数就可以定义在 wrapper 函数中。这时,又有人要问了,如果 foo 函数接收两个参数呢?三个参数呢?更有甚者,我可能传很多个。当装饰器不知道 foo 到底有多少个参数时,我们可以用 *args 来代替:

def wrapper(*args):
        logging.warn("%s is running" % func.__name__)
        return func(*args)
    return wrapper
ログイン後にコピー

如此一来,甭管 foo 定义了多少个参数,我都可以完整地传递到 func 中去。这样就不影响 foo 的业务逻辑了。这时还有读者会问,如果 foo 函数还定义了一些关键字参数呢?比如:

def foo(name, age=None, height=None):
    print("I am %s, age %s, height %s" % (name, age, height))
ログイン後にコピー

这时,你就可以把 wrapper 函数指定关键字函数:

def wrapper(*args, **kwargs):
        # args是一个数组,kwargs一个字典
        logging.warn("%s is running" % func.__name__)
        return func(*args, **kwargs)
    return wrapper
ログイン後にコピー

带参数的装饰器

装饰器还有更大的灵活性,例如带参数的装饰器,在上面的装饰器调用中,该装饰器接收唯一的参数就是执行业务的函数 foo 。装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。比如,我们可以在装饰器中指定日志的等级,因为不同业务函数可能需要的日志级别是不一样的。

def use_logging(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if level == "warn":
                logging.warn("%s is running" % func.__name__)
            elif level == "info":
                logging.info("%s is running" % func.__name__)
            return func(*args)
        return wrapper

    return decorator@use_logging(level="warn")def foo(name='foo'):
    print("i am %s" % name)foo()
ログイン後にコピー

上面的 use_logging 是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。当我 们使用@use_logging(level="warn")调用的时候,Python 能够发现这一层的封装,并把参数传递到装饰器的环境中。

@use_logging(level="warn")等价于@decorator

类装饰器

没错,装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。

class Foo(object):
    def __init__(self, func):
        self._func = func

    def __call__(self):
        print ('class decorator runing')
        self._func()
        print ('class decorator ending')@Foodef bar():
    print ('bar')bar()
ログイン後にコピー

functools.wraps

使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、__name__、参数列表,先看例子:

# 装饰器def logged(func):
    def with_logging(*args, **kwargs):
        print func.__name__      # 输出 'with_logging'
        print func.__doc__       # 输出 None
        return func(*args, **kwargs)
    return with_logging# 函数@loggeddef f(x):
   """does some math"""
   return x + x * xlogged(f)
ログイン後にコピー

不难发现,函数 f 被with_logging取代了,当然它的docstring,__name__就是变成了with_logging函数的信息了。好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器里面的 func 函数中,这使得装饰器里面的 func 函数也有和原函数 foo 一样的元信息了。

from functools import wrapsdef logged(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print func.__name__      # 输出 'f'
        print func.__doc__       # 输出 'does some math'
        return func(*args, **kwargs)
    return with_logging@loggeddef f(x):
   """does some math"""
   return x + x * x
ログイン後にコピー

装饰器顺序

一个函数还可以同时定义多个装饰器,比如:

@a
@b
@c
def f ():
    pass
ログイン後にコピー

它的执行顺序是从里到外,最先调用最里层的装饰器,最后调用最外层的装饰器,它等效于

f = a(b(c(f)))
ログイン後にコピー

zhijun liu首发于知乎专栏Python之禅,原文地址:https://zhuanlan.zhihu.com/p/24900548

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、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衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

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

SublimeText3 中国語版

SublimeText3 中国語版

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

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

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

LinuxターミナルでPythonバージョンを表示するときに発生する権限の問題を解決する方法は? LinuxターミナルでPythonバージョンを表示するときに発生する権限の問題を解決する方法は? Apr 01, 2025 pm 05:09 PM

LinuxターミナルでPythonバージョンを表示する際の許可の問題の解決策PythonターミナルでPythonバージョンを表示しようとするとき、Pythonを入力してください...

あるデータフレームの列全体を、Python内の異なる構造を持つ別のデータフレームに効率的にコピーする方法は? あるデータフレームの列全体を、Python内の異なる構造を持つ別のデータフレームに効率的にコピーする方法は? Apr 01, 2025 pm 11:15 PM

PythonのPandasライブラリを使用する場合、異なる構造を持つ2つのデータフレーム間で列全体をコピーする方法は一般的な問題です。 2つのデータがあるとします...

プロジェクトの基本と問題駆動型の方法で10時間以内にコンピューター初心者プログラミングの基本を教える方法は? プロジェクトの基本と問題駆動型の方法で10時間以内にコンピューター初心者プログラミングの基本を教える方法は? Apr 02, 2025 am 07:18 AM

10時間以内にコンピューター初心者プログラミングの基本を教える方法は?コンピューター初心者にプログラミングの知識を教えるのに10時間しかない場合、何を教えることを選びますか...

中間の読書にどこでもfiddlerを使用するときにブラウザによって検出されないようにするにはどうすればよいですか? 中間の読書にどこでもfiddlerを使用するときにブラウザによって検出されないようにするにはどうすればよいですか? Apr 02, 2025 am 07:15 AM

fiddlereveryversings for the-middleの測定値を使用するときに検出されないようにする方法

正規表現とは何ですか? 正規表現とは何ですか? Mar 20, 2025 pm 06:25 PM

正規表現は、プログラミングにおけるパターンマッチングとテキスト操作のための強力なツールであり、さまざまなアプリケーションにわたるテキスト処理の効率を高めます。

人気のあるPythonライブラリとその用途は何ですか? 人気のあるPythonライブラリとその用途は何ですか? Mar 21, 2025 pm 06:46 PM

この記事では、numpy、pandas、matplotlib、scikit-learn、tensorflow、django、flask、and requestsなどの人気のあるPythonライブラリについて説明し、科学的コンピューティング、データ分析、視覚化、機械学習、Web開発、Hの使用について説明します。

uvicornは、serving_forever()なしでhttpリクエストをどのように継続的に聞いていますか? uvicornは、serving_forever()なしでhttpリクエストをどのように継続的に聞いていますか? Apr 01, 2025 pm 10:51 PM

UvicornはどのようにしてHTTPリクエストを継続的に聞きますか? Uvicornは、ASGIに基づく軽量のWebサーバーです。そのコア機能の1つは、HTTPリクエストを聞いて続行することです...

文字列を介してオブジェクトを動的に作成し、Pythonでメソッドを呼び出す方法は? 文字列を介してオブジェクトを動的に作成し、Pythonでメソッドを呼び出す方法は? Apr 01, 2025 pm 11:18 PM

Pythonでは、文字列を介してオブジェクトを動的に作成し、そのメソッドを呼び出す方法は?これは一般的なプログラミング要件です。特に構成または実行する必要がある場合は...

See all articles