> 백엔드 개발 > 파이썬 튜토리얼 > Python Decorators_python에 대한 심층 연구

Python Decorators_python에 대한 심층 연구

不言
풀어 주다: 2018-04-08 11:11:36
원래의
1315명이 탐색했습니다.

이 글은 주로 Python 데코레이터 관련 정보에 대한 심층적인 연구를 제공합니다. 데코레이터가 따르는 원칙은 특정 참고 가치가 있습니다. 관심 있는 친구는

데코레이터란 무엇입니까

소프트웨어 제품을 업그레이드할 때 각 기능에 동일한 기능을 추가해야 하는 경우가 많습니다. 이런 상황은 매우 흔한 일입니다. 하나씩 수정하면 프로그래머가 죽을 것입니다. (누군가, 바보야, 그냥 함수 정의를 수정해라! 일어나라, 동창, 새로 추가되면 어쩌지? 함수가 매개변수를 수정하거나 값을 반환합니까?) 이제 데코레이터가 마법을 보여줄 차례입니다. 데코레이터는 원래 함수의 호출 형식을 변경하지 않고 함수에 새 함수를 추가하는 기능(즉, 함수의 투명한 처리)을 달성할 수 있습니다. 구현 방법과 구현 원리는 아래에서 자세히 설명하겠습니다.

데코레이터가 따르는 원칙

데코레이터는 이름 그대로 장식의 역할을 하는 사람이므로 장식된 대상은 그대로이며 전혀 변경할 수 없습니다. 여기서 데코레이터를 작성할 때 수정된 함수의 소스 코드는 수정될 수 없다는 철칙을 파악해야 합니다. 이 철칙을 따르는 방법은 여전히 ​​몇 가지 준비가 필요합니다. 먼저 다음과 같은 세 가지 개념을 이해해야 합니다.

함수 이름은 "변수"입니다.

파이썬에서 함수 이름은 실제로 다음과 같습니다. 이는 함수 주소를 나타내는 C 언어의 함수 포인터입니다. 인터프리터가 이 주소를 얻은 경우에만 이 메모리의 코드를 실행합니다. 따라서 본질적으로 함수 이름은 일반 변수가 가리키는 메모리가 다른 방식으로 사용된다는 점을 제외하면 다른 변수와 다르지 않습니다. 이는 프로그래머의 경우 기본 인터프리터의 메커니즘에 따라 결정됩니다. 둘 다 투명하므로 둘 사이에 차이가 없다고 생각할 수 있습니다.

고차 함수

고차 함수란 실제로는 매우 간단합니다. 두 가지 원칙만 이해하면 됩니다.

  • 형식 매개 변수에는 함수 이름이 있습니다

  • 반환 값에는 함수 이름이 있습니다

이 두 가지 원칙 중 하나만 만족한다면 고차함수라고 할 수 있습니다. 돌이켜보면 위에서 언급한 함수 이름이 여기에 나오는군요. 잘 생각해보면 여기서는 실제 매개변수로 취급하고 있지 않나요?

중첩 함수number

중첩 함수란 실제로는 매우 간단합니다. 한 가지 원칙만 파악하세요.

  • 함수의 함수 본문에 다른 함수를 정의하세요

What 여기서 강조할 점은 변수를 정의할 때 변수의 내용을 읽지 않는 것과 마찬가지로 함수를 정의할 때 함수 본문이 실행되지 않는다는 점입니다. 이는 데코레이터 구현의 원리를 이해하는 데 중요하고 매우 도움이 됩니다.

데코레이터 작성 방법

위의 예를 통해 데코레이터 작성 방법을 좀 더 이해하기 쉽도록 지금부터 자세히 설명하겠습니다.

데코레이터의 본질

사실 데코레이터는 본질적으로 함수이며 함수 이름, 매개변수 및 반환 값도 가지고 있습니다. 하지만 파이썬에서는 이를 표현하기 위해 "@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 Decorators_python에 대한 심층 연구의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿