저는 최근 Python을 배우고 있습니다. Python 학습 그룹에 소개된 내용입니다. 지금 배우고 더 많이 연습하는 것이 좋은 학습 방법이 될 것입니다.
Python에는 강력하고 사려 깊은 기능이 많이 있습니다. 가장 인기 있는 순위를 나열하고 싶다면 데코레이터가 확실히 그 순위에 포함될 것입니다.
데코레이터를 처음 만나면 우아함과 마법 같은 느낌이 들지만, 그것을 직접 손으로 구현하고 싶을 때는 마치 퍼다의 얼음 아름다움처럼 항상 거리감이 느껴집니다. 이는 데코레이터를 이해할 때 다른 개념이 혼합되어 있기 때문인 경우가 많습니다. 베일의 층을 제거하면 순수한 장식이 실제로 매우 단순하고 간단하다는 것을 알 수 있습니다.
데코레이터의 원리
인터프리터 아래에서 데코레이터의 예를 실행하여 직관적으로 느껴보세요.
# make_bold는 데코레이터입니다.
>>> @make_bold ... def get_content(): ... return 'hello world' ... >>> get_content() '<b>hello world</b>'
get_content 호출 후 반환된 결과는 자동으로 b 태그로 래핑됩니다. 어떻게 해야 할까요? 간단한 4단계로 이해할 수 있습니다.
1. 함수는 객체입니다.
get_content 함수를 정의합니다. 이때 get_content도 객체이므로 모든 객체에 대해 작업을 수행할 수 있습니다.
def get_content(): return 'hello world'
ID, 유형, 값이 있습니다.
>>> 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'
2. 사용자 정의 함수객체
클래스를 사용하여 함수객체를 구성할 수 있습니다. 멤버 함수 호출이 있는 함수 개체는 함수 개체가 호출될 때 호출되는 함수 개체입니다.
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
3. @는 구문상의 설탕입니다
데코레이터 @는 특별한 기능을 수행하지 않지만 더 많은 코드가 필요합니다.
@make_bold def get_content(): return 'hello world' # 上面的代码等价于下面的 def get_content(): return 'hello world' 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('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>'
데코레이터를 성공적으로 구현했습니다! 아주 간단하지 않나요?
앞서 강조했던 구성과 부르심의 두 가지 과정을 분석해 보겠습니다. 이해하기 쉽도록 @ 구문 설탕을 제거하겠습니다.
# 구성, 데코레이터를 사용하면 함수 객체가 구성되고 init가 호출됩니다.
>>> get_content = make_bold(get_content) Initialize # 调用,实际上直接调用的是make_bold构造出来的函数对象 >>> get_content() Call '<b>hello world</b>'
이 시점에서 완전히 명확해지면 웹 페이지를 닫아도 됩니다. 데코레이터의 원리를 알고 싶습니다 )
데코레이터의 기능적 버전
소스 코드를 읽을 때 데코레이터가 중첩된 함수로 구현되는 경우가 종종 있습니다. 이를 어떻게 이해합니까? 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. 중첩된 함수
Python 함수는 중첩된 정의일 수 있습니다.
def outer(): print('Before def:', locals()) def inner(): pass print('After def:', locals()) return inner
inner는 외부 내에서 정의되므로 외부의 지역 변수로 계산됩니다. def inner가 실행될 때까지 함수 객체는 생성되지 않으므로, external이 호출될 때마다 새로운 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. Closure
중첩 함수의 특별한 점은 무엇인가요? 폐쇄가 있기 때문입니다.
def outer(): msg = 'hello world' 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('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 중국어 웹사이트의 기타 관련 기사를 참조하세요!