Trình trang trí Python: Cách sử dụng và tại sao?

Người trang trí nhận một chức năng, thêm một số chức năng và trả về nó. Trong hướng dẫn này, bạn sẽ tìm hiểu cách bạn có thể tạo một trình trang trí và lý do bạn nên sử dụng nó.

Trình trang trí bằng Python

Python có một tính năng thú vị được gọi là trình trang trí để thêm chức năng vào mã hiện có.

Điều này còn được gọi là lập trình siêu chương trình vì một phần của chương trình cố gắng sửa đổi một phần khác của chương trình tại thời điểm biên dịch.

Điều kiện tiên quyết để học trang trí

Để hiểu về decorator, trước tiên chúng ta phải biết một vài điều cơ bản trong Python.

Chúng ta phải thoải mái với thực tế là mọi thứ trong Python (Có! Ngay cả các lớp), đều là các đối tượng. Các tên mà chúng tôi xác định chỉ đơn giản là các định danh gắn với các đối tượng này. Các hàm không có ngoại lệ, chúng cũng là các đối tượng (với các thuộc tính). Nhiều tên khác nhau có thể được liên kết với cùng một đối tượng chức năng.

Đây là một ví dụ.

 def first(msg): print(msg) first("Hello") second = first second("Hello")

Đầu ra

 xin chào

Khi bạn chạy mã, cả hai chức năng firstsecondcung cấp cùng một đầu ra. Ở đây, tên firstsecondtham chiếu đến cùng một đối tượng chức năng.

Bây giờ mọi thứ bắt đầu trở nên kỳ lạ hơn.

Các hàm có thể được truyền dưới dạng đối số cho một hàm khác.

Nếu quý vị có chức năng sử dụng như map, filterreducebằng Python, sau đó bạn đã biết về việc này.

Các hàm như vậy nhận các hàm khác làm đối số cũng được gọi là các hàm bậc cao . Đây là một ví dụ về một chức năng như vậy.

 def inc(x): return x + 1 def dec(x): return x - 1 def operate(func, x): result = func(x) return result

Chúng tôi gọi hàm như sau.

 >>> operate(inc,3) 4 >>> operate(dec,3) 2

Hơn nữa, một hàm có thể trả về một hàm khác.

 def is_called(): def is_returned(): print("Hello") return is_returned new = is_called() # Outputs "Hello" new()

Đầu ra

 xin chào

Đây, is_returned()là một hàm lồng nhau được định nghĩa và trả về mỗi khi chúng ta gọi is_called().

Cuối cùng, chúng ta phải biết về Closures trong Python.

Quay lại trang trí

Các hàm và phương thức được gọi là có thể gọi được vì chúng có thể được gọi.

Trên thực tế, bất kỳ đối tượng nào triển khai __call__()phương thức đặc biệt được gọi là có thể gọi được. Vì vậy, theo nghĩa cơ bản nhất, decorator là một hàm có thể gọi trả về một hàm có thể gọi.

Về cơ bản, người trang trí nhận một chức năng, thêm một số chức năng và trả về nó.

 def make_pretty(func): def inner(): print("I got decorated") func() return inner def ordinary(): print("I am ordinary")

Khi bạn chạy các mã sau trong shell,

 >>> ordinary() I am ordinary >>> # let's decorate this ordinary function >>> pretty = make_pretty(ordinary) >>> pretty() I got decorated I am ordinary

Trong ví dụ được hiển thị ở trên, make_pretty()là một người trang trí. Trong bước gán:

 pretty = make_pretty(ordinary)

Hàm ordinary()đã được trang trí và hàm trả về đã được đặt tên pretty.

Chúng ta có thể thấy rằng hàm decorator đã thêm một số chức năng mới vào hàm ban đầu. Điều này tương tự như đóng gói một món quà. Người trang trí hoạt động như một trình bao bọc. Bản chất của đối tượng được trang trí (món quà thực tế bên trong) không thay đổi. Nhưng bây giờ, nó trông khá đẹp (vì nó đã được trang trí).

Nói chung, chúng tôi trang trí một chức năng và gán lại nó là,

 ordinary = make_pretty(ordinary).

Đây là một cấu trúc phổ biến và vì lý do này, Python có một cú pháp để đơn giản hóa điều này.

Chúng ta có thể sử dụng @biểu tượng cùng với tên của chức năng trang trí và đặt nó phía trên định nghĩa của chức năng cần trang trí. Ví dụ,

 @make_pretty def ordinary(): print("I am ordinary")

tương đương với

 def ordinary(): print("I am ordinary") ordinary = make_pretty(ordinary)

Đây chỉ là một đường cú pháp để thực hiện các trình trang trí.

Trang trí các chức năng với các tham số

Trình trang trí ở trên rất đơn giản và nó chỉ hoạt động với các hàm không có bất kỳ tham số nào. Điều gì sẽ xảy ra nếu chúng ta có các hàm nhận các tham số như:

 def divide(a, b): return a/b

Hàm này có hai tham số, a và b. Chúng tôi biết nó sẽ báo lỗi nếu chúng tôi chuyển vào b là 0.

 >>> divide(2,5) 0.4 >>> divide(2,0) Traceback (most recent call last):… ZeroDivisionError: division by zero

Bây giờ chúng ta hãy làm một người trang trí để kiểm tra trường hợp này sẽ gây ra lỗi.

 def smart_divide(func): def inner(a, b): print("I am going to divide", a, "and", b) if b == 0: print("Whoops! cannot divide") return return func(a, b) return inner @smart_divide def divide(a, b): print(a/b)

Việc triển khai mới này sẽ trở lại Nonenếu điều kiện lỗi phát sinh.

 >>> divide(2,5) I am going to divide 2 and 5 0.4 >>> divide(2,0) I am going to divide 2 and 0 Whoops! cannot divide

Theo cách này, chúng ta có thể trang trí các hàm có tham số.

Một người quan sát tinh ý sẽ nhận thấy rằng các tham số của inner()hàm lồng nhau bên trong bộ trang trí giống với các thông số của hàm mà nó trang trí. Có tính đến điều này, bây giờ chúng ta có thể tạo các trình trang trí chung hoạt động với bất kỳ số lượng tham số nào.

In Python, this magic is done as function(*args, **kwargs). In this way, args will be the tuple of positional arguments and kwargs will be the dictionary of keyword arguments. An example of such a decorator will be:

 def works_for_all(func): def inner(*args, **kwargs): print("I can decorate any function") return func(*args, **kwargs) return inner

Chaining Decorators in Python

Multiple decorators can be chained in Python.

This is to say, a function can be decorated multiple times with different (or same) decorators. We simply place the decorators above the desired function.

 def star(func): def inner(*args, **kwargs): print("*" * 30) func(*args, **kwargs) print("*" * 30) return inner def percent(func): def inner(*args, **kwargs): print("%" * 30) func(*args, **kwargs) print("%" * 30) return inner @star @percent def printer(msg): print(msg) printer("Hello")

Output

 ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Hello %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ******************************

The above syntax of,

 @star @percent def printer(msg): print(msg)

is equivalent to

 def printer(msg): print(msg) printer = star(percent(printer))

The order in which we chain decorators matter. If we had reversed the order as,

 @percent @star def printer(msg): print(msg)

The output would be:

 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ****************************** Hello ****************************** %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

thú vị bài viết...