Python裝飾器的詳細介紹

零下一度
發布: 2017-07-20 21:16:12
原創
1209 人瀏覽過

裝飾本身就是Python函數,他可以讓其他函數在不需要做任何程式碼變動的前提下增加額外功能,裝飾器的回傳值也是一個額外的物件。

先來了解幾個定義:

#1,函數

#在python中,函數透過def關鍵字、函數名稱和可選的參數清單定義。透過return關鍵字傳回值。我們舉例來說明如何定義和呼叫一個簡單的函數:

 

#
#coding:UTF8

def foo():
     return 1
print foo()

1
登入後複製

方法體(當然多行也是一樣的)是必須的,透過縮進來表示,在方法名稱的後面加上雙括號()就能夠呼叫函數

#2,作用域

在Python中,函數會創建一個新的作用域. Python開發者可能會說函數有自己的命名空間.這就意味著在函數內部碰到一個變數的時候函數會優先在自己的命名空間裡尋找.來簡單舉例說明本地作用域與全域作用域

 

#coding:UTF8

a_string = "This is a global variable"
def foo():
     print locals()
print globals() # doctest: +ELLIPSIS

foo()  #2


{&#39;foo&#39;: <function foo at 0x00000000026ECF98>, ...., &#39;a_string&#39;: &#39;This is a global variable&#39;,...}
{}
登入後複製

 

  內建的函數globals回傳一個包含所有Python解釋器知道的變數名稱的字段(省略一部分)在#2調用了函數foo 把函數背部本地作用域裡面的內容打印出來.可以看到,函數foo有自己的獨立的命名空間,即使暫時命名空間啥也沒有.

3,變數解析規則

#當然並不是說在函數裡就不能存取外面的全域變數.在Python的作用域規則裡,創建變數一定會在當前作用域裡創建一個變數,但訪問或者修改變數是會現在當前作用域查找變數,沒有找到匹配變數會依次向上在閉合的作用域裡進行查找.so如修改函數foo的是實現列印全域的作用域的變數也是可以的

#coding:UTF8

a_string = "This is a global variable"
def foo():
     print a_string   #1

foo()

This is a global variable
登入後複製

在#1處,Python解釋器會嘗試找出變數a_string,當然在函數的本地作用域是找不到,so接著會在上層的作用域去查找
但在另外一方面,假如在函數的內部給全域變數賦值,結果會不一樣

 

#coding:UTF8

a_string = "This is a global variable"
def foo():
    a_string=&#39;Test&#39;  #1
    print locals()
    
foo()

{&#39;a_string&#39;: &#39;Test&#39;}

print a_string  #2

This is a global variable
登入後複製

 

  全域變數能夠被存取(如果是可變資料型別(如list,dict這些),甚至能夠被更改),但賦值就不行了,在函數內部的#1,實際上新創建了一個局部變量,隱藏全局作用域中的同名變量,可以通過打印出全局命名空間中的內容得出這個結論.也可以在#2處印出來的a_string沒有改變

#4,變數生存週期

值得注意的是:變數不只生存在一個個的命名空間裡,都有自己的生存週期,如下:

 

#coding:UTF8

def foo():
     x = 1
foo()
print x # 1

NameError: name &#39;x&#39; is not defined
登入後複製

 

# #1處發生的錯誤不僅僅是因為作用域規則導致,而且和Python以及其他很多程式語言中函數呼叫實現的機制有關.在此地方執行時間點並沒有什麼有效的語法可以獲取變數x的值,因為壓根就不存在,函數foo的命名空間隨著函數的呼叫開始而開始,結束而銷毀

####5,函數參數######## ####Python允許想函數傳遞參數,參數會變成本地變數存在與函數內部#########
#coding:UTF8
def foo(x):
     print locals()
foo(1)

{&#39;x&#39;: 1}
登入後複製
#########  在Python中有很多的方式來定義和傳遞參數,簡單說明下:函數的參數是必須的位置參數或是可選的命名,預設參數#########
#coding:UTF8
def foo(x, y=0): # 1
     return x - y
 
print foo(3, 1) # 2
2
print foo(3) # 3
3
print foo() # 4
TypeError: foo() takes at least 1 argument (0 given)
print foo(y=1, x=3) # 5
2
登入後複製
###

  

在#1处定义了函数foo,有一个位置参数x和一个命名参数y 在#2通过常规的方式来调用函数,即使只有一个命名参数,但参数依然可以通过位置参数传递给函数.在调用函数的时候,对于命名参数y也可以完全不管就想#3所示一样.如命名参数没有接收到任何值的话,Python会自动使用声明的默认值.但不能省略第一个位置参数x,否则会像#4发生错误

python支持函数调用时的命名参数。看看#5处的函数调用,传递的是两个命名实参,这个时候因为有名称标识,参数传递的顺序也就不用在意了。

当然相反的情况也是正确的:函数的第二个形参是y,但通过位置的方式传递值给它。在#2处的函数调用foo(3,1),我们把3传递给了第一个参数,把1传递给了第二个参数,尽管第二个参数是一个命名参数。

6,嵌套函数

Python允许创建嵌套函数,就意味着可以在函数里定义函数而且现有的作用域和变量生存周期依旧适用

#coding:UTF8
def outer():
     x = 1
     def inner():
         print x # 1
     inner() # 2
 
outer()


1
登入後複製

Python解释器需找一个叫x的本地变量,查找失败之后会继续向上层的作用域里查,这个上层的作用域定义在另外一个函数里,对于函数outer来说,变量x是一个本地变量
函数inner可以访问封闭的作用域.在#2处,可以调用函数inner,inner也仅仅是一个遵循Python变量解析规则的变量名,Python解释器会优先在outer的作用域里面对变量名inner查找匹配的变量

7,函数是Python世界中的一级类对象

在Python里函数和其他东西一样都是对象

#coding:UTF8
print issubclass(int, object) # all objects in Python inherit from a common baseclass
True
def foo():
     pass
print foo.__class__ # 1
<type &#39;function&#39;>
print issubclass(foo.__class__, object)
True
登入後複製

  

函数在Python里就是对象,和其他一样,在Python里,函数只是一些普通的值而已,也就是说把函数像参数一样传递给其他的函数或者从函数里返回函数,如:

#coding:UTF8
def add(x, y):
     return x + y
def sub(x, y):
     return x - y
def apply(func, x, y): # 1
     return func(x, y) # 2
print apply(add, 2, 1) # 3
3
print apply(sub, 2, 1)
1
登入後複製

  

在#1处看到函数准备接收一个函数的变量,只是一个普通的变量而已,和其他变量一样,在#2处调用传进来的函数:"()代表这调用函数的操作并且调用变量包含额值.在#3处,能看到传递函数并没有特殊的用法".函数的名称只是跟其他变量一样的标识符而已

Python把频繁要用的操作变成函数作为参数进行使用,向通过传递一个函数给内置排序函数的key参数 从而 来自定义排序规则

#coding:UTF8
def outer():
     def inner():
         print "Inside inner"
     return inner # 1
 
foo = outer() #2
print foo 
<function inner at 0x000000000269C048>

foo()
Inside inner
登入後複製

在#1处恰好是函数标识符的变量inner作为返回值返回出来 "把函数inner返回出来,否则它根本不可能会被调用到" 每次函数outer呗调用,函数inner都会被重新定义,如果它不被当做变量返回额话,每次执行过后将不复存在

在#2处捕获返回值--函数inner,将它存在一个新的变量foo里.当对foo进行求值,确定包含函数inner,而且能够对它进行调用

8,闭包

#coding:UTF8

def outer():
     x = 1
     def inner():
         print x # 1
     return inner
foo = outer()
print foo.func_closure

(<cell at 0x00000000026861F8: int object at 0x0000000001E279A8>,)
登入後複製

x是outer里的一个局部变量,当函数inner在#1处打印x时,Python解释器会在inner内部查找相应的变量,事实也查不到,接着会到封闭作用域里查找,并且找到匹配

从变量的生存周期来看,变量x是函数outer的一个本地变量,意味着只有当函数outer正在运行时才会存在,根据Python运行模式,无法再函数outer返回之后继续调用函数inner,在函数inner调用时,变量x早已不复存在,可能会发生一个运行时的错误
但返回的函数inner可以继续工作,Python支持一个叫做函数闭包的特性,嵌套定义在非全局作用域里的函数能够记住它在被定义的时候它所处的封闭命名空间,这能够通过查看函数的func_closure属性得出结论,这个属性里面包含封闭作用域里面的值(只会包含被捕捉到的值,比如x,如果在outer里面还定义了其他的值,封闭作用域里面是不会有的)

每次函数outer被调用的时候,函数inner都会被重新定义。现在变量x的值不会变化,所以每次返回的函数inner会是同样的逻辑
稍微改动下:

#coding:UTF8

def outer(x):
     def inner():
         print x # 1
     return inner
print1 = outer(1)
print2 = outer(2)

print1()
1
print2()
2
登入後複製

从中可以看到闭包--被函数记住的封闭作用域--能够被用来创建自定义的函数,本质上是一个硬编码的参数.事实上并不是传递参数1或者2给函数inner,实际上是创建了能够打印各种数字的各种自定义版本

闭包单独拿出来就是一个非常强大的功能,在某些方面:outer像是给inner服务器的构造器,x像是一个私有变量

9,装饰器

装饰器其实就是一个闭包,把一个函数当做参数然后返回一个替代版参数

#coding:UTF8

def outer(func):
     def inner():
         print "before func"
         ret = func() # 1
         return ret + 1
     return inner
def foo():
     return 1
decorated = outer(foo) # 2
print decorated()

before func
2
登入後複製

  

定义了一个函数outer,只有一个func参数,在其定义了嵌套的函数inner,inner会打印一串字符串,然后调用func,在#1得到返回值,在outer每次调用时func值可能会不一样,但不管怎用,都会调用它,最后,inner返回func()+1的值,通过调用在#2处存储decorated里的函数能够看到被打印出来的字符串以及返回值2,而不是期望中调用函数foo得到的返回值1。


可以认为变量decorated是函数foo的一个装饰版本,一个加强版本。事实上如果打算写一个有用的装饰器的话,可能会想愿意用装饰版本完全取代原先的函数foo,这样总是会得到我们的”加强版“foo。想要达到这个效果,完全不需要学习新的语法,简单地赋值给变量foo就行了:

foo = outer(foo)
登入後複製

现在,任何怎么调用都不会牵扯到原先的函数foo,都会得到新的装饰版本的foo,现在还是来写一个有用的装饰器

#coding:UTF8

import time
def bar():
    time.sleep(2)
    print(&#39;in the bar&#39;)
def test2(func):
    print(func)
    return func

# print(test2(bar))
bar=test2(bar)
bar()  #run bar

<function bar at 0x00000000026BCF98>
in the bar
登入後複製

  

10. 使用 @ 标识符将装饰器应用到函数和利用*args and **kwargs

Python2.4支持使用标识符@将装饰器应用在函数上,只需要在函数的定义前加上@和装饰器的名称。在上一节的例子里我们是将原本的方法用装饰后的方法代替:

bar=test2(bar)
登入後複製

这种方式能够在任何时候对任意方法进行包装。但是如果自定义一个方法,可以使用@进行装饰:

 1 #coding:UTF8 2  3 import time 4  5 def test2(func): 6     print(func) 7     return func 8 @test2 9 def bar():10     time.sleep(2)11     print(&#39;in the bar&#39;)12 13 bar()  #run bar
登入後複製

 1 #coding:UTF8 2  3 import time 4 def timer(func): #timer(test1)  func=test1 5     def deco(*args,**kwargs): 6         start_time=time.time() 7         func(*args,**kwargs)   #run test1() 8         stop_time = time.time() 9         print("the func run time  is %s" %(stop_time-start_time))10     return deco11 @timer  #test1=timer(test1)12 def test1():13     time.sleep(1)14     print(&#39;in the test1&#39;)15 16 @timer # test2 = timer(test2)  = deco  test2(name) =deco(name)17 def test2(name,age):18     print("test2:",name,age)19 20 test1()21 test2("Tom",22)22 23 24 in the test125 the func run time  is 1.0520000457826 (&#39;test2:&#39;, &#39;Tom&#39;, 22)27 the func run time  is 0.0
登入後複製

下面贡献一个高级版的装饰器:

 1 #coding:utf8 2 import time 3 user,passwd = &#39;hbert&#39;,&#39;abc&#39; 4 def auth(auth_type): 5     print("auth func:",auth_type) 6     def outer_wrapper(func): 7         def wrapper(*args, **kwargs): 8             #print("wrapper func args:", *args, **kwargs) 9             if auth_type == "local":10                 username = raw_input("Username:").strip()11                 password = raw_input("Password:").strip()12                 if user == username and passwd == password:13                     print("\033[32;1mUser has passed authentication\033[0m")14                     res = func(*args, **kwargs)  # from home15                     print("---after authenticaion ")16                     return res17                 else:18                     exit("\033[31;1mInvalid username or password\033[0m")19             elif auth_type == "ldap":20                 print("搞毛线ldap,不会。。。。")21 22         return wrapper23     return outer_wrapper24 25 def index():26     print("welcome to index page")27 @auth(auth_type="local") # home = wrapper()28 def home():29     print("welcome to home  page")30     return "from home"31 32 @auth(auth_type="ldap")33 def bbs():34     print("welcome to bbs  page")35 36 index()37 print(home()) #wrapper()38 bbs()
登入後複製

 

以上是Python裝飾器的詳細介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板