1. 程式人生 > >Python 進階用法 (持續更新)

Python 進階用法 (持續更新)

裝飾器(Decorator)

Python 的裝飾器是任何可呼叫物件(callable object),用於修改函式(Function)或類(Class)。按照用途可分為:

  • 函式裝飾器
  • 類裝飾器

裝飾器的介面定義可概括為:

  1. 接收某個函式或類的引用作為引數;
  2. 修改該函式或類並返回。

簡單函式裝飾器示例

理解裝飾器

def my_decorator(func):
    def wrapped_func(arg):
        print("Before calling " + func.__name__)
        func(arg)
        print("After calling " + func.__name__)
    return wrapped_func

def foo(arg):
    print("Hi, foo has been called with " + str(arg) )

print("We call foo BEFORE decoration:")
foo("no decoration"); print()

print("We NOW decorate foo with my_decorator...\n")
foo = my_decorator(foo)

print("We call foo AFTER decoration:")
foo("decoration")

程式對應的輸出為:

We call foo BEFORE decoration:
Hi, foo has been called with no decoration

We NOW decorate foo with my_decorator...

We call foo AFTER decoration:
Before calling foo
Hi, foo has been called with decoration
After calling foo

上述例子中的用法在實際開發中並不常用,我們更傾向於下面的寫法。

語法糖寫法

Python 提供一種更簡潔、直觀的裝飾器寫法。例如我們可以把上述例子寫成更簡便的形式:

def my_decorator(func):
    def wrapped_func(arg):
        print("Before calling " + func.__name__)
        func(arg)
        print("After calling " + func.__name__)
    return wrapped_func

@my_decorator
def foo(arg):
    print("Hi, foo has been called with " + str(arg) )

foo("decoration")

函式裝飾器應用示例

統計函式呼叫次數

def call_counter(func):
    def func_with_counts(arg):
        func_with_counts.calls += 1
        return func(arg)
    func_with_counts.calls = 0
    return wrapped_func

@call_counter
def foo(arg):
    return "foo: " + str(arg)

print("foo has been called {} time(s)".format(foo.calls))
for i in range(0, 51, 10):
    foo(i)
print("foo has been called {} time(s)".format(foo.calls))

程式對應的輸出結果是:

foo has been calld 0 time(s)
foo: 0
foo: 10
foo: 20
foo: 30
foo: 40
foo: 50
foo has been calld 6 time(s)

用記憶表(Memoization1)優化 Fibonacci 數列演算法

def memoize(func):
    memo = {}
    def memoized_func(arg):
        if arg not in memo:
            memo[arg] = func(arg)
        return memo
    return memoized_func

@memoize
def fib(n):
    if n == 0:
        return 0
    elif n = 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)

for i in range(20):
    print(fib(i), end=', ')
print(fib(20))

程式對應的輸出結果是:

    
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765

類裝飾器

首先需要明確,Python 的裝飾器是任何可呼叫物件而不僅限於函式。我們可以定義一個類並使其物件可呼叫。例如我們可以定義一個帶快取功能的類計算 Fibonacci:

class Fibonacci:
    def __init__(self):
        self.cache = {}
    def __call__(self, n):
        if n not in self.cache:
            if n == 0:
                self.cache[0] = 0
            elif n == 1:
                self.cache[1] = 1
            else:
                self.cache[n] = self.cache[n-1] + self.cache[n-2]
        return cache[n]

fib = Fibonacci()
for i in range(20):
    print(fib(i), end=', ')
print(fib(20))

程式對應的輸出結果是:

    
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765

因此,我們可以構建一個可呼叫物件並令其作為裝飾器。
在下面的例子中,我們用類裝飾器統計函式呼叫次數:

class CallCounter:
    def __init__(self, func):
        self.calls = 0
        self.func = func
    def __call__(self, arg):
    self.calls += 1
        return self.func(arg)

@CallCounter
def foo(arg):
    return "foo: " + str(arg)
    

print("foo has been called {} time(s)".format(foo.calls))
for i in range(0, 51, 10):
    foo(i)
print("foo has been called {} time(s)".format(foo.calls))

Reference
Written with StackEdit.


  1. 注意 Memoization 是專業術語,不是 Memorization。