Python裝飾器

Python裝飾器能夠動態地改變函式、方法或類的功能,而不必直接使用子類或更改修飾函式的原始碼。如果使用得當,裝飾器可以成為開發過程中的強大工具。我們來介紹Python中裝飾器函式的實現和應用。

裝飾器函式

裝飾器增強了其他函式或方法的功能,任何將函式作為引數並返回一個增強的函式的函式都可以用作裝飾器

# 這是一個簡單的裝飾器的例子,但它沒有增加被裝飾函式的功能
def super_secret_function(f):
    return f

@super_secret_function
def my_function():
    print("This is my secret function.")

這裡@是一個語法糖,它等同於以下內容:

my_function = super_secret_function(my_function)

為了理解裝飾器的工作原理,記住以下這一點很重要。這個非語法糖語法清楚地說明了為什麼裝飾器函式將函式作為引數,以及為什麼它應該返回另一個函式。下面我們來看一下,如果不返回函式將會發生什麼:

def disabled(f):
    """
    This function returns nothing, and hence removes the decorated function
    from the local scope.
    """
    pass

@disabled
def my_function():
    print("This function can no longer be called...")

my_function()
# TypeError: 'NoneType' object is not callable

因此,我們通常在裝飾器中定義一個新函式並返回它。這個新函式首先要做它需要做的事情,然後呼叫原始函式,最後處理返回值。看下下面這個簡單的裝飾器函式,它列印原始函式接收的引數,然後呼叫原始函式。

#This is the decorator
def print_args(func):
    def inner_func(*args, **kwargs):
        print(args)
        print(kwargs)
        return func(*args, **kwargs) #Call the original function with its arguments.
    return inner_func

@print_args
def multiply(num_a, num_b):
    return num_a * num_b
  
print(multiply(3, 5))
#Output:
# (3,5) - This is actually the 'args' that the function receives.
# {} - This is the 'kwargs', empty because we didn't specify keyword arguments.
# 15 - The result of the function.

裝飾器類

正如上面介紹的,裝飾器是一個可以應用於另一個函式來增強其功能的函式。語法糖相當於以下內容:my_func = decorator(my_func)。但如果如果裝飾器是一個類呢?語法仍然有效,除了my_func被替換為decorator類的例項。如果這個類已經實施了__call__()魔術方法,那麼仍然可以像使用函式一樣的來使用my_func

class Decorator(object):
    """Simple decorator class."""

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

    def __call__(self, *args, **kwargs):
        print('Before the function call.')
        res = self.func(*args, **kwargs)
        print('After the function call.')
        return res

@Decorator
def testfunc():
    print('Inside the function.')

testfunc()
# Before the function call.
# Inside the function.
# After the function call.

請注意,使用類裝飾器來裝飾的函式,其型別將不再被視為函式:

import types
isinstance(testfunc, types.FunctionType)
# False
type(testfunc)
# <class '__main__.Decorator'>

裝飾方法

你需要定義一個額外的__get__方法來裝飾方法。

from types import MethodType

class Decorator(object):
    def __init__(self, func):
        self.func = func
        
    def __call__(self, *args, **kwargs):
        print('Inside the decorator.')
        return self.func(*args, **kwargs)
    
    def __get__(self, instance, cls):
        # Return a Method if it is called on an instance
        return self if instance is None else MethodType(self, instance)

class Test(object):
    @Decorator
    def __init__(self):
        pass
    
a = Test()
Inside the decorator.