Python 데코레이터의 지식 포인트를 요약합니다.

WBOY
풀어 주다: 2022-06-17 13:50:40
앞으로
2275명이 탐색했습니다.

이 글은 클로저, 데코레이터, 다중 데코레이터 사용, 매개변수가 있는 데코레이터 등 데코레이터와 관련된 문제를 주로 소개하는python에 대한 관련 지식을 제공합니다. 함께 살펴보도록 하겠습니다. 도움이 되기를 바랍니다. 모두에게.

Python 데코레이터의 지식 포인트를 요약합니다.

추천 학습:python 비디오 튜토리얼

1. 클로저

데코레이터(데코레이터)가 무엇인지 이해하려면 먼저클로저(클로저)의 개념을 알아야 합니다.

클로저, 클로저 함수 또는 닫힌 함수라고도 함. 일반인의 용어로 함수가 객체로 반환되고 외부 변수도 포함하면 클로저를 형성합니다.

예를 들어 Hello World를 인쇄해 보겠습니다. 먼저 중첩 함수의 구조가 어떻게 생겼는지 살펴보겠습니다.

def print_msg(msg): def printer(): print(msg) printer()print_msg('Hello World')# Hello World
로그인 후 복사

print_msg('Hello World')를 실행하는 것은 print(), 즉print(msg)가 실행되므로Hello World가 출력됩니다.print_msg('Hello World')相当于执行了printer(),也就是执行print(msg),所以将输出Hello World

我们再来看一下如果是闭包,该是什么样的结构:

def print_msg(msg): def printer(): print(msg) return printer my_msg = print_msg('Hello World')my_msg()# Hello World
로그인 후 복사

本例中的printer函数就是闭包。

执行print_msg('Hello World')实际上是返回了如下这样一个函数,它夹带了外部变量'Hello World'

def printer(): print('Hello World')
로그인 후 복사

于是调用my_msg就相当于执行printer()


那么如何判断一个函数是否是闭包函数呢?闭包函数的__closure__属性里面定义了一个元组用于存放所有的cell对象,每个cell对象保存了这个闭包中所有的外部变量。而普通函数的__closure__属性为None

def outer(content): def inner(): print(content) return innerprint(outer.__closure__) # Noneinner = outer('Hello World')print(inner.__closure__) # (,)
로그인 후 복사

由此可见outer函数不是闭包,而inner函数是闭包。

我们还可以查看闭包所携带的外部变量:

print(inner.__closure__[0].cell_contents)# Hello World
로그인 후 복사

说了那么多,那么闭包究竟有什么用呢?闭包存在的意义就是它夹带了外部变量(私货),如果它不夹带私货,那么就和普通的函数没有任何区别。

闭包的优点如下:

  • 局部变量无法共享和长久的保存,而全局变量可能造成变量污染,闭包既可以长久的保存变量又不会造成全局污染。
  • 闭包使得函数内局部变量的值始终保持在内存中,不会在外部函数调用后被自动清除。

二、装饰器

我们先考虑这样一个场景,假设先前编写的一个函数已经实现了4个功能,为简便起见,我们用print语句来代表每一个具体的功能:

def module(): print('功能1') print('功能2') print('功能3') print('功能4')
로그인 후 복사

现在,由于某种原因,你需要为module这个函数新增一个功能5,你完全可以这样修改:

def module(): print('功能1') print('功能2') print('功能3') print('功能4') print('功能5')
로그인 후 복사

但在现实业务中,直接做出这样的修改往往是比较危险的(会变得不易于维护)。那么如何在不修改原函数的基础上去为它新添一个功能呢?

你可能已经想到了使用之前的闭包知识:

def func_5(original_module): def wrapper(): original_module() print('功能5') return wrapper
로그인 후 복사

func_5代表该函数主要用于实现功能5,我们接下来将module传入进去来观察效果:

new_module = func_5(module)new_module()# 功能1# 功能2# 功能3# 功能4# 功能5
로그인 후 복사

可以看出,我们的新模块:new_module已经实现了功能5

在上面的例子中,函数func_5就是一个装饰器,它装饰了原来的模块(为它新添了一个功能)。

当然,Python有更简洁的写法(称之为语法糖),我们可以将@符号与装饰器函数的名称一起使用,并将其放置在要装饰的函数的定义上方:

def func_5(original_module): def wrapper(): original_module() print('功能5') return wrapper@func_5def module(): print('功能1') print('功能2') print('功能3') print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5
로그인 후 복사

基于此,我们可以在不修改原函数的基础上完成计时任务(计算原函数的运行时间),如下:

def timer(func): def wrapper(): import time tic = time.time() func() toc = time.time() print('程序用时: {}s'.format(toc - tic)) return wrapper@timerdef make_list(): return [i * i for i in range(10**7)]my_list = make_list()# 程序用时: 0.8369960784912109s
로그인 후 복사

事实上,my_list并不是列表,直接打印会显示None,这是因为我们的wrapper函数没有设置返回值。如果需要获得make_list的返回值,可以这样修改wrapper函数:

def wrapper(): import time tic = time.time() a = func() toc = time.time() print('程序用时: {}s'.format(toc - tic)) return a
로그인 후 복사

三、使用多个装饰器

假如我们要为module新添功能5功能6

클로저라면 어떤 구조일지 살펴보겠습니다.

def func_5(original_module): def wrapper(): original_module() print('功能5') return wrapperdef func_6(original_module): def wrapper(): original_module() print('功能6') return wrapper@func_6@func_5def module(): print('功能1') print('功能2') print('功能3') print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5# 功能6
로그인 후 복사
이 예에서 printer함수는 클로저입니다.

print_msg('Hello World')를 실행하면 실제로 외부 변수 'Hello World'를 포함하는 다음과 같은 함수가 반환됩니다.

def module(): print('功能1') print('功能2') print('功能3') print('功能4')new_module = func_6(func_5(module))new_module()
로그인 후 복사
Somy_msg를 호출하는 것은printer()를 실행하는 것과 같습니다.
그렇다면 함수가 클로저 함수인지 어떻게 판단할 수 있을까요? 클로저 함수의 __closure__속성은 모든 셀 객체를 저장하는 튜플을 정의합니다. 각 셀 객체는 클로저에 모든 외부 변수를 저장합니다. 일반 함수의 __closure__속성은 None입니다.

@func_5@func_6def module(): print('功能1') print('功能2') print('功能3') print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能6# 功能5
로그인 후 복사

outer함수는 클로저가 아니지만, inner함수는 클로저임을 알 수 있습니다.

클로저가 전달하는 외부 변수도 볼 수 있습니다:

def pide(a, b): return a / b
로그인 후 복사
로그인 후 복사

너무 많이 말했지만 클로저가 무슨 소용이 있나요? 클로저의 존재 의미는 외부 변수(사적 재화)를 운반한다는 것입니다. 사적 재화를 운반하지 않는다면 일반 기능과 다르지 않습니다. 클로저의 장점은 다음과 같습니다.
  • 로컬 변수는 장기간 공유 및 저장할 수 없지만, 글로벌 변수는 변수 오염을 일으킬 수 있지만 클로저는 글로벌 오염을 일으키지 않고 오랫동안 변수를 저장할 수 있습니다.
  • 클로저를 사용하면 함수 내의 지역 변수 값이 항상 메모리에 유지되고 외부 함수가 호출된 후에 자동으로 지워지지 않습니다.
2. 데코레이터 먼저 이전에 작성된 함수가 4개의 함수를 구현한 시나리오를 고려해 보겠습니다. 단순화를 위해 print문을 사용하여 각 특정 함수를 나타냅니다.
def smart_pide(func): def wrapper(a, b): if b == 0: return '被除数不能为0!' else: return func(a, b) return wrapper
로그인 후 복사
로그인 후 복사
이제 어떤 이유로 모듈함수에 함수 5를 추가해야 합니다. 다음과 같이 수정할 수 있습니다.
@smart_pidedef pide(a, b): return a / bprint(pide(3, 0))# 被除数不能为0!print(pide(3, 1))# 3.0
로그인 후 복사
로그인 후 복사
하지만 실제 비즈니스에서는 이러한 수정을 직접 수행하는 것은 종종 위험합니다(유지 관리가 어려워집니다). 그렇다면 원래 기능을 수정하지 않고 새 기능을 추가하는 방법은 무엇일까요? 이전 클로저 지식을 사용해 보셨을 수도 있습니다:
def decorator(func): def wrapper(*args, **kwargs): # ... res = func(*args, **kwargs) # ... return res # 也可以不return return wrapper
로그인 후 복사
로그인 후 복사
func_5는 이 함수가 주로 함수 5를 구현하는 데 사용된다는 의미입니다. 다음에는 하겠습니다. 효과를 관찰하려면 모듈을 전달하세요.
def func_5_with_name(name=None): def func_5(original_module): def wrapper(): original_module() print('功能5由{}实现'.format(name)) return wrapper return func_5
로그인 후 복사
로그인 후 복사
새 모듈인 new_module함수 5를 구현한 것을 볼 수 있습니다.
위의 예에서 func_5함수는 원래 모듈을 장식하는 데코레이터입니다(새 함수를 추가함).
물론 Python에는 더 간결하게 작성하는 방법이 있습니다(구문 설탕이라고 함). 데코레이터 함수 이름과 함께 @ 기호를 사용하고 장식할 함수 정의 위에 배치할 수 있습니다.
@func_5_with_name(name='若水')def module(): print('功能1') print('功能2') print('功能3') print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5由若水实现
로그인 후 복사
로그인 후 복사

이를 바탕으로 다음과 같이 원래 함수를 수정하지 않고도 타이밍 작업(원래 함수의 실행 시간 계산)을 완료할 수 있습니다.
class Timer: def __init__(self, func): self.func = func def __call__(self): import time tic = time.time() self.func() toc = time.time() print('用时: {}s'.format(toc - tic))@Timerdef make_list(): return [i**2 for i in range(10**7)]make_list()# 用时: 2.928966999053955s
로그인 후 복사
로그인 후 복사
실제로 my_list목록이 아닌 경우 직접 인쇄하면 없음이 표시됩니다. 이는 래퍼함수가 반환 값을 설정하지 않기 때문입니다. make_list의 반환 값을 가져와야 하는 경우 wrapper함수를 다음과 같이 수정할 수 있습니다.
class Timer: def __init__(self, func): self.func = func def __call__(self, num): import time tic = time.time() res = self.func(num) toc = time.time() print('用时: {}s'.format(toc - tic)) return res@Timerdef make_list(num): return [i**2 for i in range(num)]my_list = make_list(10**7)# 用时: 2.8219943046569824sprint(len(my_list))# 10000000
로그인 후 복사
로그인 후 복사
3. 여러 데코레이터를 사용하세요 모듈 code>에 함수 5함수 6(번호순)이 추가되었는데 어떻게 해야 하나요? 다행히도 Python에서는 여러 데코레이터를 동시에 사용할 수 있습니다.
class Func_5: def __init__(self, name=None): self.name = name def __call__(self, func): def wrapper(): func() print('功能5由{}实现'.format(self.name)) return wrapper@Func_5('若水')def module(): print('功能1') print('功能2') print('功能3') print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5由若水实现
로그인 후 복사
로그인 후 복사
위 프로세스는 실제로 다음과 동일합니다.
class A: num = 100 def func1(self): print('功能1') @classmethod def func2(cls): print('功能2') print(cls.num) cls().func1()A.func2()# 功能2# 100# 功能1
로그인 후 복사
로그인 후 복사
또한 여러 데코레이터를 사용할 경우 함수에 가장 가까운 데코레이터 정의 함수가 먼저 장식됩니다. 장식 순서를 변경하면 출력 결과도 변경됩니다.
class A: @staticmethod def add(a, b): return a + bprint(A.add(2, 3))# 5print(A().add(2, 3))# 5
로그인 후 복사
로그인 후 복사
4. 장식된 함수에 매개변수가 있습니다장식된 함수에 매개변수가 있는 경우 장식을 구성하는 방법은 무엇입니까? 장치? 다음과 같은 함수를 생각해 보세요:
def pide(a, b): return a / b
로그인 후 복사
로그인 후 복사

b=0时会出现ZeropisionError。如何在避免修改该函数的基础上给出一个更加人性化的提醒呢?

因为我们的pide函数接收两个参数,所以我们的wrapper函数也应当接收两个参数:

def smart_pide(func): def wrapper(a, b): if b == 0: return '被除数不能为0!' else: return func(a, b) return wrapper
로그인 후 복사
로그인 후 복사

使用该装饰器进行装饰:

@smart_pidedef pide(a, b): return a / bprint(pide(3, 0))# 被除数不能为0!print(pide(3, 1))# 3.0
로그인 후 복사
로그인 후 복사

如果不知道要被装饰的函数有多少个参数,我们可以使用下面更为通用的模板:

def decorator(func): def wrapper(*args, **kwargs): # ... res = func(*args, **kwargs) # ... return res # 也可以不return return wrapper
로그인 후 복사
로그인 후 복사

五、带参数的装饰器

我们之前提到的装饰器都没有带参数,即语法糖@decorator中没有参数,那么该如何写一个带参数的装饰器呢?

前面实现的装饰器都是两层嵌套函数,而带参数的装饰器是一个三层嵌套函数。

考虑这样一个场景。假如我们在为module添加新功能时,希望能够加上实现该功能的开发人员的花名,则可以这样构造装饰器(以功能5为例):

def func_5_with_name(name=None): def func_5(original_module): def wrapper(): original_module() print('功能5由{}实现'.format(name)) return wrapper return func_5
로그인 후 복사
로그인 후 복사

效果如下:

@func_5_with_name(name='若水')def module(): print('功能1') print('功能2') print('功能3') print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5由若水实现
로그인 후 복사
로그인 후 복사

对于这种三层嵌套函数,我们可以这样理解:当为func_5_with_name指定了参数后,func_5_with_name(name='若水')实际上返回了一个decorator,于是@func_5_with_name(name='若水')就相当于@decorator

六、使用类作为装饰器

将类作为装饰器,我们需要实现__init__方法和__call__方法。

以计时器为例,具体实现如下:

class Timer: def __init__(self, func): self.func = func def __call__(self): import time tic = time.time() self.func() toc = time.time() print('用时: {}s'.format(toc - tic))@Timerdef make_list(): return [i**2 for i in range(10**7)]make_list()# 用时: 2.928966999053955s
로그인 후 복사
로그인 후 복사

如果想要自定义生成列表的长度并获得列表(即被装饰的函数带有参数情形),我们就需要在__call__方法中传入相应的参数,具体如下:

class Timer: def __init__(self, func): self.func = func def __call__(self, num): import time tic = time.time() res = self.func(num) toc = time.time() print('用时: {}s'.format(toc - tic)) return res@Timerdef make_list(num): return [i**2 for i in range(num)]my_list = make_list(10**7)# 用时: 2.8219943046569824sprint(len(my_list))# 10000000
로그인 후 복사
로그인 후 복사

如果要构建带参数的类装饰器,则不能把func传入__init__中,而是传入到__call__中,同时__init__用来初始化类装饰器的参数。

接下来我们使用类装饰器来复现第五章节中的效果:

class Func_5: def __init__(self, name=None): self.name = name def __call__(self, func): def wrapper(): func() print('功能5由{}实现'.format(self.name)) return wrapper@Func_5('若水')def module(): print('功能1') print('功能2') print('功能3') print('功能4')module()# 功能1# 功能2# 功能3# 功能4# 功能5由若水实现
로그인 후 복사
로그인 후 복사

七、内置装饰器

Python中有许多内置装饰器,这里仅介绍最常见的三种:@classmethod@staticmethod@property

7.1 @classmethod

@classmethod用于装饰类中的函数,使用它装饰的函数不需要进行实例化也可调用。需要注意的是,被装饰的函数不需要self参数,但第一个参数需要是表示自身类的cls参数,它可以来调用类的属性,类的方法,实例化对象等。

cls代表类本身,self代表实例本身。

具体请看下例:

class A: num = 100 def func1(self): print('功能1') @classmethod def func2(cls): print('功能2') print(cls.num) cls().func1()A.func2()# 功能2# 100# 功能1
로그인 후 복사
로그인 후 복사

7.2 @staticmethod

@staticmethod同样用来修饰类中的方法,使用它装饰的函数的参数没有任何限制(即无需传入self参数),并且可以不用实例化调用该方法。当然,实例化后调用该方法也是允许的。

具体如下:

class A: @staticmethod def add(a, b): return a + bprint(A.add(2, 3))# 5print(A().add(2, 3))# 5
로그인 후 복사
로그인 후 복사

7.3 @property

使用@property装饰器,我们可以直接通过方法名来访问类方法,不需要在方法名后添加一对()小括号。

class A: @property def printer(self): print('Hello World')a = A()a.printer# Hello World
로그인 후 복사

除此之外,@property还可以用来防止类的属性被修改。考虑如下场景

class A: def __init__(self): self.name = 'ABC'a = A()print(a.name)# ABCa.name = 1print(a.name)# 1
로그인 후 복사

可以看出类中的属性name可以被随意修改。如果要防止修改,则可以这样做

class A: def __init__(self): self.name_ = 'ABC' @property def name(self): return self.name_ a = A()print(a.name)# ABCa.name = 1print(a.name)# AttributeError: can't set attribute
로그인 후 복사

推荐学习:python视频教程

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

관련 라벨:
원천:csdn.net
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!