私は最近 Python を学習しています。以下は Python 学習グループで紹介されている内容です。今すぐ学習してさらに練習するのが良い方法です。
Python には強力で思いやりのある機能がたくさんあります。最も人気のあるランキングをリストしたい場合、デコレータは間違いなくその中に含まれます。
初めてデコレーターに会ったときは、エレガントで魔法のように感じますが、それを自分の手で実現したいと思うと、プルダの氷の美しさのように、常に距離感を感じます。これは多くの場合、デコレータを理解するときに他の概念が混在していることが原因です。ベールの層を取り除くと、純粋な装飾が実際には非常にシンプルで単純であることがわかります。
デコレータの原理
デコレータのサンプルをインタプリタ下で実行して直感的に感じてみましょう。
# make_bold はデコレータです。実装メソッドはここでは省略します。
>>> @make_bold ... def get_content(): ... return 'hello world' ... >>> get_content() '<b>hello world</b>'
def get_content(): return 'hello world'
>>> id(get_content) 140090200473112 >>> type(get_content) <class 'function'> >>> get_content <function get_content at 0x7f694aa2be18>
>>> func_name = get_content >>> func_name() 'hello world'
>>> def foo(bar): ... print(bar()) ... return bar ... >>> func = foo(get_content) hello world >>> func() 'hello world'
カスタム関数オブジェクト
クラスを使用して関数オブジェクトを構築できます。メンバ関数呼び出しを持つ関数オブジェクトは、関数オブジェクトの呼び出し時に呼び出される関数オブジェクトです。
class FuncObj(object): def init(self, name): print('Initialize') self.name= name def call(self): print('Hi', self.name)
>>> fo = FuncObj('python') Initialize >>> fo() Hi python
デコレータ @ を使わなくても同じ機能を実現できますが、より多くのコードが必要です。
@make_bold def get_content(): return 'hello world' # 上面的代码等价于下面的 def get_content(): return 'hello world' get_content = make_bold(get_content)
4. クラスを使用してデコレーターを実装します
入力パラメーターが関数オブジェクトであり、戻りパラメーターが関数オブジェクトである場合、ステップ 2 のクラスのコンストラクターが、入力パラメーターが関数オブジェクトである関数オブジェクトに変更されます。 、要件を満たしているだけではないでしょうか? make_bold を実装してみましょう。
class make_bold(object): def init(self, func): print('Initialize') self.func = func def call(self): print('Call') return '<b>{}</b>'.format(self.func())
>>> @make_bold ... def get_content(): ... return 'hello world' ... Initialize >>> get_content() Call '<b>hello world</b>'
ここでは、以前に強調した構築と呼び出しの2つのプロセスを分析します。理解しやすくするために、@ 構文のシュガーを削除しましょう。
# 構築、デコレーターを使用すると、関数オブジェクトが構築され、init が呼び出されます>>> get_content = make_bold(get_content) Initialize # 调用,实际上直接调用的是make_bold构造出来的函数对象 >>> get_content() Call '<b>hello world</b>'
この時点で、花を完成させたら、Web ページを閉じることができます~~~(デコレータの原理を知りたいだけです)
デコレータの関数版
ソースコードを読んでいると、ネストされた関数で実装されているデコレータをよく見かけます。必要な手順は 4 つだけです。
1. defの関数オブジェクトの初期化クラスで実装された関数オブジェクトがいつ構築されるかはわかりやすいのですが、defで定義された関数オブジェクトはいつ構築されるのでしょうか?# ここでのグローバル変数は無関係なコンテンツを削除します
>>> globals() {} >>> def func(): ... pass ... >>> globals() {'func': <function func at 0x10f5baf28>}
一部のコンパイル言語とは異なり、関数はプログラムの開始時にすでに構築されています。上の例からわかるように、関数オブジェクトは def が実行されて変数 make_bold に代入されるまで構築されます。
class NoName(object): def call(self): pass func = NoName()
2. ネストされた関数
def outer(): print('Before def:', locals()) def inner(): pass print('After def:', locals()) return inner
innerはouter内で定義されているため、outerのローカル変数としてカウントされます。関数オブジェクトは def inner が実行されるまで作成されないため、outer が呼び出されるたびに新しい inner が作成されます。以下に示すように、毎回返されるインナーは異なります。
>>> outer() Before def: {} After def: {'inner': <function outer.<locals>.inner at 0x7f0b18fa0048>} <function outer.<locals>.inner at 0x7f0b18fa0048> >>> outer() Before def: {} After def: {'inner': <function outer.<locals>.inner at 0x7f0b18fa00d0>} <function outer.<locals>.inner at 0x7f0b18fa00d0>
3. 終わりに
def outer(): msg = 'hello world' def inner(): print(msg) return inner
次のテストは、 inner が external のローカル変数 msg にアクセスできることを示しています。
>>> func = outer() >>> func() hello world
Closure には 2 つの特徴があります
1. inner は、outer 関数とその祖先関数の名前空間内の変数 (ローカル変数、
) にアクセスできます。
2. 外部への呼び出しは返されましたが、その名前空間は返された内部オブジェクトによって参照されているため、まだリサイクルされません。 この部分をさらに詳しく知りたい場合は、Python の LEGB ルールについて学ぶことができます。
def make_bold(func): print('Initialize') def wrapper(): print('Call') return '<b>{}</b>'.format(func()) return wrapper
用法跟类实现的装饰器一样。可以去掉 @ 语法糖分析下 构造 和 调用 的时机。
>>> @make_bold ... def get_content(): ... return 'hello world' ... Initialize >>> get_content() Call '<b>hello world</b>'
因为返回的 wrapper 还在引用着,所以存在于 make_bold 命名空间的 func 不会消失。 make_bold 可以装饰多个函数, wrapper 不会调用混淆,因为每次调用 make_bold ,都会有创建新的命名空间和新的 wrapper 。
到此函数实现装饰器也理清楚了,完结撒花,可以关掉网页了~~~(后面是使用装饰的常见问题)
常见问题
1. 怎么实现带参数的装饰器?
带参数的装饰器,有时会异常的好用。我们看个例子。
>>> @make_header(2) ... def get_content(): ... return 'hello world' ... >>> get_content() '<h2>hello world</h2>'
怎么做到的呢?其实这跟装饰器语法没什么关系。去掉 @ 语法糖会变得很容易理解。
@make_header(2) def get_content(): return 'hello world' # 等价于 def get_content(): return 'hello world' unnamed_decorator = make_header(2) get_content = unnamed_decorator(get_content)
上面代码中的 unnamed_decorator 才是真正的装饰器, make_header 是个普通的函数,它的返回值是装饰器。
来看一下实现的代码。
def make_header(level): print('Create decorator') # 这部分跟通常的装饰器一样,只是wrapper通过闭包访问了变量level def decorator(func): print('Initialize') def wrapper(): print('Call') return '<h{0}>{1}</h{0}>'.format(level, func()) return wrapper # make_header返回装饰器 return decorator
看了实现代码,装饰器的 构造 和 调用 的时序已经很清楚了。
>>> @make_header(2) ... def get_content(): ... return 'hello world' ... Create decorator Initialize >>> get_content() Call '<h2>hello world</h2>'
2. 如何装饰有参数的函数?
为了有条理地理解装饰器,之前例子里的被装饰函数有意设计成无参的。我们来看个例子。
@make_bold def get_login_tip(name): return 'Welcome back, {}'.format(name)
最直接的想法是把 get_login_tip 的参数透传下去。
class make_bold(object): def init(self, func): self.func = func def call(self, name): return '<b>{}</b>'.format(self.func(name))
如果被装饰的函数参数是明确固定的,这么写是没有问题的。但是 make_bold 明显不是这种场景。它既需要装饰没有参数的 get_content ,又需要装饰有参数的 get_login_tip 。这时候就需要可变参数了。
class make_bold(object): def init(self, func): self.func = func def call(self, *args, **kwargs): return '<b>{}</b>'.format(self.func(*args, **kwargs))
当装饰器不关心被装饰函数的参数,或是被装饰函数的参数多种多样的时候,可变参数非常合适。可变参数不属于装饰器的语法内容,这里就不深入探讨了。
3. 一个函数能否被多个装饰器装饰?
下面这么写合法吗?
@make_italic @make_bold def get_content(): return 'hello world'
合法。上面的的代码和下面等价,留意一下装饰的顺序。
def get_content(): return 'hello world' get_content = make_bold(get_content) # 先装饰离函数定义近的 get_content = make_italic(get_content)
4. functools.wraps 有什么用?
Python的装饰器倍感贴心的地方是对调用方透明。调用方完全不知道也不需要知道调用的函数被装饰了。这样我们就能在调用方的代码完全不改动的前提下,给函数patch功能。
为了对调用方透明,装饰器返回的对象要伪装成被装饰的函数。伪装得越像,对调用方来说差异越小。有时光伪装函数名和参数是不够的,因为Python的函数对象有一些元信息调用方可能读取了。为了连这些元信息也伪装上, functools.wraps 出场了。它能用于把被调用函数的 module , name , qualname , doc , annotations 赋值给装饰器返回的函数对象。
import functools def make_bold(func): @functools.wraps(func) def wrapper(*args, **kwargs): return '<b>{}</b>'.format(func(*args, **kwargs)) return wrapper
对比一下效果。
>>> @make_bold ... def get_content(): ... '''Return page content''' ... return 'hello world' # 不用functools.wraps的结果 >>> get_content.name 'wrapper' >>> get_content.doc >>> # 用functools.wraps的结果 >>> get_content.name 'get_content' >>> get_content.doc 'Return page content'
实现装饰器时往往不知道调用方会怎么用,所以养成好习惯加上 functools.wraps 吧。
这次是真·完结了,撒花吧~~~
以上がPython でのデコレータの使用の概要の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。