Python裝飾器的理解及應用方式

WBOY
發布: 2023-05-08 08:10:10
轉載
1405 人瀏覽過

裝飾器(decorator)是一種高階Python語法。可以對一個函數、方法或類別進行加工。在Python中,我們有多種方法對函數和類別進行加工,相對於其它方式,裝飾器語法簡單,程式碼可讀性高。因此,裝飾器在Python專案中有廣泛的應用。修飾器常被用於有切面需求的場景,較為經典的有插入日誌、效能測試、事務處理, Web權限校驗, Cache等。

如何理解 Python 装饰器

裝飾器的優點是能夠抽離大量函數中與函數功能本身無關的雷同程式碼並繼續重複使用。即,可以將函數「修飾」為完全不同的行為,可以有效的將業務邏輯正交分解。概括的講,裝飾器的作用就是為已經存在的物件添加額外的功能。例如記錄日誌,需要對某些函數進行記錄。笨的辦法,每個函數加入程式碼,如果程式碼變了,就悲催了。裝飾器的辦法,定義一個專門日誌記錄的裝飾器,對所需的函數進行裝飾。

如何理解 Python 装饰器

Python 的 Decorator在使用上和Java/C#的Annotation很相似,都是在方法名稱前面加上一個@XXX註解來為這個方法裝飾一些東西。但是,Java/C#的Annotation也很令人望而卻步,在使用它之前你需要了解一堆Annotation的類庫文檔,讓人感覺就是在學習另一門語言。而Python使用了一種相對於Decorator Pattern和Annotation來說非常優雅的方法,這種方法不需要你去掌握什麼複雜的OO模型或是Annotation的各種類別庫規定,完全就是語言層面的玩法:一種函數式程式設計的技巧。

裝飾器背後的原理

在Python中,裝飾器實作是十分方便。原因是:函數可以被丟來丟去。

Python的函數就是物件

要理解裝飾器,就必須先知道,在Python裡,函數也是物件(functions are objects)。明白這一點非常重要,讓我們透過一個例子來看看為什麼。

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"
登入後複製

函數引用(Functions references)

你剛剛已經知道了,Python的函數也是對象,因此:

  • 可以被賦值給變數
  • 可以在另一個函數體內定義

那麼,這樣就意味著一個函數可以回傳另一個函數:

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!
登入後複製

裝飾器實戰

#現在已經具備了理解裝飾器的所有基礎知識了。裝飾器也就是一種包裝材料,它們可以讓你在執行被裝飾的函數之前或之後執行其他程式碼,而且不需要修改函數本身。

手工製作的裝飾器

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

**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 只是下面這條語句的簡寫(shortcut):

another_stand_alone_function = my_shiny_new_decorator(another_stand_alone_function)
登入後複製

裝飾器語法糖其實就是裝飾器模式的一個Python化的變體。為了方便開發,Python已經內建了好幾種經典的設計模式,例如迭代器(iterators)。當然,你也可以堆積使用裝飾器:

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
登入後複製

含參數的裝飾器

在上面的裝飾器呼叫中,例如@decorator,裝飾器預設它後面的函數是唯一的參數。裝飾器的語法允許我們呼叫decorator時,提供其它參數,例如@decorator(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)
登入後複製

裝飾「類別中的方法」

Python的一個偉大之處在於:方法和函數幾乎是一樣的(methods and functions are really the same),除了方法的第一個參數應該是當前物件的參考(也就是self)。這也意味著只要記得把self 考慮在內,你就可以用同樣的方法給方法創建裝飾器:

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
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!