python(四)下:python裝飾器詳解

裝飾器

一、介紹

  • :代表函數的意思。裝飾器本質就是是函數
  • 功能:裝飾其他函數,就是爲其他函數添加附加功能

    被裝飾函數感受不到裝飾器的存在
  • 原則:
    1. 不能修改被裝飾的函數的源代碼(比如線上環境)
    2. 不能修改被裝飾的函數的調用方式
       
  • 實現裝飾器知識儲備
    1. 函數即是“變量”
    2. 高階函數
    3. 嵌套函數

高階函數+嵌套函數=>裝飾器

二、通過高階函數+嵌套函數==>實現裝飾器

先分析以下兩段代碼能不能運行?

def foo():
    print("in the foo")
    bar()
def bar():
    print("in the bar")

foo()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
def foo():
    print("in the foo")
    bar()

foo()
def bar():
    print("in the bar")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

第二段代碼報錯:

NameError: name 'bar' is not defined
  • 1

1、變量知識回顧

定義變量:
如:定義變量:x=1,會在內存中找塊內存空間把“1”存進去,把“1”的內存地址給x
 
前面提到:函數即變量

# 定義函數
def test():
    pass
# 就相當於把函數體賦值給test變量
test = '函數體'  # 函數體就是一堆字符串而已
# 只不過函數調用要加上小括號調用
test()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

 
python內存回收機制,是解釋器做的。解釋器到底怎麼去回收這個變量?
python解釋器當中有種概念叫做引用計數。什麼叫引用計數呢?
比如:定義x=1,之後又定義了y=1或y=x,實際上又把內存空間“1”的內存地址賦值給y
這裏x代表一次引用,y代表一次引用。加起來兩次引用。
python什麼時候會把“1”這個內存空間清空呢?會回收內存呢?
當x這個變量沒有了,y這個變量也沒有了,便會把“1”這個內存空間清掉

del x  # 刪的只是變量名,內存中的值是解釋器回收
  • 1

 
匿名函數

lambda x:x*x
  • 1

匿名函數沒有函數名,沒有引用,所以會被垃圾回收機制立馬回收掉。
所以匿名函數要賦值給變量,把函數體賦值給變量名

calc = lambda x:x*x
print(calc(4))
  • 1
  • 2

現在可以再理解下最開始兩段代碼能不能運行的原因。
 

2、高階函數(裝飾器前奏)

什麼叫高階函數呢:

  1. 把一個函數名當做形實傳給另外一個函數
  2. 返回值中包含函數名
def f1():
    print("in the func1")

def test1(func):
    print(func)

test1(f1)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

運行結果(打印內存地址)

<function func1 at 0x000002805DE12378>
  • 1

如下代碼,能不能運行:

def f1():
    print("in the func1")

def test1(func):
    print(func)
    func()

test1(f1)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

函數即變量,像“x=1,y=x”,同樣f是一個是一個函數,可不可以像一個變量一樣來回賦值呢?

import time
def func1():
    print("in the func1")
    time.sleep(1)
def test1(func):
    start_time = time.time()
    func()
    stop_time = time.time()
    print("the func run time is %s" %(stop_time-start_time))

test1(func1)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

到這裏,貌似實現了裝飾函數的功能。
看上面裝飾器的原則:
這裏:沒有修改func1的源代碼,但是調用方式改變了。現在是test1(func1),之前是func1()
現在能做到哪一點呢?
把一個函數名當做實參傳給另外一個函數(不修改被裝飾的函數源代碼的情況下爲其添加功能)


轉載請務必保留此出處:http://blog.csdn.net/fgf00/article/details/52319711


2) 下面用第二個條件(返回值中包含函數名),做另外一個高階函數

import time
def func2():
    time.sleep(1)
    print("in the func2")
def test2(func):
    print(func)
    return(func)

print(test2(func2))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

運行結果:

<function func2 at 0x00000162F3672378>
<function func2 at 0x00000162F3672378>
  • 1
  • 2

把函數內存地址都打印出來了,看到這麼多內存地址,有什麼想法?
加上小括號就能運行。
上面代碼“test2(func2())”和“test2(func2)”有什麼區別?加上小括號是函數返回結果,不加是函數內存地址。所以加上小括號就不符合高階函數定義了。
既然以後有了函數的內存地址,是不是可以賦值給其他變量?下面

import time
def func2():
    print("in the func2")
    time.sleep(1)
def test2(func):
    print(func)
    return(func)

t = test2(func2)
print(t)
t()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

好像還沒什麼用,怎麼讓他有用呢?
把test2(func2)賦值給func2

import time
def func2():
    print("in the func2")
    time.sleep(1)
def test2(func):
    print(func)
    return(func)

func2 = (test2(func2))
func2()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

這就是高階函數的第二個好處:返回值中包含函數名(不修改函數的調用方式)

3、嵌套函數(裝飾器前戲)

嵌套函數:在一個函數體內,用def去聲明一個函數

def foo():
    print("in the foo")
    def bar():
        print("in the bar")
    bar()

foo()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

看一下下面的代碼是不是嵌套:

def foo():
    print("in the foo")
def bar():
    foo()

bar()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

注意函數嵌套和函數調用區別
 
局部作用域和全局作用域的訪問順序:

x = 0
def grandpa():
    # x = 1
    def dad():
        x = 2
        def son():
            x = 3
            print(x)
        son()
    dad()
grandpa()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

三、裝飾器

1、裝飾器

前面鋪墊了那麼多,現在開講正題:裝飾器
先用高階函數實現給函數不修改源代碼的情況下添加功能

import time
def deco(func):
    start_time = time.time()
    func()
    stop_time = time.time()
    print("the func tun time is %s" %(stop_time-start_time))
def test1():
    time.sleep(1)
    print("in the test1")
def test2():
    time.sleep(1)
    print("in the test2")

deco(test1)
deco(test2)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

按照上面說的,如何實現不改變調用方式?直接“test1 = deco(test1)”和“test2 = deco(test2)”嗎?
別忘記了,第二種方式,高階函數要加上return,如下

import time
def deco(func):
    start_time = time.time()
    return func()
    stop_time = time.time()
    print("the func tun time is %s" %(stop_time-start_time))
def test1():
    time.sleep(1)
    print("in the test1")
def test2():
    time.sleep(1)
    print("in the test2")

test1 = deco(test1)
test2 = deco(test2)

deco(test1)
deco(test2)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

雖然沒有修改源代碼和調用方式,但是函數加上return,函數就結束了,然並卵。怎麼實現呢?
前面一直在用高階函數,還沒有用嵌套函數,加上嵌套函數能不能實現呢?看一下

import time
def timer(func):  # timer(test1)  func=test1
    def deco():
        start_time = time.time()
        func()
        stop_time = time.time()
        print("the func tun time is %s" %(stop_time-start_time))
    return deco  # 返回deco的內存地址
def test1():
    time.sleep(1)
    print("in the test1")
def test2():
    time.sleep(1)
    print("in the test2")

print(timer(test1))  # 可見:返回deco的內存地址
test1 = timer(test1)
test1()
timer(test2)()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

到此,完成實現了裝飾器的功能。但是還是有點麻煩,如何能不要“test1 = timer(test1)”,
python解釋器提供了語法糖“@”符合,給哪個函數新增功能,就加在哪個函數頭部

import time
def timer(func):  # timer(test1)  func=test1
    def deco():
        start_time = time.time()
        func()
        stop_time = time.time()
        print("the func tun time is %s" %(stop_time-start_time))
    return deco  # 返回deco的內存地址
@timer
def test1():
    time.sleep(1)
    print("in the test1")
@timer
def test2():
    time.sleep(1)
    print("in the test2")

test1()
test2()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

2、有參裝飾器

前面實現了裝飾器的功能,但是如果函數有參數,能不能也能運行呢

import time
def timer(func):  # timer(test1)  func=test1
    def deco():
        start_time = time.time()
        func()
        stop_time = time.time()
        print("the func tun time is %s" %(stop_time-start_time))
    return deco  # 返回deco的內存地址
@timer
def test1():
    time.sleep(1)
    print("in the test1")
@timer
def test2(name):
    time.sleep(1)
    print("in the test2",name)

test1()
test2()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

報錯:丟失參數

TypeError: test2() missing 1 required positional argument: 'name'
  • 1

@timer 相當於 test2=timer(test2) =deco
test2() 相當於運行deco(),所以沒指定參數,報錯。
如何傳參數呢?爲了適應各種不同參數的函數

import time
def timer(func):  # timer(test1)  func=test1
    def deco(*args,**kwargs):
        start_time = time.time()
        func(*args,**kwargs)
        stop_time = time.time()
        print("the func tun time is %s" %(stop_time-start_time))
    return deco  # 返回deco的內存地址
@timer
def test1():
    time.sleep(1)
    print("in the test1")
@timer
def test2(name):
    time.sleep(1)
    print("in the test2",name)

test1()
test2("fgf")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

3、終極裝飾器

注意,上面的例子中還沒有涉及返回值,看下面的例子可以體會一下
假設:公司網站需要驗證登錄,有不同的驗證方式:本地認證、LDAP認證等

#/usr/bin/env python
# -*- coding: UTF-8 -*-

import time
user,passwd = 'fgf','abc123'
def auth(auth_type):
    print("auth func:",auth_type)
    def outer_wrapper(func):
        def wrapper(*args, **kwargs):
            print("wrapper func args:", *args, **kwargs)
            if auth_type == "local":
                username = input("Username:").strip()
                password = input("Password:").strip()
                if user == username and passwd == password:
                    print("\033[32;1mUser has passed authentication\033[0m")
                    res = func(*args, **kwargs)  # from home
                    print("---after authenticaion ")
                    return res
                else:
                    exit("\033[31;1mInvalid username or password\033[0m")
            elif auth_type == "ldap":
                print("搞毛線ldap,不會。。。。")

        return wrapper
    return outer_wrapper

def index():
    print("welcome to index page")
@auth(auth_type="local") # home = wrapper()
def home():
    print("welcome to home  page")
    return "from home"

@auth(auth_type="ldap")
def bbs():
    print("welcome to bbs  page")

index()
print(home()) #wrapper()
bbs()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

轉載請務必保留此出處:http://blog.csdn.net/fgf00/article/details/52319711

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章