1. 程式人生 > >python黑魔法——裝飾器

python黑魔法——裝飾器

目錄

一、簡單的例子

使用@裝飾符(下面的@deco等同於myfunc = deco(myfunc))

def deco(func):
    print("step in deco")
    func()
    print("leave out deco")
    return func

@deco
def myfunc():
    print("myfunc() called")

myfunc()
myfunc()

輸出:
>>> step in deco
    myfunc() called
    leave out deco
    myfunc() called
    myfunc() called

可以發現裝飾器中的內容只執行了一次,而myfunc函式卻多執行了一次(其實是在@deco這句執行的),這是為什麼呢?回到之前說的@deco等同於myfunc = deco(myfunc),此時@deco執行了一次myfunc,並且返回myfunc, 也就是說其實是執行了myfunc = myfunc(myfunc被賦值為自己本身),所以並沒有達到裝飾的效果。
其實我們想要的效果是myfunc = deco 而非 myfunc = deco(myfunc)

改進的方法:裝飾器加入內層函式

def deco(func):
    def wrapper():
        print("step in deco")
        func()
        print("leave out deco")
    return wrapper

@deco
def myfunc():
    print("myfunc() called")

myfunc()
myfunc()

輸出:
>>> step in deco
    myfunc() called
    leave out deco
    step in
deco myfunc() called leave out deco

這時候,@deco並沒有執行myfunc,而是返回帶有func引數閉包的wrapper函式的引用,這是我們呼叫myfunc的時候就相當於呼叫了wrapper,就起到了裝飾的效果。


二、修飾帶引數和存在返回值的函式

def deco(func):
    def wrapper(a, b):
        print("step in deco")
        ret = func(a, b)
        print("leave out deco")
        return ret
    return wrapper

@deco
def myfunc(a, b):
    print("myfunc called")
    return a + b

print(myfunc(3, 5))

輸出:
>>> step in deco
    myfunc called
    leave out deco
    8

@裝飾符的意義和上面的簡單例子相同,只是在內層wrapper函式加入了引數和返回值


三、帶引數的裝飾器

有時候我們想讓一個裝飾器有多種裝飾功能,在使用@裝飾符的時候通過向裝飾器傳遞引數以指明呼叫哪種裝飾功能

import time

def deco(arg):
    def _deco(func):
        def __deco():
            if arg == 'year':
                print("the year is: {0}".format(time.localtime(time.time()).tm_year))
            elif arg == 'month':
                print("the month is: {0}".format(time.localtime(time.time()).tm_mon))
            elif arg == 'day':
                print("the day is: {0}".format(time.localtime(time.time()).tm_mday))
            else:
                print("the time is: {0}".format(time.ctime()))
            func()
        return __deco
    return _deco

@deco("year")
def myfunc():
    print("myfunc() called")

@deco("month")
def myfunc2():
    print("myfunc2() called")

@deco("")
def myfunc3():
    print("myfunc3() called")

myfunc()
myfunc2()
myfunc3()

輸出:
>>> the year is: 2018
    myfunc() called
    the month is: 6
    myfunc2() called
    the time is: Wed Jun  6 01:26:33 2018
    myfunc3() called

同理,此處的@deco(“year”)等同於myfunc = deco(“year”)(myfunc),其實最終等同於myfunc = __deco,所以需要三層函式來實現


四、裝飾器呼叫順序

import time

def deco(arg):
    def _deco(func):
        def __deco():
            if arg == 'year':
                print("the year is: {0}".format(time.localtime(time.time()).tm_year))
            elif arg == 'month':
                print("the month is: {0}".format(time.localtime(time.time()).tm_mon))
            elif arg == 'day':
                print("the day is: {0}".format(time.localtime(time.time()).tm_mday))
            else:
                print("the time is: {0}".format(time.ctime()))
            func()
        return __deco
    return _deco

@deco("year")
@deco("month")
@deco("day")
def myfunc():
    print("myfunc called")

myfunc()

輸出:
>>> the year is: 2018
    the month is: 6
    the day is: 6
    myfunc called

再來看另一個例子:

def deco1(func):
    print("step in deco1")
    def wrapper1():
        print("step in wrapper1")
        func()
        print("leave out wrapper1")
    print("leave out deco1")
    return wrapper1

def deco2(func):
    print("step in deco2")
    def wrapper2():
        print("step in wrapper2")
        func()
        print("leave out wrapper2")
    print("leave out deco2")
    return wrapper2

def deco3(func):
    print("step in deco3")
    def wrapper3():
        print("step in wrapper3")
        func()
        print("leave out wrapper3")
    print("leave out deco3")
    return wrapper3

@deco1
@deco2
@deco3
def myfunc():
    print("myfunc() called")

myfunc()

輸出:
>>> step in deco3
    leave out deco3
    step in deco2
    leave out deco2
    step in deco1
    leave out deco1
    step in wrapper1
    step in wrapper2
    step in wrapper3
    myfunc() called
    leave out wrapper3
    leave out wrapper2
    leave out wrapper1

可以看到,呼叫裝飾器的順序和@裝飾符的宣告順序相反的,但是呼叫wrapper的順序卻和裝飾符的宣告順序相同。此處等同於myfunc = deco3(deco2(deco1(myfunc))),其實就是內層函式wrapper3裝飾了myfunc,wrapper2裝飾了wrapper3, wrapper1裝飾了wrapper2.


五、python內建的裝飾器

在Python中有三個內建的裝飾器,都是跟class相關的:staticmethod、classmethod 和property。

  • staticmethod 是類靜態方法,其跟成員方法的區別是沒有 self 引數,並且可以在類不進行例項化的情況下呼叫
  • classmethod 與成員方法的區別在於所接收的第一個引數不是 self (類例項的指標),而是cls(當前類的具體型別)
  • property 是屬性的意思,表示可以通過通過類例項直接訪問的資訊(可以用其實現C#的get和set方法)

三者的具體用法:

@property

使呼叫類中的方法像引用類中的欄位屬性一樣。被修飾的特性方法,內部可以實現處理邏輯,但對外提供統一的呼叫方式。遵循了統一訪問的原則。
property函式原型為property(fget=None,fset=None,fdel=None,doc=None)
示例:

# coding: utf-8
class TestClass:
    name = "test"

    def __init__(self, name):
        self.name = name

    @property
    def sayHello(self):
        print "hello", self.name

cls = TestClass("felix")
print "通過例項引用屬性"
print cls.name
print "像引用屬性一樣呼叫@property修飾的方法"
cls.sayHello

實現類似java和C#get的功能

class Boy(object):
    def __init__(self, name):
        self.name = name

        @property
        def name(self):
            return self.name

        # 下面這個裝飾器是由上面的@property衍生出來的裝飾器
        @name.setter
        def name(self, new_name):
            self.name = new_name

        # 下面這個裝飾器是由上面的@property衍生出來的裝飾器
        @name.deleter
        def del_name(self):
            del self.name

# 例項化
boy = Boy('Tom')

# 更新name(set方法)
boy.name = 'Alice'
# 獲得'Alice'(get方法)
print(boy.name) 
# 刪除name(del方法)
del boy.name

@staticmethod

將類中的方法裝飾為靜態方法,即類不需要建立例項的情況下,可以通過類名直接引用。到達將函式功能與例項解綁的效果。

class TestClass:
    name = "test"

    def __init__(self, name):
        self.name = name

    @staticmethod
    def fun(self, x, y):
        return  x + y

cls = TestClass("felix")
print ("通過例項引用方法")
print (cls.fun(None, 2, 3)) # 引數個數必須與定義中的個數保持一致,否則報錯
print ("類名直接引用靜態方法")
print (TestClass.fun(None, 2, 3)) # 引數個數必須與定義中的個數保持一致,否則報錯


輸出:
>>> 通過例項引用方法
    5
    類名直接引用靜態方法
    5

@classmethod

類方法的第一個引數是一個類,是將類本身作為操作的方法。類方法被哪個類呼叫,就傳入哪個類作為第一個引數進行操作。

class Car(object):
    car = "audi"

    @classmethod
    def value(self, category): # 可定義多個引數,但第一個引數為類本身
        print ("{0} car of {1}".format(category, self.car))

class BMW(Car):
    car = "BMW"

class Benz(Car):
    car = "Benz"

print ("通過例項呼叫")
baoma = BMW()
baoma.value("Normal") # 由於第一個引數為類本身,呼叫時傳入的引數對應的時category

print ("通過類名直接呼叫")
Benz.value("SUV")


輸出:
>>> 通過例項呼叫
    Normal car of BMW
    通過類名直接呼叫
    SUV car of Benz