函數進階-02裝飾器

裝飾器就是閉包的一個很好的應用拓展!!!python

一丶無參裝飾器

1.1 什麼是裝飾器

器指的是工具,而程序中的函數就是具有某一功能的工具,因此裝飾器指的是爲被裝飾器對象添加額外功能。所以定義裝飾器就是定義一個函數,只不過該函數的功能是用來爲其餘函數添加額外的功能。mysql

須要注意的是:sql

裝飾器自己實際上是能夠任意可調用的對象
被裝飾的對象也能夠是任意可調用的對象mongodb

1.2 爲何要用裝飾器

若是咱們已經上線了一個項目,咱們須要修改某一個方法,可是咱們不想修改方法的使用方法,這個時候可使用裝飾器。由於軟件的維護應該遵循開放封閉原則,即軟件一旦上線運行後,軟件的維護對修改源代碼是封閉的,對擴展功能指的是開放的。閉包

裝飾器的實現必須遵循兩大原則:app

不修改被裝飾對象的源代碼
不修改被裝飾對象的調用方式
裝飾器其實就是在遵循以上兩個原則的前提下爲被裝飾對象添加新功能。函數

1.3 怎麼用裝飾器

改變源代碼工具

import time

def index():
    start = time.time()
    print('welcome to index')
    time.sleep(1)
    end = time.time()
    print(F"index run time is {start-end}")

index()

welcome to index index run time is -1.0008180141448975
編寫重複代碼.net

import time

def index():
    print('welcome to index')
    time.sleep(1)

def f2():
    print('welcome to index')
    time.sleep(1)

start = time.time()
index()
end = time.time()
print(F"index run time is {start-end}")

start = time.time()
f2()
end = time.time()
print(F"f2 run time is {start-end}")

welcome to index index run time is -1.0046868324279785 welcome to index f2 run time is -1.000690221786499
第一種傳參方式:改變調用方式代理

import time

def index():
    print('welcome to index')
    time.sleep(1)

def time_count(func):
    start = time.time()
    func()
    end = time.time()
    print(f"{func} time is {start-end}")

time_count(index)

welcome to index <function index at 0x102977378> time is -1.000748872756958
第二種傳參方式:包給函數-外包

import time

def index():
    print('welcome to index')
    time.sleep(1)

def time_count(func):
    # func = 最原始的index
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print(f"{func} time is {start-end}")
    return wrapper

# f = time_count(index)
# f()

index = time_count(index)  # index爲被裝飾函數的內存地址,即index = wrapper
index()  # wrapper()

welcome to index <function index at 0x102977730> time is -1.0038220882415771

1.4 完善裝飾器

上述的裝飾器,最後調用index()的時候,實際上是在調用wrapper(),所以若是原始的index()有返回值的時候,wrapper()函數的返回值應該和index()的返回值相同,也就是說,咱們須要同步原始的index()和wrapper()方法的返回值。

import time


def index():
    print('welcome to index')
    time.sleep(1)

    return 123

def time_count(func):
    # func = 最原始的index
    def wrapper():
        start = time.time()
        res = func()
        end = time.time()
        print(f"{func} time is {start-end}")

        return res
    return wrapper

index = time_count(index)
res = index()
print(f"res: {res}")

welcome to index <function index at 0x102977620> time is -1.0050289630889893 res: 123
若是原始的index()方法須要傳參,那麼咱們以前的裝飾器是沒法實現該功能的,因爲有wrapper()=index(),因此給wrapper()方法傳參便可。

import time


def index():
    print('welcome to index')
    time.sleep(1)
    return 123

def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)

    return name

def time_count(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print(f"{func} time is {start-end}")

        return res
    return wrapper

home = time_count(home)

res = home('egon')
print(f"res: {res}")

welcome egon to home page <function home at 0x102977378> time is -1.0039079189300537 res: egon

1.5裝飾器語法糖

在被裝飾函數正上方,而且是單獨一行寫上@裝飾器名

import time

def time_count(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print(f"{func} time is {start-end}")

        return res
    return wrapper

@time_count  # home = time_count(home)
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)

    return name

@time_count  # index = time_count(index)
def index():
    print('welcome to index')
    time.sleep(1)

    return 123

res = home('egon')
print(f"res: {res}")

welcome egon to home page <function home at 0x102977620> time is -1.0005171298980713 res: egon

1.6裝飾器模板

雙層裝飾器模板

def deco(func): def wrapper(*args,**kwargs): res = func(*args,**kwargs) return res return wrapper

三層裝飾器模板(有參裝飾器)

通常參數器最多3層,也能夠套更多層,但沒有意義,3層足夠裝飾器自己的實現想要的功能添加的需求,固然也能夠給某個想實現功能函數或類裝飾更多的功能裝飾器來實現不一樣場景狀況
提早說下哈...


二丶有參裝飾器

無參裝飾器只套了兩層,本節將講一個套三層的裝飾器——有參裝飾器,但如今咱們先實現一個用戶登陸註冊的裝飾器。

import time

current_user = {'username': None}

def login(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):
        if current_user['username']:
            res = func(*args, **kwargs)
            return res
        user = input('username: ').strip()
        pwd = input('password: ').strip()

        if user == 'nash' and pwd == '123':
            print('login successful')
            current_uesr['usre'] = user
            res = func(*args, **kwargs)
            return res
        else:
            print('user or password error')
    return wrapper

@login
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)
    return name

@login
def index():
    print('welcome to index')
    time.sleep(1)
    return 123

res = index()

username: nash password: 123 login successful welcome to index
對於上面的登陸註冊,咱們把用戶登陸成功的信息寫入內存當中。可是在工業上,用戶信息能夠存在文本中、mysql中、mongodb當中,可是咱們只讓用戶信息來自於file的用戶能夠認證。所以咱們能夠改寫上述的裝飾器。

import time

current_user = {'username': None}


def login(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):

        if current_user['username']:
            res = func(*args, **kwargs)
            return res

        user = input('username: ').strip()
        pwd = input('password: ').strip()
        
        engine = 'file'

        if engine == 'file':
            print('base of file')
            if user == 'nash' and pwd == '123':
                print('login successful')
                current_uesr['usre'] = user
                res = func(*args, **kwargs)
                return res
            else:
                print('user or password error')
        elif engine == 'mysql':
            print('base of mysql')
        elif engine == 'mongodb':
            print('base of mongodb')
        else:
            print('default')
    return wrapper

@login
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)

@login
def index():
    print('welcome to index')
    time.sleep(1)


res = index()

username: nash password: 123 base of file login successful welcome to index

2.1三層閉包

def f1(y):
    def f2():
        x = 1
        def f3():
            print(f"x: {x}")
            print(f"y: {y}")
        return f3
    return f2

f2 = f1(2)
f3 = f2()
f3()

x: 1 y: 2
如今需求改了,咱們須要判斷用戶動態的獲取用戶密碼的方式,若是是file類型的,咱們則讓用戶進行認證。所以咱們可使用有參裝飾器。

import time

current_uesr = {'username': None}


def auth(engine='file'):

    def login(func):
        # func = 最原始的index
        def wrapper(*args, **kwargs):
            if current_user['username']:
                res = func(*args, **kwargs)

                return res

            user = input('username: ').strip()
            pwd = input('password: ').strip()

            if engine == 'file':
                print('base of file')
                if user == 'nash' and pwd == '123':
                    print('login successful')
                    current_uesr['usre'] = user
                    res = func(*args, **kwargs)
                    return res
                else:
                    print('user or password error')
            elif engine == 'mysql':
                print('base of mysql, please base of file')
            elif engine == 'mongodb':
                print('base of mongodb, please base of file')
            else:
                print('please base of file')

        return wrapper

    return login

@auth(engine='mysql')
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)

@auth(engine='file')
def index():
    print('welcome to index')
    time.sleep(1)

res = index()

username: nash password: 123 base of file login successful welcome to index
因爲兩層的裝飾器,參數必須得固定位func,可是三層的裝飾器解除了這個限制。咱們不只僅可使用上述單個參數的三層裝飾器,多個參數的只須要在三層裝飾器中多加入幾個參數便可。也就是說裝飾器三層便可,多加一層反倒無用。


三丶裝飾器應用於類

回顧什麼是裝飾器:
  裝飾器定義:本質就是函數(高階函數),功能是爲其餘函數(對象)添加新功能

1、類的裝飾器基本實現原理以下:

def deco(cls):
    print('類的裝飾器=========》')
    print('='*20)
    return cls

@deco      #====> Foo = deco(Foo)
class Foo:
    pass

2、上述的簡單裝飾器是沒有參數的,如今咱們加上參數

def cls_decorator(**kwargs):            #支持傳入參數(屬性和對應的值),字典形式
    def deco(cls):
        for key,val in kwargs.items():
            setattr(cls,key,val)        #給類設置對應的屬性和值
        return cls
    return deco

@cls_decorator(name='Menawey',age=24,gender='male')        # 1 運行cls_decorator(...),返回deco;2 @deco===> Peolple = deco(People)
#至關於給People類設置了name、age、gender屬性
class People:
    pass

print(People.__dict__)                                   #查看被裝飾過的類的屬性字典

經過這樣就能夠動態的給不一樣的類在他實例化前增長屬性

3、結合描述符
  經過描述符和類的裝飾器組合使用,能夠完成不少功能,好比爲類添加屬性,而且能夠限定屬性的類型。

#描述符
class Desc:
    def __init__(self,key,value_type):
        self.key = key
        self.value_type = value_type
    def __get__(self, instance, owner):
        return instance.__dict__[self.key]
    def __set__(self, instance, value):
        if not isinstance(value,self.value_type):
            raise TypeError('%s 傳入的類型不是 %s'%(self.key,self.value_type))
        instance.__dict__[self.key] = value
    def __delete__(self, instance):
        instance.__dict__.pop(self.key)

#裝飾器
def cls_decorator(**kwargs):            #支持傳入參數(屬性和對應的值),字典形式
    def deco(cls):
        for key,val in kwargs.items():    #這裏須要用到描述符對屬性進行代理,可是val是指定的類型,因此要用Desc(key,val)來描述爲對應的值
            setattr(cls,key,Desc(key,val))        #給類設置對應的屬性和值
        return cls
    return deco

@cls_decorator(name=str,age=int,gender=str,salary=float)    #使用裝飾器

#被裝飾和描述的類
class People:
    def __init__(self,name,age,gender,salary):
        self.name = name
        self.age = age
        self.gender = gender
        self.salary = salary

p1 = People('Menawey',24,'male',11.1)  #由於gender屬性指定的是sstr,可是TypeError: age 傳入的類型不是 <class 'int'>
print(People.__dict__)


四丶腦大洞開

What??!! python中類做爲裝飾器!!!??
參考連接:
https://blog.csdn.net/qq_29767317/article/details/80799410

da



#重寫了__call__方法,類的對象就可以被調用,直接使用t()來調用類,並打印__call__方法裏面的內容
class Test(object):
    print("--------test1----------")
    def __call__(self, *args, **kwargs):
        print(da) #注意一點,__call__方法裏面的是最後執行。
    def  eat(self):
        print("-----------eat-------------")
    print("---------test2---------")
t = Test()
t()

執行結果:
D:\python3.6\pythonw.exe F:/python項目/09Day/03python高級/01類做爲裝飾器.py
--------test1----------
---------test2---------
---------test---------

類作爲裝飾器:

class Test(object):
    def __init__(self,func):
        print("-------初始化-------")
        print("func name is %s" %func.__name__)
        self.__func = func #類的私有屬性self.__func也指向了test1函數的內存地址。
    def __call__(self, *args, **kwargs): #test1 = Test(test1) #調用類的對象。就會調用call方法。
        print("------裝飾器中的功能-------")
        self.__func() #self.__func指向了函數test1的內存地址。這句話至關於執行test1()函數。


#使用類做爲裝飾器,須要重寫Call方法,沒有調用test1()方法的時候,執行代碼獲得下面的結果
# -------初始化-------
# func name is test1

@Test #至關於 test1 = Test(test1)  也至關於func指向了下面函數test1的名字, 前面的test1指向了 Test()這個對象。
# 調用test1對象的時候至關於調用類方法Test(),調用類方法必調用__call方法,調用call方法的時候,先執行    print("------裝飾器中的功能-------")
#而後在執行self.__func() ,由於self.__func函數指向的是test1函數,test1()至關於執行self.__func().
def test1():
    print("----test1---------")

test1() #調用test1

D:\python3.6\pythonw.exe F:/python項目/09Day/03python高級/02類做爲裝飾器.py -------初始化------- func name is test1 ------裝飾器中的功能------- ----test1---------

相關文章
相關標籤/搜索