>  기사  >  백엔드 개발  >  Python의 데코레이터 사용 요약

Python의 데코레이터 사용 요약

零下一度
零下一度원래의
2017-06-29 15:34:321190검색

저는 최근 Python을 배우고 있습니다. Python 학습 그룹에 소개된 내용입니다. 지금 배우고 더 많이 연습하는 것이 좋은 학습 방법이 될 것입니다.

Python에는 강력하고 사려 깊은 기능이 많이 있습니다. 가장 인기 있는 순위를 나열하고 싶다면 데코레이터가 확실히 그 순위에 포함될 것입니다.

데코레이터를 처음 만나면 우아함과 마법 같은 느낌이 들지만, 그것을 직접 손으로 구현하고 싶을 때는 마치 퍼다의 얼음 아름다움처럼 항상 거리감이 느껴집니다. 이는 데코레이터를 이해할 때 다른 개념이 혼합되어 있기 때문인 경우가 많습니다. 베일의 층을 제거하면 순수한 장식이 실제로 매우 단순하고 간단하다는 것을 알 수 있습니다.

데코레이터의 원리

인터프리터 아래에서 데코레이터의 예를 실행하여 직관적으로 느껴보세요.
# make_bold는 데코레이터입니다.


>>> @make_bold
... def get_content():
...  return 'hello world'
...
>>> get_content()
&#39;<b>hello world</b>&#39;

get_content 호출 후 반환된 결과는 자동으로 b 태그로 래핑됩니다. 어떻게 해야 할까요? 간단한 4단계로 이해할 수 있습니다.

1. 함수는 객체입니다.

get_content 함수를 정의합니다. 이때 get_content도 객체이므로 모든 객체에 대해 작업을 수행할 수 있습니다.


def get_content():
  return &#39;hello world&#39;

ID, 유형, 값이 있습니다.


>>> id(get_content)
140090200473112
>>> type(get_content)
<class &#39;function&#39;>
>>> get_content
<function get_content at 0x7f694aa2be18>

은 다른 객체와 마찬가지로 다른 변수에 할당될 수 있습니다.


>>> func_name = get_content
>>> func_name()
&#39;hello world&#39;

매개변수 또는 반환 값으로 전달될 수 있습니다


>>> def foo(bar):
...   print(bar())
...   return bar
...
>>> func = foo(get_content)
hello world
>>> func()
&#39;hello world&#39;

2. 사용자 정의 함수객체

클래스를 사용하여 함수객체를 구성할 수 있습니다. 멤버 함수 호출이 있는 함수 개체는 함수 개체가 호출될 때 호출되는 함수 개체입니다.


class FuncObj(object):
  def init(self, name):
    print(&#39;Initialize&#39;)
    self.name= name

  def call(self):
    print(&#39;Hi&#39;, self.name)

전화해서 알아보자. 보시다시피, 함수 객체의 사용은 구성과 호출의 두 단계로 나뉩니다. (학생 여러분, 주의하세요. 이것이 테스트 포인트입니다.)


>>> fo = FuncObj(&#39;python&#39;)
Initialize
>>> fo()
Hi python

3. @는 구문상의 설탕입니다

데코레이터 @는 특별한 기능을 수행하지 않지만 더 많은 코드가 필요합니다.


@make_bold
def get_content():
  return &#39;hello world&#39;

# 上面的代码等价于下面的

def get_content():
  return &#39;hello world&#39;
get_content = make_bold(get_content)

make_bold는 입력 매개변수가 함수 개체이고 반환 값이 함수 개체여야 하는 함수입니다. @의 구문 설탕은 실제로 위 코드의 마지막 줄을 제거하여 더 읽기 쉽게 만듭니다. 데코레이터를 사용한 후 get_content가 호출될 때마다 make_bold가 반환한 함수 개체가 실제로 호출됩니다.

4. 클래스를 사용하여 데코레이터 구현

입력 매개변수는 함수 개체이고, 반환 매개변수는 함수 개체입니다. 2단계에서 클래스의 생성자가 입력 매개변수가 함수 개체인 함수 개체로 변경된 경우입니다. , 요구 사항을 충족하지 않습니까? make_bold를 구현해 보겠습니다.


class make_bold(object):
  def init(self, func):
    print(&#39;Initialize&#39;)
    self.func = func

  def call(self):
    print(&#39;Call&#39;)
    return &#39;<b>{}</b>&#39;.format(self.func())

완료되었습니다. 작동하는지 확인해 보겠습니다.


>>> @make_bold
... def get_content():
...   return &#39;hello world&#39;
...
Initialize
>>> get_content()
Call
&#39;<b>hello world</b>&#39;

데코레이터를 성공적으로 구현했습니다! 아주 간단하지 않나요?

앞서 강조했던 구성과 부르심의 두 가지 과정을 분석해 보겠습니다. 이해하기 쉽도록 @ 구문 설탕을 제거하겠습니다.
# 구성, 데코레이터를 사용하면 함수 객체가 구성되고 init가 호출됩니다.


>>> get_content = make_bold(get_content)
Initialize

# 调用,实际上直接调用的是make_bold构造出来的函数对象
>>> get_content()
Call
&#39;<b>hello world</b>&#39;

이 시점에서 완전히 명확해지면 웹 페이지를 닫아도 됩니다. 데코레이터의 원리를 알고 싶습니다 )

데코레이터의 기능적 버전

소스 코드를 읽을 때 데코레이터가 중첩된 함수로 구현되는 경우가 종종 있습니다. 이를 어떻게 이해합니까? 4단계만 거치면 됩니다.

1. def의 함수 객체 초기화

클래스로 구현된 함수 객체가 언제 생성되는지 쉽게 알 수 있는데, def로 정의된 함수 객체는 언제 생성되는 걸까요?
# 여기서 전역 변수는 관련 없는 내용을 삭제합니다


>>> globals()
{}
>>> def func():
...   pass
...
>>> globals()
{&#39;func&#39;: <function func at 0x10f5baf28>}

일부 컴파일 언어와 달리 프로그램 시작 시 함수가 이미 구성되어 있습니다. 위의 예에서 볼 수 있듯이 def가 실행되어 make_bold 변수에 할당될 때까지 함수 개체가 생성됩니다.

이 코드의 효과는 아래 코드와 매우 유사합니다.


class NoName(object):
  def call(self):
    pass

func = NoName()

2. 중첩된 함수

Python 함수는 중첩된 정의일 수 있습니다.


def outer():
  print(&#39;Before def:&#39;, locals())
  def inner():
    pass
  print(&#39;After def:&#39;, locals())
  return inner

inner는 외부 내에서 정의되므로 외부의 지역 변수로 계산됩니다. def inner가 실행될 때까지 함수 객체는 생성되지 않으므로, external이 호출될 때마다 새로운 inner가 생성됩니다. 아래에서 볼 수 있듯이 매번 반환되는 내부가 다릅니다.


>>> outer()
Before def: {}
After def: {&#39;inner&#39;: <function outer.<locals>.inner at 0x7f0b18fa0048>}
<function outer.<locals>.inner at 0x7f0b18fa0048>
>>> outer()
Before def: {}
After def: {&#39;inner&#39;: <function outer.<locals>.inner at 0x7f0b18fa00d0>}
<function outer.<locals>.inner at 0x7f0b18fa00d0>

3. Closure

중첩 함수의 특별한 점은 무엇인가요? 폐쇄가 있기 때문입니다.


def outer():
  msg = &#39;hello world&#39;
  def inner():
    print(msg)
  return inner

다음 테스트는 inner가 external의 로컬 변수 msg에 액세스할 수 있음을 보여줍니다.


>>> func = outer()
>>> func()
hello world

클로저에는 2가지 특성이 있습니다.
1. 내부는 외부 및 상위 함수의 네임스페이스에 있는 변수(지역 변수, 함수 매개변수)에 액세스할 수 있습니다.
2.outer에 대한 호출이 반환되었지만 해당 네임스페이스는 반환된 내부 개체에서 참조되므로 아직 재활용되지 않습니다.

이 부분에 대해 더 자세히 알고 싶다면 Python의 LEGB 규칙에 대해 알아볼 수 있습니다.

4. 함수를 사용하여 데코레이터 구현

데코레이터는 입력 매개변수가 함수 개체여야 하고 반환 값이 함수 개체여야 합니다.


def make_bold(func):
  print(&#39;Initialize&#39;)
  def wrapper():
    print(&#39;Call&#39;)
    return &#39;<b>{}</b>&#39;.format(func())
  return wrapper

用法跟类实现的装饰器一样。可以去掉 @ 语法糖分析下 构造 和 调用 的时机。


>>> @make_bold
... def get_content():
...   return &#39;hello world&#39;
...
Initialize
>>> get_content()
Call
&#39;<b>hello world</b>&#39;

因为返回的 wrapper 还在引用着,所以存在于 make_bold 命名空间的 func 不会消失。 make_bold 可以装饰多个函数, wrapper 不会调用混淆,因为每次调用 make_bold ,都会有创建新的命名空间和新的 wrapper 。

到此函数实现装饰器也理清楚了,完结撒花,可以关掉网页了~~~(后面是使用装饰的常见问题)

常见问题

1. 怎么实现带参数的装饰器?

带参数的装饰器,有时会异常的好用。我们看个例子。


>>> @make_header(2)
... def get_content():
...   return &#39;hello world&#39;
...
>>> get_content()
&#39;<h2>hello world</h2>&#39;

怎么做到的呢?其实这跟装饰器语法没什么关系。去掉 @ 语法糖会变得很容易理解。


@make_header(2)
def get_content():
  return &#39;hello world&#39;

# 等价于

def get_content():
  return &#39;hello world&#39;
unnamed_decorator = make_header(2)
get_content = unnamed_decorator(get_content)

上面代码中的 unnamed_decorator 才是真正的装饰器, make_header 是个普通的函数,它的返回值是装饰器。

来看一下实现的代码。


def make_header(level):
  print(&#39;Create decorator&#39;)

  # 这部分跟通常的装饰器一样,只是wrapper通过闭包访问了变量level
  def decorator(func):
    print(&#39;Initialize&#39;)
    def wrapper():
      print(&#39;Call&#39;)
      return &#39;<h{0}>{1}</h{0}>&#39;.format(level, func())
    return wrapper

  # make_header返回装饰器
  return decorator

看了实现代码,装饰器的 构造 和 调用 的时序已经很清楚了。


>>> @make_header(2)
... def get_content():
...   return &#39;hello world&#39;
...
Create decorator
Initialize
>>> get_content()
Call
&#39;<h2>hello world</h2>&#39;

2. 如何装饰有参数的函数

为了有条理地理解装饰器,之前例子里的被装饰函数有意设计成无参的。我们来看个例子。


@make_bold
def get_login_tip(name):
  return &#39;Welcome back, {}&#39;.format(name)

最直接的想法是把 get_login_tip 的参数透传下去。


class make_bold(object):
  def init(self, func):
    self.func = func

  def call(self, name):
    return &#39;<b>{}</b>&#39;.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 &#39;<b>{}</b>&#39;.format(self.func(*args, **kwargs))

当装饰器不关心被装饰函数的参数,或是被装饰函数的参数多种多样的时候,可变参数非常合适。可变参数不属于装饰器的语法内容,这里就不深入探讨了。

3. 一个函数能否被多个装饰器装饰?

下面这么写合法吗?


@make_italic
@make_bold
def get_content():
  return &#39;hello world&#39;

合法。上面的的代码和下面等价,留意一下装饰的顺序。


def get_content():
  return &#39;hello world&#39;
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 &#39;<b>{}</b>&#39;.format(func(*args, **kwargs))
  return wrapper

对比一下效果。


>>> @make_bold
... def get_content():
...   &#39;&#39;&#39;Return page content&#39;&#39;&#39;
...   return &#39;hello world&#39;

# 不用functools.wraps的结果
>>> get_content.name
&#39;wrapper&#39;
>>> get_content.doc
>>>

# 用functools.wraps的结果
>>> get_content.name
&#39;get_content&#39;
>>> get_content.doc
&#39;Return page content&#39;

实现装饰器时往往不知道调用方会怎么用,所以养成好习惯加上 functools.wraps 吧。

这次是真·完结了,撒花吧~~~

위 내용은 Python의 데코레이터 사용 요약의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.