ホームページ  >  記事  >  バックエンド開発  >  Python の詳細な学習 Decorators_python

Python の詳細な学習 Decorators_python

不言
不言オリジナル
2018-04-08 11:11:361224ブラウズ

この記事では、主に Python デコレータ関連の情報を詳しく説明します。デコレータが従う原則には、一定の参考値があります。興味のある方は、

デコレータとは

を参照してください。当社のソフトウェア製品をアップグレードするとき、同じ機能の各機能に新しい機能を追加する必要があることがよくあります。この状況は非常によくあることです。これを 1 つずつ変更すると、プログラマは死んでしまいます (誰かが言いました、バカ、関数定義を変更してください! 目を覚ましてください、クラスメート、新しく追加されたものはどうなるでしょう)関数はパラメータまたは戻り値を変更しますか?)今度は、デコレーターがその魔法を披露するときです。デコレータは、元の関数の呼び出し形式を変えることなく、関数に新しい関数を追加する機能(つまり、関数の透過的な処理)を実現します。その実装方法と実装原理については、以下で詳しく説明します。

デコレータが従う原則

デコレータは、名前が示すように、装飾の役割を果たします。装飾されたオブジェクトはそのままであり、まったく変更することはできません。 ここで、デコレータを書く際には、変更した関数のソースコードは変更できないという鉄則を理解しておく必要があります。 この鉄則に従うには、まだいくつかの準備が必要です。 まず、次の 3 つの概念を理解する必要があります:

関数名は「変数」です

Python では、関数名は実際には次のようになります。これは C 言語の関数ポインタであり、関数アドレスを表します。インタプリタがこのアドレスを取得した場合にのみ、このメモリ内のコードが実行されます。したがって、基本的に、関数名と通常の変数が指すメモリが異なる方法で使用されることを除いて、関数名はさまざまな変数と変わりません。これらは、プログラマにとって、基礎となるインタプリタのメカニズムによって決定されます。どちらも透明なので、両者に違いはないと考えて良いでしょう。

高階関数

高階関数とは実際には非常に簡単で、2つの原則を理解するだけです:

  • 仮パラメータには関数名がある

  • 戻り値には関数名がある

この 2 つの原則のいずれかが満たされていれば、それを高階関数と呼ぶことができます。振り返ってみると、上で説明した関数名がここに表示されます。よく考えてみると、ここでは実際のパラメータとして扱っているのではありませんか。

入れ子関数番号

入れ子関数とは実際には非常に簡単で、1つの原則を理解するだけです:

  • 関数の関数本体で別の関数を定義する

何ここで強調しておく必要があるのは、変数が定義されたときに変数の内容が読み取られないのと同様に、関数が定義されたときに関数本体は実行されないということです。これは非常に重要であり、デコレータ実装の原理を理解するのに非常に役立ちます。

デコレータの書き方

上記の伏線を踏まえて、より分かりやすくデコレータの書き方を詳しく説明していきます。

デコレータの本質

実際、デコレータは本質的には関数であり、関数名、パラメータ、戻り値も持っています。しかし、Python では、それを表すために「@auth」を使用します。

@auth    # 其等价于:func = auth(func)
def func():
  print("func called")

この例は、Python で func 関数の形式を装飾する方法です。もちろん、デコレーター関数はまだ実装されていません。注意すべきことは、コメントに書かれている内容です:

  • デコレーター関数は実際には高階関数です (パラメーターと戻り値の両方が関数名です)。

  • 「auth(func)」はデコレーター関数を呼び出しています。つまり、デコレーター関数の関数本体が実行されることを忘れないでください。

設計アイデア

デコレータは関数であり、上記で紹介した等価関係があるため、次のようにデコレータを設計できます:

  • デコレータの関数内で新しい関数を定義します。本体では、この新しく定義された関数内で変更された関数を呼び出し、同時に変更された関数のコンテキストに新しい関数を追加します。最後に、デコレーター関数の戻り値を使用して、新しく定義した関数の関数名を返します。

  • このことから、「func = auth(func)」の戻り値 func は、デコレーター内で新しく定義された関数の関数名を表していることがわかります。

ここでデコレーターの実装メカニズムを明らかにするために、以前に多くの準備をしました。実際には、それは何もなく、非常に簡単です。

  • 装饰器机制改变了被修饰函数的函数名表示的地址数据。说白了就是,被修饰前,函数名代表的是A内存块;被修饰后,函数名代表的是B内存块;只不过,在执行B内存块时,会调用A内存块罢了。B内存块中的代码就是我们新加的功能。而这种机制的实现,使用了“高阶函数”和“嵌套函数”的机制。

  • 最终的效果就是,但在调用被修饰过的函数时,其实调用的不是原来的内存块,而是修饰器新申请的内存块。

第一步:设计装饰器函数

装饰器函数定义跟普通函数定义没什么区别,关键是函数体怎么写的问题。这里,为了便于理解,先用无参数的装饰器函数说明。

#装饰器函数定义格式
def deco(func):
  '''函数体...'''
return func

这里说的无参数,指的是没有除了“func”之外的参数
难点是函数体的编写,下面的示例先告诉你为什么要有第二步:

#使用语法糖@来装饰函数,相当于“myfunc = deco(myfunc)”
def deco(func):
  print("before myfunc() called.")
  func()
  print("after myfunc() called.")
  return func
 
@deco
def myfunc():
  print("myfunc() called.")
 
myfunc()
myfunc()
 
#output:
before myfunc() called.
myfunc() called.
after myfunc() called.
myfunc() called.
myfunc() called.

由输出结果可以看出,我们的装饰器并没有生效。别跟我说装饰器只生效了一次,那是大家忽略了“@deco”的等效机制。解释到“@deco”时,会解释成“myfunc = deco(myfunc)”。注意了,前面我提到了,这里其实在调用deco函数的,因此,deco的函数体会被执行。所以output的前三行并不是调用myfunc函数时产生的效果,那有怎能说装饰器生效了一次呢?第二步就是解决装饰器没生效的问题的。

第二步:包装被修饰函数

#基本格式
def deco(func):
  def _deco()
    #新增功能
    #...
    #...
    func() #别修饰函数调用
  return_deco

 下面给出个示例:

#使用内嵌包装函数来确保每次新函数都被调用,
#内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象
 
def deco(func):
  def _deco():
    print("before myfunc() called.")
    func()
    print("after myfunc() called.")
    # 不需要返回func,实际上应返回原函数的返回值
  return _deco
 
@deco
def myfunc():
  print("myfunc() called.")
  return 'ok'
 
myfunc()
 
#output:
before myfunc() called.
myfunc() called.
after myfunc() called.

  第三步:被修饰函数参数和返回值透明化处理

当完成了第二步时,其实装饰器已经完成了主要部分,下面就是对被修饰函数的参数和返回值的处理。这样才能真正实现装饰器的铁律。话不多说,直接上代码:

#基本格式
def deco(func):
  def _deco(*args, **kwargs) #参数透明化
    #新增功能
    #...
    #...
    res = func(*args, **kwargs) #别修饰函数调用
    return res #返回值透明化
  return_deco

通过上面的分析知:

参数透明化:当我们在调用被装饰后的函数时,其实调用的时这里的_deco函数。那么,我们就给_deco函数加上可变参数,并把得到的可变参数传递给func函数不就可以了。
返回值透明化:和参数透明化同理,给_deco函数定义返回值,并返回func的返回值就可以了。

透明化处理就是这么简单!至此,我们的装饰器编写完成。给个示例吧:

#对带参数的函数进行装饰,
#内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象
 
def deco(func):
  def _deco(*agrs, **kwagrs):
    print("before myfunc() called.")
    ret = func(*agrs, **kwagrs)
    print(" after myfunc() called. result: %s" % ret)
    return ret
  return _deco
 
@deco
def myfunc(a, b):
  print(" myfunc(%s,%s) called." % (a, b))
  return a + b
 
print("sum=",myfunc(1, 2))
print("sum=",myfunc(3, 4))
 
#output:
before myfunc() called.
 myfunc(1,2) called.
 after myfunc() called. result: 3
sum= 3
before myfunc() called.
 myfunc(3,4) called.
 after myfunc() called. result: 7
sum= 7

装饰器进阶

带参数装饰器

装饰器即然也是函数,那么我们也可以给其传递参数。我这里说的是:“@auth(auth_type = 'type1')”这中形式哟。先上个代码吧:

#基本格式
def deco(deco_type)
  def _deco(func):
    def __deco(*args, **kwargs) #参数透明化
      #新增功能
      #...
      #...
      print("deco_type:",deco_type) #使用装饰器参数
      res = func(*args, **kwargs) #别修饰函数调用
      return res #返回值透明化
    return __deco
  return_deco

 说白了,就是在原来的装饰器的基础上再在最外层套一个deco函数,并用其来接收装饰器参数。由于是在最外层套了一个函数,那么这个函数的形参的作用范围就是函数体内部,所以里面的函数定义中随便用啦,就这么任性。
那怎么理解解释器的解析过程呢?在这里,只要我们明白一点就好,那就是: “@auth(auth_type = 'type1')”等价于“func = auth(auth_type = 'type1')(func)” 解释器会先翻译“auth(auth_type = 'type1')”,再将其返回值(假设给了_func这个不存在的函数名)当作函数指针,这里的_func函数名代表的是_deco,然后再去执行“func = _func(func)”,而这个func函数名代表的其实就是__deco。

至此,就达到了通过装饰器来传参的目的。给个示例吧:

#示例7: 在示例4的基础上,让装饰器带参数,
#和上一示例相比在外层多了一层包装。
#装饰函数名实际上应更有意义些
 
def deco(deco_type):
  def _deco(func):
    def __deco(*args, **kwagrs):
      print("before %s called [%s]." % (func.__name__, deco_type))
      func(*args, **kwagrs)
      print(" after %s called [%s]." % (func.__name__, deco_type))
    return __deco
  return _deco
 
@deco("mymodule")
def myfunc():
  print(" myfunc() called.")
 
@deco("module2")
def myfunc2():
  print(" myfunc2() called.")
 
myfunc()
myfunc2()
 
#output:
before myfunc called [mymodule].
 myfunc() called.
 after myfunc called [mymodule].
before myfunc2 called [module2].
 myfunc2() called.
 after myfunc2 called [module2].

多重装饰器修饰函数

如果说,我上面说的内容都理解了,那么这个东东,就太简单不过了。不就是把我们的是装饰器当中被修饰的函数,对它进行装饰吗?但我在这里还想说的是,我们换个角度看问题。我们的关注点放在原来的被修饰的函数上,就会发现,NB呀,我可以给它添加若干个功能撒。给个示例吧:

def deco(deco_type):
  def _deco(func):
    def __deco(*args, **kwagrs):
      print("before %s called [%s]." % (func.__name__, deco_type))
      func(*args, **kwagrs)
      print(" after %s called [%s]." % (func.__name__, deco_type))
    return __deco
  return _deco
 
@deco("module1")
@deco("mymodule")
def myfunc():
  print(" myfunc() called.")
 
@deco("module2")
def myfunc2():
  print(" myfunc2() called.")
 
myfunc()
 
#output:
before __deco called [module1].
before myfunc called [mymodule].
 myfunc() called.
 after myfunc called [mymodule].
 after __deco called [module1].

 注意结果哟,@deco("module1"),来修饰的deco("mymdule")的,和我们想的是一样的,完美!

相关推荐:

深度理解Python装饰器的概念和含义

Pythonデコレータの徹底解説

以上がPython の詳細な学習 Decorators_pythonの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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