>  기사  >  백엔드 개발  >  Python 데코레이터 이해 및 적용

Python 데코레이터 이해 및 적용

WBOY
WBOY앞으로
2023-05-08 08:10:101442검색

Decorator는 고급 Python 구문입니다. 함수, 메소드 또는 클래스를 처리할 수 있습니다. Python에는 함수와 클래스를 처리하는 여러 가지 방법이 있습니다. 다른 방법에 비해 데코레이터는 구문이 간단하고 코드 가독성이 높습니다. 따라서 데코레이터는 Python 프로젝트에서 널리 사용됩니다. 데코레이터는 교차 요구 사항이 있는 시나리오에서 자주 사용됩니다. 일반적인 예로는 삽입 로그, 성능 테스트, 트랜잭션 처리, 웹 권한 확인, 캐시 등이 있습니다.

如何理解 Python 装饰器

데코레이터의 장점은 함수 자체와 관련이 없는 수많은 함수에서 유사한 코드를 추출하여 계속해서 재사용할 수 있다는 것입니다. 즉, 기능은 완전히 다른 동작으로 "수정"될 수 있으며 비즈니스 로직은 효과적으로 직교적으로 분해될 수 있습니다. 간단히 말해서 데코레이터의 목적은 기존 객체에 추가 기능을 추가하는 것입니다. 예를 들어, 로깅에는 특정 기능의 로깅이 필요합니다. 어리석은 방법은 각 기능에 코드를 추가하는 것입니다. 코드가 변경되면 비극이 될 것입니다. 데코레이터 방법은 필요한 기능을 장식하기 위해 특별한 로깅 데코레이터를 정의하는 것입니다.

如何理解 Python 装饰器

Python의 Decorator는 사용 중인 Java/C#의 Annotation과 매우 유사합니다. 둘 다 메소드 이름 앞에 @XXX 주석을 추가하여 메소드를 장식합니다. 그러나 Java/C#의 Annotation은 매우 어려운 작업이기도 합니다. 이를 사용하기 전에 수많은 Annotation 클래스 라이브러리 문서를 이해해야 하므로 마치 다른 언어를 배우는 것처럼 느껴집니다. Python은 Decorator Pattern 및 Annotation에 비해 매우 우아한 방법을 사용합니다. 이 방법은 Annotation의 복잡한 OO 모델이나 다양한 클래스 라이브러리 규정을 완전히 익힐 필요가 없습니다. 1. 함수형 프로그래밍 기술.

데코레이터의 원리

파이썬에서는 데코레이터 구현이 매우 편리합니다. 그 이유는 함수가 던져질 수 있기 때문입니다.

Python의 함수는 객체입니다.

데코레이터를 이해하려면 먼저 Python에서 함수가 객체라는 점을 알아야 합니다. 이를 이해하는 것이 중요합니다. 예를 들어 그 이유를 살펴보겠습니다.

def shout(word="yes"):

**return** word.capitalize() + "!"

print(shout())

# outputs : 'Yes!'

# 作为一个对象,你可以像其他对象一样把函数赋值给其他变量

scream = shout

# 注意我们没有用括号:我们不是在调用函数,

# 而是把函数'shout'的值绑定到'scream'这个变量上

# 这也意味着你可以通过'scream'这个变量来调用'shout'函数

print(scream())

# outputs : 'Yes!'

# 不仅如此,这也还意味着你可以把原来的名字'shout'删掉,

# 而这个函数仍然可以通过'scream'来访问

del shout

**try**:

print(shout())

**except** NameError as e:

print(e)

# outputs: "name 'shout' is not defined"

print(scream())

# outputs: 'Yes!'

Python 함수의 또 다른 흥미로운 특징은 다른 함수의 본문 내부에서 정의할 수 있다는 것입니다.

def talk():

# 你可以在 'talk' 里动态的(on the fly)定义一个函数...

**def** whisper(word="yes"):

**return** word.lower() + "..."

# ... 然后马上调用它!

print(whisper())

# 每当调用'talk',都会定义一次'whisper',然后'whisper'在'talk'里被调用

talk()

# outputs:

# "yes..."

# 但是"whisper" 在 "talk"外并不存在:

**try**:

print(whisper())

**except** NameError as e:

print(e)

# outputs : "name 'whisper' is not defined"

함수 참조

파이썬 함수도 객체라는 사실을 아셨을 겁니다. 따라서:

  • 변수에 할당할 수 있습니다.
  • 다른 함수 본문에 정의할 수 있습니다.

즉, 함수는 다른 함수를 반환할 수 있습니다.

def get_talk(type="shout"):

# 我们先动态定义一些函数

**def** shout(word="yes"):

**return** word.capitalize() + "!"

**def** whisper(word="yes"):

**return** word.lower() + "..."

# 然后返回其中一个

**if** type == "shout":

# 注意:我们是在返回函数对象,而不是调用函数,所以不要用到括号 "()"

**return** shout

**else**:

**return** whisper

# 那你改如何使用d呢?

# 先把函数赋值给一个变量

talk = get_talk()

# 你可以发现 "talk" 其实是一个函数对象:

print(talk)

# outputs : <function shout at 0xb7ea817c>

# 这个对象就是 get_talk 函数返回的:

print(talk())

# outputs : Yes!

# 你甚至还可以直接这样使用:

print(get_talk("whisper")())

# outputs : yes...

함수는 반환될 수 있으므로 매개변수처럼 전달할 수도 있습니다.

def shout(word="yes"):

**return** word.capitalize() + "!"

scream = shout

**def** do_something_before(func):

print("I do something before then I call the function you gave me")

print(func())

do_something_before(scream)

# outputs:

# I do something before then I call the function you gave me

# Yes!

Decorator in action

이제 데코레이터를 이해하는 데 필요한 모든 기본 지식을 갖추었습니다. 데코레이터는 함수 자체를 수정하지 않고도 데코레이팅된 함수를 실행하기 전이나 후에 다른 코드를 실행할 수 있게 해주는 패키징 자료입니다.

수제 데코레이터

# 一个装饰器是一个需要另一个函数作为参数的函数

**def** my_shiny_new_decorator(a_function_to_decorate):

# 在装饰器内部动态定义一个函数:wrapper(原意:包装纸).

# 这个函数将被包装在原始函数的四周

# 因此就可以在原始函数之前和之后执行一些代码.

**def** the_wrapper_around_the_original_function():

# 把想要在调用原始函数前运行的代码放这里

print("Before the function runs")

# 调用原始函数(需要带括号)

a_function_to_decorate()

# 把想要在调用原始函数后运行的代码放这里

print("After the function runs")

# 直到现在,"a_function_to_decorate"还没有执行过 (HAS NEVER BEEN EXECUTED).

# 我们把刚刚创建的 wrapper 函数返回.

# wrapper 函数包含了这个函数,还有一些需要提前后之后执行的代码,

# 可以直接使用了(It's ready to use!)

**return** the_wrapper_around_the_original_function

# Now imagine you create a function you don't want to ever touch again.

**def** a_stand_alone_function():

print("I am a stand alone function, don't you dare modify me")

a_stand_alone_function()

# outputs: I am a stand alone function, don't you dare modify me

# 现在,你可以装饰一下来修改它的行为.

# 只要简单的把它传递给装饰器,后者能用任何你想要的代码动态的包装

# 而且返回一个可以直接使用的新函数:

a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)

a_stand_alone_function_decorated()

# outputs:

# Before the function runs

# I am a stand alone function, don't you dare modify me

# After the function runs

데코레이터를 위한 구문 설탕

데코레이터 구문을 사용하여 이전 예제를 다시 작성해 보겠습니다.

# 一个装饰器是一个需要另一个函数作为参数的函数

**def** my_shiny_new_decorator(a_function_to_decorate):

# 在装饰器内部动态定义一个函数:wrapper(原意:包装纸).

# 这个函数将被包装在原始函数的四周

# 因此就可以在原始函数之前和之后执行一些代码.

**def** the_wrapper_around_the_original_function():

# 把想要在调用原始函数前运行的代码放这里

print("Before the function runs")

# 调用原始函数(需要带括号)

a_function_to_decorate()

# 把想要在调用原始函数后运行的代码放这里

print("After the function runs")

# 直到现在,"a_function_to_decorate"还没有执行过 (HAS NEVER BEEN EXECUTED).

# 我们把刚刚创建的 wrapper 函数返回.

# wrapper 函数包含了这个函数,还有一些需要提前后之后执行的代码,

# 可以直接使用了(It's ready to use!)

**return** the_wrapper_around_the_original_function

@my_shiny_new_decorator

**def** another_stand_alone_function():

print("Leave me alone")

another_stand_alone_function()

# outputs:

# Before the function runs

# Leave me alone

# After the function runs

예, 그게 전부입니다. 매우 간단합니다. @ decorator는 다음 문장의 단축어일 뿐입니다:

another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)

데코레이터 구문 설탕은 실제로 데코레이터 패턴의 Pythonic 변형입니다. 개발을 용이하게 하기 위해 Python에는 반복자와 같은 몇 가지 고전적인 디자인 패턴이 내장되어 있습니다. 물론, 데코레이터를 쌓을 수도 있습니다:

def bread(func):

**def** wrapper():

print("</''''''>")

func()

print("<______/>")

**return** wrapper

**def** ingredients(func):

**def** wrapper():

print("#tomatoes#")

func()

print("~salad~")

**return** wrapper

**def** sandwich(food="--ham--"):

print(food)

sandwich()

# outputs: --ham--

sandwich = bread(ingredients(sandwich))

sandwich()

# outputs:

# </''''''>

# #tomatoes#

# --ham--

# ~salad~

# <______/>

Python의 데코레이터 구문으로 표현:

def bread(func):

**def** wrapper():

print("</''''''>")

func()

print("<______/>")

**return** wrapper

**def** ingredients(func):

**def** wrapper():

print("#tomatoes#")

func()

print("~salad~")

**return** wrapper

@bread

@ingredients

**def** sandwich(food="--ham--"):

print(food)

sandwich()

# outputs:

# </''''''>

# #tomatoes#

# --ham--

# ~salad~

# <______/>

데코레이터가 배치되는 순서도 중요합니다:

def bread(func):

**def** wrapper():

print("</''''''>")

func()

print("<______/>")

**return** wrapper

**def** ingredients(func):

**def** wrapper():

print("#tomatoes#")

func()

print("~salad~")

**return** wrapper

@ingredients

@bread

**def** strange_sandwich(food="--ham--"):

print(food)

strange_sandwich()

# outputs:

##tomatoes#

# </''''''>

# --ham--

# <______/>

# ~salad~

데코레이터 함수에 매개변수 전달

# 这不是什么黑色魔法(black magic),你只是必须让wrapper传递参数:

**def** a_decorator_passing_arguments(function_to_decorate):

**def** a_wrapper_accepting_arguments(arg1, arg2):

print("I got args! Look:", arg1, arg2)

function_to_decorate(arg1, arg2)

**return** a_wrapper_accepting_arguments

# 当你调用装饰器返回的函数式,你就在调用wrapper,而给wrapper的

# 参数传递将会让它把参数传递给要装饰的函数

@a_decorator_passing_arguments

**def** print_full_name(first_name, last_name):

print("My name is", first_name, last_name)

print_full_name("Peter", "Venkman")

# outputs:

# I got args! Look: Peter Venkman

# My name is Peter Venkman

매개변수가 있는 데코레이터

In @ decorator와 같은 위의 데코레이터 호출에서 데코레이터는 기본적으로 그 뒤에 오는 함수를 유일한 매개변수로 사용합니다. 데코레이터의 구문을 사용하면 @designator(a)와 같은 데코레이터를 호출할 때 다른 매개변수를 제공할 수 있습니다. 이는 데코레이터 작성 및 사용에 있어 더 큰 유연성을 제공합니다.

# a new wrapper layer

**def** pre_str(pre=''):

# old decorator

**def** decorator(F):

**def** new_F(a, b):

print(pre + " input", a, b)

**return** F(a, b)

**return** new_F

**return** decorator

# get square sum

@pre_str('^_^')

**def** square_sum(a, b):

**return** a ** 2 + b ** 2

# get square diff

@pre_str('T_T')

**def** square_diff(a, b):

**return** a ** 2 - b ** 2

print(square_sum(3, 4))

print(square_diff(3, 4))

# outputs:

# ('^_^ input', 3, 4)

# 25

# ('T_T input', 3, 4)

# -7

위의 pre_str은 매개변수를 허용하는 데코레이터입니다. 실제로는 원래 데코레이터의 함수 캡슐화이며 데코레이터를 반환합니다. 우리는 이를 환경 매개변수를 포함하는 클로저로 이해할 수 있습니다. @pre_str('^_^')을 사용하여 호출하면 Python은 이 캡슐화 계층을 발견하고 매개변수를 데코레이터 환경에 전달할 수 있습니다. 호출은 다음과 동일합니다:

square_sum = pre_str('^_^') (square_sum)

Decorated "method in class"

Python의 가장 큰 장점 중 하나는 메소드의 첫 번째 매개변수가 현재 객체에 대한 참조라는 점을 제외하면 메소드와 함수가 실제로 동일하다는 것입니다. (즉, 자기). 즉, 자신을 고려하는 한 동일한 방법으로 메서드에 대한 데코레이터를 만들 수 있습니다.

def method_friendly_decorator(method_to_decorate):

**def** wrapper(self, lie):

lie = lie - 3# very friendly, decrease age even more :-)

**return** method_to_decorate(self, lie)

**return** wrapper

**class** Lucy(object):

**def** __init__(self):

self.age = 32

@method_friendly_decorator

**def** say_your_age(self, lie):

print("I am %s, what did you think?" % (self.age + lie))

l = Lucy()

l.say_your_age(-3)

# outputs: I am 26, what did you think?

물론, 모든 기능과 메서드를 장식하는 데 사용할 수 있는 매우 일반적인 데코레이터를 작성하려면, 특정 매개변수를 무시하고 *args, **kwargs를 직접 사용할 수 있습니다:

def a_decorator_passing_arbitrary_arguments(function_to_decorate):

# The wrapper accepts any arguments

**def** a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):

print("Do I have args?:")

print(args)

print(kwargs)

# Then you unpack the arguments, here *args, **kwargs

# If you are not familiar with unpacking, check:

# http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/

function_to_decorate(*args, **kwargs)

**return** a_wrapper_accepting_arbitrary_arguments

@a_decorator_passing_arbitrary_arguments

**def** function_with_no_argument():

print("Python is cool, no argument here.")

function_with_no_argument()

# outputs

# Do I have args?:

# ()

# {}

# Python is cool, no argument here.

@a_decorator_passing_arbitrary_arguments

**def** function_with_arguments(a, b, c):

print(a, b, c)

function_with_arguments(1, 2, 3)

# outputs

# Do I have args?:

# (1, 2, 3)

# {}

# 1 2 3

@a_decorator_passing_arbitrary_arguments

**def** function_with_named_arguments(a, b, c, platypus="Why not ?"):

print("Do %s, %s and %s like platypus? %s" % (a, b, c, platypus))

function_with_named_arguments("Bill", "Linus", "Steve", platypus="Indeed!")

# outputs

# Do I have args ? :

# ('Bill', 'Linus', 'Steve')

# {'platypus': 'Indeed!'}

# Do Bill, Linus and Steve like platypus? Indeed!

**class** Mary(object):

**def** __init__(self):

self.age = 31

@a_decorator_passing_arbitrary_arguments

**def** say_your_age(self, lie=-3):# You can now add a default value

print("I am %s, what did you think ?" % (self.age + lie))

m = Mary()

m.say_your_age()

# outputs

# Do I have args?:

# (<__main__.Mary object at 0xb7d303ac>,)

# {}

# I am 28, what did you think?

装饰类

在上面的例子中,装饰器接收一个函数,并返回一个函数,从而起到加工函数的效果。在Python 2.6以后,装饰器被拓展到类。一个装饰器可以接收一个类,并返回一个类,从而起到加工类的效果。

def decorator(aClass):

**class** newClass:

**def** __init__(self, age):

self.total_display = 0

self.wrapped = aClass(age)

**def** display(self):

self.total_display += 1

print("total display", self.total_display)

self.wrapped.display()

**return** newClass

@decorator

**class** Bird:

**def** __init__(self, age):

self.age = age

**def** display(self):

print("My age is", self.age)

eagleLord = Bird(5)

**for** i **in** range(3):

eagleLord.display()

在decorator中,我们返回了一个新类newClass。在新类中,我们记录了原来类生成的对象(self.wrapped),并附加了新的属性total_display,用于记录调用display的次数。我们也同时更改了display方法。通过修改,我们的Bird类可以显示调用display的次数了。

内置装饰器

Python中有三种我们经常会用到的装饰器, property、 staticmethod、 classmethod,他们有个共同点,都是作用于类方法之上。

property 装饰器

property 装饰器用于类中的函数,使得我们可以像访问属性一样来获取一个函数的返回值。

class XiaoMing:

first_name = '明'

last_name = '小'

@property

**def** full_name(self):

**return** self.last_name + self.first_name

xiaoming = XiaoMing()

print(xiaoming.full_name)

例子中我们像获取属性一样获取 full_name 方法的返回值,这就是用 property 装饰器的意义,既能像属性一样获取值,又可以在获取值的时候做一些操作。

staticmethod 装饰器

staticmethod 装饰器同样是用于类中的方法,这表示这个方法将会是一个静态方法,意味着该方法可以直接被调用无需实例化,但同样意味着它没有 self 参数,也无法访问实例化后的对象。

class XiaoMing:

@staticmethod

**def** say_hello():

print('同学你好')

XiaoMing.say_hello()

# 实例化调用也是同样的效果

# 有点多此一举

xiaoming = XiaoMing()

xiaoming.say_hello()

classmethod 装饰器

classmethod 依旧是用于类中的方法,这表示这个方法将会是一个类方法,意味着该方法可以直接被调用无需实例化,但同样意味着它没有 self 参数,也无法访问实例化后的对象。相对于 staticmethod 的区别在于它会接收一个指向类本身的 cls 参数。

class XiaoMing:

name = '小明'

@classmethod

**def** say_hello(cls):

print('同学你好, 我是' + cls.name)

print(cls)

XiaoMing.say_hello()

wraps 装饰器

一个函数不止有他的执行语句,还有着 name(函数名),doc (说明文档)等属性,我们之前的例子会导致这些属性改变。

def decorator(func):

**def** wrapper(*args, **kwargs):

"""doc of wrapper"""

print('123')

**return** func(*args, **kwargs)

**return** wrapper

@decorator

**def** say_hello():

"""doc of say hello"""

print('同学你好')

print(say_hello.__name__)

print(say_hello.__doc__)

由于装饰器返回了 wrapper 函数替换掉了之前的 say_hello 函数,导致函数名,帮助文档变成了 wrapper 函数的了。解决这一问题的办法是通过 functools 模块下的 wraps 装饰器。

from functools import wraps

**def** decorator(func):

@wraps(func)

**def** wrapper(*args, **kwargs):

"""doc of wrapper"""

print('123')

**return** func(*args, **kwargs)

**return** wrapper

@decorator

**def** say_hello():

"""doc of say hello"""

print('同学你好')

print(say_hello.__name__)

print(say_hello.__doc__)

装饰器总结

装饰器的核心作用是name binding。这种语法是Python多编程范式的又一个体现。大部分Python用户都不怎么需要定义装饰器,但有可能会使用装饰器。鉴于装饰器在Python项目中的广泛使用,了解这一语法是非常有益的。

常见错误:“装饰器”=“装饰器模式”

设计模式是一个在计算机世界里鼎鼎大名的词。假如你是一名 Java 程序员,而你一点设计模式都不懂,那么我打赌你找工作的面试过程一定会度过的相当艰难。

但写 Python 时,我们极少谈起“设计模式”。虽然 Python 也是一门支持面向对象的编程语言,但它的鸭子类型设计以及出色的动态特性决定了,大部分设计模式对我们来说并不是必需品。所以,很多 Python 程序员在工作很长一段时间后,可能并没有真正应用过几种设计模式。

不过装饰器模式(Decorator Pattern)是个例外。因为 Python 的“装饰器”和“装饰器模式”有着一模一样的名字,我不止一次听到有人把它们俩当成一回事,认为使用“装饰器”就是在实践“装饰器模式”。但事实上,它们是两个完全不同的东西。

“装饰器模式”是一个完全基于“面向对象”衍生出的编程手法。它拥有几个关键组成:一个统一的接口定义、若干个遵循该接口的类、类与类之间一层一层的包装。最终由它们共同形成一种“装饰”的效果。

而 Python 里的“装饰器”和“面向对象”没有任何直接联系,**它完全可以只是发生在函数和函数间的把戏。事实上,“装饰器”并没有提供某种无法替代的功能,它仅仅就是一颗“语法糖”而已。下面这段使用了装饰器的代码:

@log_time

@cache_result

**def** foo(): pass

基本完全等同于:

def foo(): pass

foo = log_time(cache_result(foo))

装饰器最大的功劳,在于让我们在某些特定场景时,可以写出更符合直觉、易于阅读的代码。它只是一颗“糖”,并不是某个面向对象领域的复杂编程模式。

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

성명:
이 기사는 51cto.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제