Decorator Python là gì

Decorator trong Python khá dễ hiểu và quen thuộc với những lập trình viên Python. Tuy nhiên việc viết Decorator cũng yêu cầu tổng hợp rất nhiều các kỹ năng khác nhau. Để viết Decorator trong Python, trước hết các lập trình viên cần hiểu một số khái niệm sau đây:

  1. closures
  2. Cách sử dụng hàm như một tham số [function as first-class arguments].
  3. Cách mã nguồn python được thực thi như thế nào.

Các khái niệm này cũng khá dễ để tìm tài liệu trên mạng, tuy nhiên khái niệm đầu tiên có thể khó hiểu với mọi người nên mình sẽ giải thích khái niệm này. Trong Python, “closures” là các hàm được viết trong thân hàm khác [functions within function], hàm này có thể truy cập các biến ở hàm bên ngoài nó [outer function]. Ở đây mình xin phép chỉ đi sâu vào các ứng dụng Python Decorator trong việc phân tích, lưu vết và tính toán hiệu năng.

Với những ứng dụng phức tạp, chúng ta thường xuyên phải lưu các thông số của hệ thống để phục vụ các mục đích khác nhau trong phân tích, tính toán hiệu năng, và ta có thể dùng Decorator để thuận tiện trong các tác vụ này. Chẳng hạn đoạn mã nguồn dưới đây sẽ cho ta thông tin khi nào hàm được gọi, thời gian thực thi của hàm:

Kết quả khi chạy đoạn mã nguồn trên như sau:

Đồng bộ hoá

Có thể sử dụng Decorator cho mục đích đồng bộ hoá trong khi chạy đa luồng.

ta sử dụng 100 luồng để tăng giá trị của counter, vì vậy giá trị counter sau khi chạy mã nguồn sẽ là 100. Việc sử dụng decorators ở đây để cho việc hàm increment sẽ được đồng bộ trong các luồng.

Xác thực trong các framework

Decorator có thể sử dụng để viết các hàm xác thực việc đăng nhập vào hệ thống, thường được sử dụng trong các web framework như flask, Django. Một Decorator đơn giản có thể sử dụng như sau:

Decorators Authenticate có chức năng chuyển hướng về trang đăng nhập nếu thông tin người dùng nhập vào xác thực không đúng. Trong trường hợp thông tin đăng nhập chính xác, hàm index sẽ được khởi chạy để đưa người dùng tới trang tiếp theo [index.mako].

Trên đây là một số ứng dụng của Decorator trong Python. Việc sử dụng có thể không hề dễ dàng khi mới tiếp cận với bất cứ lập trình viên nào. Nhưng hiệu quả của nó mang lại lại cực kỳ lớn. Mong rằng qua bài viết này, các bạn sẽ cảm thấy hứng thú với những gì mà Decorators có thể làm được.

Decorators có lẽ là một design pattern đẹp nhất và mạnh mẽ nhất trong Python, nhưng đồng thời khái niệm này được nhiều người cho là phức tạp. Chính xác, việc sử dụng decorators rất dễ dàng, nhưng việc viết decorator có thể trở nên phức tạp nếu bạn không có kinh nghiệm với decorator và một số khái niệm functional program.

Mặc dù khái niệm cơ bản giống nhau, Python có hai loại decorator khác nhau:

    Function decorators

    Class decorators

Một decorator trong Python là bất kỳ đối tượng Python callable nào được sử dụng để sửa đổi một hàm hay một lớp. Một tham chiếu đến một hàm "func" hoặc một lớp "C" được truyền cho một decorator và decorator sẽ trả về một hàm hay lớp được sửa đổi. Các hàm hoặc lớp sửa đổi thường chứa các lời gọi đến hàm gốc "func" hoặc lớp "C". Chú ý “C” là class.

Bước đầu để decorator

Để giới thiệu decorator dễ hình dung nhất, chúng tôi sẽ giới thiệu decorator bằng cách lặp lại một số khía cạnh quan trọng của các hàm. Đầu tiên bạn phải biết hoặc nhớ rằng các tên hàm là các tham chiếu đến các hàm và chúng ta có thể gán nhiều tên cho cùng một hàm:

def add[x, y]:
    return x + y

add_negtive = add
add_positive = add
print "add_negtive[-1,-3] = ", add_negtive[-1, -3]
print "add_positive[3,4] = ", add_positive[3, 4]

Output:

add_negtive[-1,-3] =  -4

add_positive[3,4] =  7

Điều này có nghĩa là chúng tôi có ba tên, tức là "add", "add_ negtive" và “add_ positive” cho cùng một chức năng. Thực tế, chúng ta có thể xoá "add" hoặc "add_ negtive" hoặc “add_ positive” mà không xoá chức năng của chính nó.

def add[x, y]:
    return x + y
add_negtive = add
add_positive = add
print "add_negtive[-1,-3] = ", add_negtive[-1, -3]
print "add_positive[3,4] = ", add_positive[3, 4]
del add
print "add[] was deleted, add_negtive[-1,-3] = ", add_negtive[-1, -3]

Output:

add_negtive[-1,-3] =  -4

add_positive[3,4] =  7

add[] was deleted, add_negtive[-1,-3] = -4

Hàm trong hàm

Việc định nghĩa các hàm bên trong của một hàm khác là hoàn toàn mới đối với các lập trình viên C hoặc C++. Nhưng điều này có thể thực hiện trong Python.

Ví dụ sau ta sẽ thấy hàm toVnd[] được định nghĩa và sử dụng bên trong hàm money[]

def money[x]:
    def toVnd[m]:
        return m * 22000

   print toVnd[x]

money[23]

Output:

506000

Hàm như đối số:

Mỗi tham số của một hàm là một tham chiếu đến một đối tượng và các hàm cũng chính là các đối tượng, chúng ta có thể truyền các hàm như các tham số cho một hàm khác. Chúng ta sẽ chứng minh điều này trong ví dụ đơn giản tiếp theo:

def convertMoney[amount, func]:
    print "using", func
    return func[amount]


def toVND[x]:
    return x * 22000

def toSGD[x]:
    return x * 22000 / 16500

print "converting US$40 to SGD$"
print convertMoney[40, toSGD]
print "converting US$40 to VND"
print convertMoney[40, toVND]

Output:

converting US$40 to SGD$

using 

53

converting US$40 to VND

using 

880000

Hàm trả về hàm

Đầu ra của một hàm cũng là một tham chiếu đến một đối tượng. Do đó các hàm có thể trả về tham chiếu đến các đối tượng hàm.

def f[x]:
    def g[y]:
        return x*y**2
   return g

func1 = f[1]
func2 = f[4]
print "func1[3] = ",func1[3]
print "func2[3] = ",func2[3]

Output:

func1[3] =  9

func2[3] =  36

Một ví dụ khác, tính đa thức với số bậc tự do tùy ý.

def polynomial_creator[*coefficients]:
    """ coefficients are in the form a_0, a_1, ... a_n
    """
   def polynomial[x]:
        res = 0
       for index, coeff in enumerate[coefficients]:
            res += coeff * x** index
        return res
    return polynomial

p1 = polynomial_creator[4]
p2 = polynomial_creator[2, 4]
p3 = polynomial_creator[2, 3, -1, 8, 1]
p4  = polynomial_creator[-1, 2, 1]


for x in range[-2, 2, 1]:
    print[x, p1[x], p2[x], p3[x], p4[x]]

Output:

[-2, 4, -6, -56, -1]

[-1, 4, -2, -9, -2]

[0, 4, 2, 2, -1]

[1, 4, 6, 13, 2]

Tôi giải thích thêm về ý nghĩa hàm enumerate[], qua ví dụ sau:

for index, value in enumerate[[1,2,3,4]]:
    print index, value


Output:


0 1

1 2

2 3

3 4

Tạo một decorator đơn giản:Nó sẽ hoạt động như sau, sau phép gán foo = our_decorator[foo], foo đã tham chiếu tới func_wrappe[] bên trong our_decorator[]. Vậy là bằng cách decorate đơn giản ta đã có khoác lên hàm foo[] một diện mạo mới, diện mạo của func_wrapper[].

def our_decorator[func]:
   def func_wrapper[x]:
      print["Before calling "+ func.__name__]
      func[x]
      print["After calling "+ func.__name__]
   return func_wrapper

def foo[x]:
   print["Hi, foo called with " + str[x]]

print "We call foo before decoration:"
foo["Hello"]
print "---------------------------------"
print "We now decorate foo with f: "
foo = our_decorator[foo]
print "We call foo after decoration:"
foo[120]

Output:

We call foo before decoration:

Hi, foo called with Hello

---------------------------------

We now decorate foo with f:

We call foo after decoration:

Before calling foo

Hi, foo called with 120

After calling foo

Cú pháp thường dùng cho decorator trong python

“decorator” trong Python thường không được thực hiện như cách chúng ta đã làm nó trong ví dụ trước mặc dù các ký hiệu foo = our_decorator [foo] là dễ nhớ và dễ nắm bắt. Đây là lý do tại sao chúng tôi không sử dụng nó! Bạn cũng có thể nhìn thấy một vấn đề thiết kế trong cách tiếp cận trước đây của chúng tôi. "foo" tồn tại trong cùng một chương trình với hai phiên bản, trước khi decorator và sau khi decorator.

Chúng tôi sẽ làm một decorator phù hợp ngay bây giờ. Các decorator xảy ra tại dòng trước định nghĩa hàm. Kí tự "@" được theo sau bởi tên hàm decorator.

Chúng ta sẽ viết lại ví dụ ban đầu của chúng ta. Thay vì viết foo = our_decorator[foo], ta viết @our_decorator.

def our_decorator[func]:
    def func_wrapper[x]:
        print["Befor calling "+ func.__name__]
        func[x]
        print["After calling "+ func.__name__]
    return func_wrapper
@our_decorator
def foo[x]:
    print["Hi, foo has been called with " + str[x]]

foo["Hi"]

Output:

Befor calling foo

Hi, foo has been called with Hi

After calling foo

Ta cũng hoàn toàn có thể decorate cả những hàm có sẵn [build-in] qua cách sau: ví dụ hai hàm sẵn có là sin và cos.

from math import sin, cos
def our_decorator[func]:
    def function_wrapper[x]:
        print["Before calling " + func.__name__]
        res = func[x]
        print[res]
        print["After calling " + func.__name__]
    return function_wrapper

sin = our_decorator[sin]
cos = our_decorator[cos]
for f in [sin, cos]:
    f[3.1415]

Output:

Before calling sin

9.26535896605e-05

After calling sin

Before calling cos

-0.999999995708

After calling cos

Tổng kết:

chúng ta có thể nói rằng một decorator trong Python là một đối tượng “callable Python object” được sử dụng để sửa đổi một hàm, phương thức hoặc lớp. Các đối tượng ban đầu, sẽ được sửa đổi, được chuyển đến một decorator như một đối số. Decorator sẽ trả về đối tượng được sửa đổi, ví dụ: hàm sửa đổi, nó ràng buộc với tên được sử dụng trong định nghĩa.

Một số trường hợp cho decorator:

Dùng decorator để kiểm tra đối số, trong ví dụ về tính dãy số Fibonacci, nếu ta truyền vào một số âm thì hàm fib[n] ta viết bị LẶP VÔ HẠN. Bằng cách nào ta có thể ngăn chặn việc này mà không cần thay đổi hàm đó trực tiếp?

Đó chính là lý do cho sự có mặt của decorator [phần code bên phải]. ta raise lỗi trước, để không xảy ra lỗi lặp vô hạn như phía trên.

def add[x, y]:
    return x + y
add_negtive = add
add_positive = add
print "add_negtive[-1,-3] = ", add_negtive[-1, -3]
print "add_positive[3,4] = ", add_positive[3, 4]
del add
print "add[] was deleted, add_negtive[-1,-3] = ", add_negtive[-1, -3]

Output:

add_negtive[-1,-3] =  -4

add_positive[3,4] =  7

add[] was deleted, add_negtive[-1,-3] = -4
0
def add[x, y]:
    return x + y
add_negtive = add
add_positive = add
print "add_negtive[-1,-3] = ", add_negtive[-1, -3]
print "add_positive[3,4] = ", add_positive[3, 4]
del add
print "add[] was deleted, add_negtive[-1,-3] = ", add_negtive[-1, -3]

Output:

add_negtive[-1,-3] =  -4

add_positive[3,4] =  7

add[] was deleted, add_negtive[-1,-3] = -4
1

Đếm số lần gọi hàm

Ví dụ sau sử dụng một decorator, ta cần đếm số lần một hàm đã được gọi. Chúng ta có thể sử dụng decorator này chỉ cho các hàm với chính xác một tham số:

def add[x, y]:
    return x + y
add_negtive = add
add_positive = add
print "add_negtive[-1,-3] = ", add_negtive[-1, -3]
print "add_positive[3,4] = ", add_positive[3, 4]
del add
print "add[] was deleted, add_negtive[-1,-3] = ", add_negtive[-1, -3]

Output:

add_negtive[-1,-3] =  -4

add_positive[3,4] =  7

add[] was deleted, add_negtive[-1,-3] = -4
2

Với hàm phía trên succ[i] ta chỉ có một đối số. Vậy ta muốn xây dựng một decorator có thể dùng cho nhiều đối số ta sẽ làm thế nào? Các bạn tự giải quyết bài toán này nhé! Gợi ý của tôi là dùng *args và **kwargs

Decorators với các tham số

Nếu chúng ta muốn thêm tham số cho decorator để có thể thực hiện một chức năng lớn hơn, chúng ta phải  dùng hàm khác wrap hàm decorator trước đó. Trong ví dụ sau mô tả cách mà tôi muốn thêm một lời chào vào hàm greeting_decorator[].

def add[x, y]:
    return x + y
add_negtive = add
add_positive = add
print "add_negtive[-1,-3] = ", add_negtive[-1, -3]
print "add_positive[3,4] = ", add_positive[3, 4]
del add
print "add[] was deleted, add_negtive[-1,-3] = ", add_negtive[-1, -3]

Output:

add_negtive[-1,-3] =  -4

add_positive[3,4] =  7

add[] was deleted, add_negtive[-1,-3] = -4
3

Kết luận

Tóm lại, Có 2 loại decorator trong Python là Function decorator và Class decorator. Nhưng trong khuôn khổ của bài học này, chúng tôi mới chỉ tập trung đề cập đến Function decorator. Để tiếp cận dễ dàng hơn với decorator trước hết các bạn cần nắm được một số khía cạnh quan trọng của hàm trong Python. Chúng đều khá xa lạ với các ngôn ngữ lập trình cơ bản như C/C++. Điểm đầu tiên là chúng ta có thể gán nhiều tên cho cùng một hàm và khi xóa một trong số những cái tên đó, hàm không mất đi chức năng của nó. Kế đến là có thể khai báo hàm bên trong của một hàm khác, truyền hàm như đối số cho một hàm khác. Cuối cùng là hàm trả về hàm. Cú pháp khai báo decorator cũng khá dễ nhớ với kí tự "@" được theo sau bởi tên hàm decorator và đặt trước định nghĩa hàm. Decorator thực sự là công cụ hữu ích trong việc sửa đổi, tùy biến một hàm hay một lớp mà không làm mất đi chức năng vốn có của nó. Trong bài tiếp theo chúng tôi sẽ tiếp tục đề cập đến decorator trong memorization.

Chủ Đề