AOPとはアスペクト指向プログラミングで、簡単に言うとアスペクト指向で指定されたメソッドやクラスの指定位置にコードを動的に切り出すプログラミングの考え方です。プログラミング。
指定されたクラスの指定されたメソッドに切り込むコードスニペットをアスペクトと呼び、どのクラスやメソッドに切り込まれるのかをエントリポイントと呼びます。このようにして、複数のクラスに共通するコードをスライスに抽出し、必要に応じてオブジェクトに切り込むことで、元の動作を変更できます。
この種の考え方は、元のコードのロジックをより明確にし、元のコードを非侵襲にすることができ、権限管理、ログ記録、トランザクション管理などによく使用されます。
Python のデコレータは非常に有名な設計であり、アスペクト要件のあるシナリオでよく使用されます。
たとえば、Django は、権限制御、コンテンツ フィルタリング、リクエスト管理などの要件のさまざまな側面を完了するために、多数のデコレータを使用します。
Python デコレーター (関数デコレーター) は、元の関数名やクラス名を変更しないことを目的として、元の関数の機能を拡張するために使用される関数です。関数に新しい機能を追加します。
デコレーターが私とどのように協力するかについて詳しく学びましょう。
まず、概念を明確にする必要があります。Python ではすべてがオブジェクトであり、関数もオブジェクトです。
つまり、オブジェクトとしての関数を別の関数で定義することができます。次の例を見てください:
def a(): def b(): print("I'm b") b() c = b return c d = a() d() b() c()
出力結果は次のとおりです:
I'm b
I'm b
Throws NameError: name ' b' は定義されていませんエラー
throws NameError: 名前 'c' は定義されていませんエラー
上記からわかるように、関数はオブジェクトであるため、次のようになります:
変数に代入可能
別の関数で定義可能
return 関数オブジェクトを返すことができます。この関数オブジェクトは別の関数内で定義されています。スコープが異なるため、
return によって返される関数のみを呼び出すことができ、関数
a で定義および割り当てられた関数
b および
c は外部的に動作します。 . ドメインを呼び出すことができません。
return できることを意味します。
関数オブジェクトは、オブジェクトとして返されるだけでなく、パラメーターとして別の関数に渡すこともできます:
def a(): print("I'm a") def b(func): print("I'm b") func() b(a)
出力結果:
I'm bOK、関数のこれらの特性に基づいて、変更せずに使用するデコレーターを作成できます。元の関数の場合、関数が実装されます。 関数デコレータたとえば、関数の実行前後に他の操作を実行したい場合は、上記の関数に従って、それをパラメーターとして渡すことができます。このように実装します。以下の例を参照してください。 :I'm a
def a(): print("I'm a") def b(func): print('在函数执行前,做一些操作') func() print("在函数执行后,做一些操作") b(a)
出力結果:
関数が実行される前に、いくつかの操作を行いますただし、この場合、元の関数は別の関数になります。関数が追加されるたびに、関数の新しい層をラップする必要があります外部にあるため、元の呼び出し場所をすべて変更する必要があります。これは明らかに不便であり、関数の動作を変更する方法があるのではないかと疑問に思うかもしれませんが、名前は変更されず、引き続き使用されます。本来の機能として。I' m a
関数が実行された後、いくつかの操作を実行します。 操作
次の例を見てください:
def a(): print("I'm a") def c(func): def b(): print('在函数执行前,做一些操作') func() print("在函数执行后,做一些操作") return b a = c(a) a()
出力結果:
関数が実行される前に、次のようにします。いくつかの操作b## を返すことができます# オブジェクトとして。このように、関数I'm a
#上記のように、関数を別のレイヤーでラップし、新しい関数
関数が実行された後、いくつかの操作を実行します
c を通じて、
a が変更されて
a に再割り当てされるため、関数
a が変更され、同じ関数
が使用されます。 a を呼び出します。
しかし、これは再代入が必要なため非常に書くのが不便なので、Python では
@
例を見てください:
def c(func): def b(): print('在函数执行前,做一些操作') func() print("在函数执行后,做一些操作") return b @c def a(): print("I'm a") a()
関数が実行される前にいくつかの操作を実行します。
I'm a関数が実行された後、いくつかの操作を行いますを通じて、関数@c
上記のように、
a は次のように実装されます。パラメータとして
c を渡し、返された関数を関数
a として再キャストします。この
c も単純な関数デコレータです。
また、関数に戻り値がある場合は、次のように、変更された関数にも戻り値が必要です:
def c(func): def b(): print('在函数执行前,做一些操作') result = func() print("在函数执行后,做一些操作") return result return b @c def a(): print("函数执行中。。。") return "I'm a" print(a())
関数を実行する前にいくつかの操作を行ってください。
関数は実行中です。 。 。関数が実行された後、いくつかの操作を実行します私は
です
如上所示:通过将返回值进行传递,就可以实现函数执行前后的操作。但是你会发现一个问题,就是为什么输出 I'm a
会在最后才打印出来?
因为 I'm a
是返回的结果,而实际上函数是 print("在函数执行后,做一些操作")
这一操作前运行的,只是先将返回的结果给到了 result
,然后 result
传递出来,最后由最下方的 print(a())
打印了出来。
那如何函数 a
带参数怎么办呢?很简单,函数 a
带参数,那么我们返回的函数也同样要带参数就好啦。
看下面示例:
def c(func): def b(name, age): print('在函数执行前,做一些操作') result = func(name, age) print("在函数执行后,做一些操作") return result return b @c def a(name, age): print("函数执行中。。。") return "我是 {}, 今年{}岁 ".format(name, age) print(a('Amos', 24))
输出结果:
在函数执行前,做一些操作
函数执行中。。。
在函数执行后,做一些操作
我是 Amos, 今年24岁
但是又有问题了,我写一个装饰器 c
,需要装饰多个不同的函数,这些函数的参数各不相同,那么怎么办呢?简单,用 *args
和 **kwargs
来表示所有参数即可。
如下示例:
def c(func): def b(*args, **kwargs): print('在函数执行前,做一些操作') result = func(*args, **kwargs) print("在函数执行后,做一些操作") return result return b @c def a(name, age): print('函数执行中。。。') return "我是 {}, 今年{}岁 ".format(name, age) @c def d(sex, height): print('函数执行中。。。') return '性别:{},身高:{}'.format(sex, height) print(a('Amos', 24)) print(d('男', 175))
输出结果:
在函数执行前,做一些操作
函数执行中。。。
在函数执行后,做一些操作
我是 Amos, 今年24岁在函数执行前,做一些操作
函数执行中。。。
在函数执行后,做一些操作
性别:男,身高:175
如上就解决了参数的问题,哇,这么好用。那是不是这样就没有问题了?并不是!经过装饰器装饰后的函数,实际上已经变成了装饰器函数 c
中定义的函数 b
,所以函数的元数据则全部改变了!
如下示例:
def c(func): def b(*args, **kwargs): print('在函数执行前,做一些操作') result = func(*args, **kwargs) print("在函数执行后,做一些操作") return result return b @c def a(name, age): print('函数执行中。。。') return "我是 {}, 今年{}岁 ".format(name, age) print(a.__name__)
输出结果:
b
会发现函数实际上是函数 b
了,这就有问题了,那么该怎么解决呢,有人就会想到,可以在装饰器函数中先把原函数的元数据保存下来,在最后再讲 b
函数的元数据改为原函数的,再返回 b
。这样的确是可以的!但我们不这样用,为什么?
因为 Python 早就想到这个问题啦,所以给我们提供了一个内置的方法,来自动实现原数据的保存和替换工作。哈哈,这样就不同我们自己动手啦!
看下面示例:
from functools import wraps def c(func): @wraps(func) def b(*args, **kwargs): print('在函数执行前,做一些操作') result = func(*args, **kwargs) print("在函数执行后,做一些操作") return result return b @c def a(name, age): print('函数执行中。。。') return "我是 {}, 今年{}岁 ".format(name, age) print(a.__name__)
输出结果:
a
使用内置的 wraps
装饰器,将原函数作为装饰器参数,实现函数原数据的保留替换功能。
耶!装饰器还可以带参数啊,你看上面 wraps
装饰器就传入了参数。哈哈,是的,装饰器还可以带参数,那怎么实现呢?
看下面示例:
from functools import wraps def d(name): def c(func): @wraps(func) def b(*args, **kwargs): print('装饰器传入参数为:{}'.format(name)) print('在函数执行前,做一些操作') result = func(*args, **kwargs) print("在函数执行后,做一些操作") return result return b return c @d(name='我是装饰器参数') def a(name, age): print('函数执行中。。。') return "我是 {}, 今年{}岁 ".format(name, age) print(a('Amos', 24))
输出结果:
装饰器传入参数为:我是装饰器参数
在函数执行前,做一些操作
函数执行中。。。
在函数执行后,做一些操作
我是 Amos, 今年24岁
如上所示,很简单,只需要在原本的装饰器之上,再包一层,相当于先接收装饰器参数,然后返回一个不带参数的装饰器,然后再将函数传入,最后返回变化后的函数。
这样就可以实现很多功能了,这样可以根据传给装饰器的参数不同,来分别实现不同的功能。
另外,可能会有人问, 可以在同一个函数上,使用多个装饰器吗?答案是:可以!
看下面示例:
from functools import wraps def d(name): def c(func): @wraps(func) def b(*args, **kwargs): print('装饰器传入参数为:{}'.format(name)) print('我是装饰器d: 在函数执行前,做一些操作') result = func(*args, **kwargs) print("我是装饰器d: 在函数执行后,做一些操作") return result return b return c def e(name): def c(func): @wraps(func) def b(*args, **kwargs): print('装饰器传入参数为:{}'.format(name)) print('我是装饰器e: 在函数执行前,做一些操作') result = func(*args, **kwargs) print("我是装饰器e: 在函数执行后,做一些操作") return result return b return c @e(name='我是装饰器e') @d(name='我是装饰器d') def func_a(name, age): print('函数执行中。。。') return "我是 {}, 今年{}岁 ".format(name, age) print(func_a('Amos', 24)) 行后,做一些操作 我是 Amos, 今年24岁
输出结果:
装饰器传入参数为:我是装饰器e
我是装饰器e: 在函数执行前,做一些操作装饰器传入参数为:我是装饰器d
我是装饰器d: 在函数执行前,做一些操作
函数执行中。。。
我是装饰器d: 在函数执行后,做一些操作我是装饰器e: 在函数执
如上所示,当两个装饰器同时使用时,可以想象成洋葱,最下层的装饰器先包装一层,然后一直到最上层装饰器,完成多层的包装。然后执行时,就像切洋葱,从最外层开始,只执行到被装饰函数运行时,就到了下一层,下一层又执行到函数运行时到下一层,一直到执行了被装饰函数后,就像切到了洋葱的中间,然后再往下,依次从最内层开始,依次执行到最外层。
示例:
当一个函数 a
被 b
,c
,d
三个装饰器装饰时,执行顺序如下图所示,多个同理。
@d @c @b def a(): pass
在函数装饰器方面,很多人搞不清楚,是因为装饰器可以用函数实现(像上面),也可以用类实现。因为函数和类都是对象,同样可以作为被装饰的对象,所以根据被装饰的对象不同,一同有下面四种情况:
函数装饰函数
类装饰函数
函数装饰类
类装饰类
下面我们依次来说明一下这四种情况的使用。
from functools import wraps def d(name): def c(func): @wraps(func) def b(*args, **kwargs): print('装饰器传入参数为:{}'.format(name)) print('在函数执行前,做一些操作') result = func(*args, **kwargs) print("在函数执行后,做一些操作") return result return b return c @d(name='我是装饰器参数') def a(name, age): print('函数执行中。。。') return "我是 {}, 今年{}岁 ".format(name, age) print(a.__class__) # 输出结果: <class 'function'>
此为最常见的装饰器,用于装饰函数,返回的是一个函数。
也就是通过类来实现装饰器的功能而已。通过类的 __call__
方法实现:
from functools import wraps class D(object): def __init__(self, name): self._name = name def __call__(self, func): @wraps(func) def wrapper(*args, **kwargs): print('装饰器传入参数为:{}'.format(self._name)) print('在函数执行前,做一些操作') result = func(*args, **kwargs) print("在函数执行后,做一些操作") return result return wrapper @D(name='我是装饰器参数') def a(name, age): print('函数执行中。。。') return "我是 {}, 今年{}岁 ".format(name, age) print(a.__class__) # 输出结果: <class 'function'>
以上所示,只是将用函数定义的装饰器改为使用类来实现而已。还是用于装饰函数,因为在类的 __call__
中,最后返回的还是一个函数。
此为带装饰器参数的装饰器实现方法,是通过 __call__
方法。
若装饰器不带参数,则可以将 __init__
方法去掉,但是在使用装饰器时,需要 @D()
这样使用,如下:
from functools import wraps class D(object): def __call__(self, func): @wraps(func) def wrapper(*args, **kwargs): print('在函数执行前,做一些操作') result = func(*args, **kwargs) print("在函数执行后,做一些操作") return result return wrapper @D() def a(name, age): print('函数执行中。。。') return "我是 {}, 今年{}岁 ".format(name, age) print(a.__class__) # 输出结果: <class 'function'>
如上是比较方便简答的,使用类定义函数装饰器,且返回对象为函数,元数据保留。
下面重点来啦,我们常见的装饰器都是用于装饰函数的,返回的对象也是一个函数,而要装饰类,那么返回的对象就要是类,且类的元数据等也要保留。
不怕丢脸的说,目前我还不知道怎么实现完美的类装饰器,在装饰类的时候,一般有两种方法:
返回一个函数,实现类在创建实例的前后执行操作,并正常返回此类的实例。但是这样经过装饰器的类就属于函数了,其无法继承,但可以正常调用创建实例。
如下:
from functools import wraps def d(name): def c(cls): @wraps(cls) def b(*args, **kwargs): print('装饰器传入参数为:{}'.format(name)) print('在类初始化前,做一些操作') instance = cls(*args, **kwargs) print("在类初始化后,做一些操作") return instance return b return c @d(name='我是装饰器参数') class A(object): def __init__(self, name, age): self.name = name self.age = age print('类初始化实例,{} {}'.format(self.name, self.age)) a = A('Amos', 24) print(a.__class__) print(A.__class__) # 输出结果: 装饰器传入参数为:我是装饰器参数 在类初始化前,做一些操作 类初始化实例,Amos 24 在类初始化后,做一些操作 <class '__main__.A'> <class 'function'>
如上所示,就是第一种方法。
接上文,返回一个类,实现类在创建实例的前后执行操作,但类已经改变了,创建的实例也已经不是原本类的实例了。
看下面示例:
def desc(name): def decorator(aClass): class Wrapper(object): def __init__(self, *args, **kwargs): print('装饰器传入参数为:{}'.format(name)) print('在类初始化前,做一些操作') self.wrapped = aClass(*args, **kwargs) print("在类初始化后,做一些操作") def __getattr__(self, name): print('Getting the {} of {}'.format(name, self.wrapped)) return getattr(self.wrapped, name) def __setattr__(self, key, value): if key == 'wrapped': # 这里捕捉对wrapped的赋值 self.__dict__[key] = value else: setattr(self.wrapped, key, value) return Wrapper return decorator @desc(name='我是装饰器参数') class A(object): def __init__(self, name, age): self.name = name self.age = age print('类初始化实例,{} {}'.format(self.name, self.age)) a = A('Amos', 24) print(a.__class__) print(A.__class__) print(A.__name__)
输出结果:
装饰器传入参数为:我是装饰器参数
在类初始化前,做一些操作
类初始化实例,Amos 24
在类初始化后,做一些操作.decorator. .Wrapper'>
Wrapper
如上,看到了吗,通过在函数中新定义类,并返回类,这样函数还是类,但是经过装饰器后,类 A
已经变成了类 Wrapper
,且生成的实例 a
也是类 Wrapper
的实例,即使通过 __getattr__
和 __setattr__
两个方法,使得实例a的属性都是在由类 A
创建的实例 wrapped
的属性,但是类的元数据无法改变。很多内置的方法也就会有问题。我个人是不推荐这种做法的!
所以,我推荐在代码中,尽量避免类装饰器的使用,如果要在类中做一些操作,完全可以通过修改类的魔法方法,继承,元类等等方式来实现。如果避免不了,那也请谨慎处理。
以上がPython でアスペクト指向プログラミング AOP とデコレーターを使用する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。