Vietnamese (Tiếng Việt) translation by Dai Phong (you can also view the original English article)
Decorator là một trong những tính năng tuyệt vời nhất của Python, tuy nhiên đối với người mới bắt đầu lập trình Python, chúng có vẻ như là phép thuật vậy. Mục đích của bài viết này là để tìm hiểu sâu cơ chế đằng sau các decoration trong Python.
Đây là những gì bạn sẽ được học:
- decorator trong Python là gì và tác dụng của chúng
- cách định nghĩa các decorator của riêng chúng ta
- các ví dụ decorator thực tế và cách chúng hoạt động
- cách để viết code tốt hơn bằng decorator
Giới thiệu
Trong trường hợp bạn chưa từng thấy (hoặc có lẽ bạn không biết bạn đang làm việc với nó), các decorator sẽ giống như thế này:
@decorator def function_to_decorate(): pass
Bạn thường gặp chúng ở trên định nghĩa của một hàm, và chúng có tiền tố @
. Các decorator đặc biệt hữu ích trong việc giữ code của bạn không bị lặp lại (hay còn gọi là DRY), và chúng làm điều đó nhưng đồng thời cũng cải thiện khả năng đọc của code.
Vẫn còn mập mờ? Không phải vậy chứ, vì decorator chỉ là các hàm Python. Đúng rồi! Bạn đã biết cách tạo ra một hàm. Trong thực tế, nguyên tắc cơ bản đằng sau decorator là hàm phức hợp. Hãy lấy một ví dụ:
def x_plus_2(x): return x + 2 print(x_plus_2(2)) # 2 + 2 == 4 def x_squared(x): return x * x print(x_squared(3)) # 3 ^ 2 == 9 # Let's compose the two functions for x=2 print(x_squared(x_plus_2(2))) # (2 + 2) ^ 2 == 16 print(x_squared(x_plus_2(3))) # (3 + 2) ^ 2 == 25 print(x_squared(x_plus_2(4))) # (4 + 2) ^ 2 == 36
Điều gì sẽ xảy ra nếu chúng ta muốn tạo ra một hàm khác, hàm x_plus_2_squared
? Cố gắng tổ hợp các hàm sẽ là vô nghĩa:
x_squared(x_plus_2) # TypeError: unsupported operand type(s) for *: 'function' and 'function'
Bạn không thể tổ hợp các hàm bằng cách này bởi vì cả hai hàm đều nhận các con số như là các đối số. Tuy nhiên, cái này sẽ hoạt động:
# Let's now create a proper function composition without actually applying the function x_plus_2_squared = lambda x: x_squared(x_plus_2(x)) print(x_plus_2_squared(2)) # (2 + 2) ^ 2 == 16 print(x_plus_2_squared(3)) # (3 + 2) ^ 2 == 25 print(x_plus_2_squared(4)) # (4 + 2) ^ 2 == 36
Hãy định nghĩa lại cách hàm x_squared
làm việc. Nếu chúng ta muốn x_squared
có thể tái tổ hợp theo mặc định, nó nên:
- Chấp nhận một hàm như một đối số
- Trả về một hàm khác
Chúng ta sẽ đặt tên phiên bản có thể tái tổ hợp của x_squared
đơn giản là squared
.
def squared(func): return lambda x: func(x) * func(x) print(squared(x_plus_2)(2)) # (2 + 2) ^ 2 == 16 print(squared(x_plus_2)(3)) # (3 + 2) ^ 2 == 25 print(squared(x_plus_2)(4)) # (4 + 2) ^ 2 == 36
Bây giờ chúng ta đã định nghĩa hàm squared
theo cách làm cho nó có thể tái tổ hợp, chúng ta có thể sử dụng nó với bất kỳ hàm nào khác. Dưới đây là một số ví dụ:
def x_plus_3(x): return x + 3 def x_times_2(x): return x * 2 print(squared(x_plus_3)(2)) # (2 + 3) ^ 2 == 25 print(squared(x_times_2)(2)) # (2 * 2) ^ 2 == 16
Chúng ta có thể nói rằng hàm squared
decorate các hàm x_plus_2
, x_plus_3
và x_times_2
. Chúng ta ở rất gần ký hiệu tiêu chuẩn của decorator. Hãy xem:
x_plus_2 = squared(x_plus_2) # We decorated x_plus_2 with squared print(x_plus_2(2)) # x_plus_2 now returns the decorated squared result: (2 + 2) ^ 2
Xong rồi! x_plus_2
là một hàm decorator phù hợp. Đây là nơi ký hiệu @
được đặt đúng vị trí:
def x_plus_2(x): return x + 2 x_plus_2 = squared(x_plus_2) # ^ This is completely equivalent with: @squared def x_plus_2(x): return x + 2
Trong thực tế, ký hiệu @
là một hình thức cú pháp dễ đọc. Hãy thử điều đó:
@squared def x_times_3(x): return 3 * x print(x_times_3(2)) # (3 * 2) ^ 2 = 36. # It might be a bit confusing, but by decorating it with squared, x_times_3 became in fact (3 * x) * (3 * x) @squared def x_minus_1(x): return x - 1 print(x_minus_1(3)) # (3 - 1) ^ 2 = 4
Nếu squared
là decorator đầu tiên mà bạn viết, hãy tự thưởng cho mình. Bạn đã nắm được một trong những khái niệm phức tạp nhất trong Python. Trong quá trình tìm hiểu, bạn đã học được một tính năng cơ bản của ngôn ngữ lập trình hàm: tổ hợp hàm.
Xây dựng Decorator của riêng bạn
Một decorator là một hàm nhận một hàm như là một đối số và trả về một hàm khác. Mặc dù vậy, hình thức chung cho việc định nghĩa decorator là:
def decorator(function_to_decorate): # ... return decorated_function
Trong trường hợp bạn không biết, bạn có thể định nghĩa các hàm bên trong các hàm khác. Trong hầu hết các trường hợp, decorated_function
sẽ được định nghĩa bên trong decorator
.
def decorator(function_to_decorate): def decorated_function(*args, **kwargs): # ... Since we decorate `function_to_decorate`, we should use it somewhere inside here return decorated_function
Hãy nhìn vào một ví dụ thực tế hơn:
import pytz from datetime import datetime def to_utc(function_to_decorate): def decorated_function(): # Get the result of function_to_decorate and transform the result to UTC return function_to_decorate().astimezone(pytz.utc) return decorated_function @to_utc def package_pickup_time(): """ This can come from a database or from an API """ tz = pytz.timezone('US/Pacific') return tz.localize(datetime(2017, 8, 2, 12, 30, 0, 0)) @to_utc def package_delivery_time(): """ This can come from a database or from an API """ tz = pytz.timezone('US/Eastern') return tz.localize(datetime(2017, 8, 2, 12, 30, 0, 0)) # What a coincidence, same time different timezone! print("PICKUP: ", package_pickup_time()) # '2017-08-02 19:30:00+00:00' print("DELIVERY: ", package_delivery_time()) # '2017-08-02 16:30:00+00:00'
Tuyệt! Bây giờ bạn có thể chắc chắn rằng mọi thứ bên trong ứng dụng của bạn đã được chuẩn hóa cho múi giờ UTC.
Một Ví dụ Thực tế
Một trường hợp sử dụng rất phổ biến và rất kinh điển khác đối với decorator là lưu cache kết quả của một hàm:
import time def cached(function_to_decorate): _cache = {} # Where we keep the results def decorated_function(*args): start_time = time.time() print('_cache:', _cache) if args not in _cache: _cache[args] = function_to_decorate(*args) # Perform the computation and store it in cache print('Compute time: %ss' % round(time.time() - start_time, 2)) return _cache[args] return decorated_function @cached def complex_computation(x, y): print('Processing ...') time.sleep(2) return x + y print(complex_computation(1, 2)) # 3, Performing the expensive operation print(complex_computation(1, 2)) # 3, SKIP performing the expensive operation print(complex_computation(4, 5)) # 9, Performing the expensive operation print(complex_computation(4, 5)) # 9, SKIP performing the expensive operation print(complex_computation(1, 2)) # 3, SKIP performing the expensive operation
Nếu bạn nhìn sơ qua code, bạn có thể thấy khó chịu. Decorator không thể sử dụng lại được! Nếu chúng ta decorate một hàm khác (gọi là another_complex_computation
) và gọi nó với các tham số tương tự thì chúng ta sẽ nhận được các kết quả được cache từ hàm complex_computation
. Điều này sẽ không xảy ra. Decorator có thể sử dụng lại và đây là lý do:
@cached def another_complex_computation(x, y): print('Processing ...') time.sleep(2) return x * y print(another_complex_computation(1, 2)) # 2, Performing the expensive operation print(another_complex_computation(1, 2)) # 2, SKIP performing the expensive operation print(another_complex_computation(1, 2)) # 2, SKIP performing the expensive operation
Hàm chached
được gọi một lần cho mỗi hàm mà nó decorate, do đó, một biến _cache
khác được khởi tạo mỗi lần và tồn tại trong ngữ cảnh đó. Hãy thử điều đó:
print(complex_computation(10, 20)) # -> 30 print(another_complex_computation(10, 20)) # -> 200
Decorator trong Thực tế
Decorator mà chúng ta vừa mớ viết, như bạn có thể nhận thấy, rất hữu ích. Nó hữu ích đến nỗi một phiên bản phức tạp và mạnh mẽ hơn đã tồn tại trong mô đun functools
tiêu chuẩn. Nó được đặt tên là lru_cache
. LRU là viết tắt của Least Recently Used, một chiến lược cahe.
from functools import lru_cache @lru_cache() def complex_computation(x, y): print('Processing ...') time.sleep(2) return x + y print(complex_computation(1, 2)) # Processing ... 3 print(complex_computation(1, 2)) # 3 print(complex_computation(2, 3)) # Processing ... 5 print(complex_computation(1, 2)) # 3 print(complex_computation(2, 3)) # 5
Một trong những trường hợp sử dụng decorator yêu thích của tôi là trong framework Flask. Nó thì gọn gàng, đó là đoạn code điều đầu tiên bạn nhìn thấy trên trang web Flask. Đây là đoạn code đó:
from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello World!" if __name__ == "__main__": app.run()
Decorator app.route
gán hàm hello
làm trình xử lý yêu cầu cho tuyến "/"
. Thật sự đơn giản.
Một cách sử dụng decorator gọn gàng khác là bên trong Django. Thông thường, các ứng dụng web có hai kiểu trang:
- các trang bạn có thể xem mà không cần chứng thực (trang chủ, trang landing, bài blog, đăng nhập, đăng ký)
- các trang bạn cần phải được chứng thực để xem (cài đặt tiểu sử, hộp thư đến, bảng điều khiển)
Nếu bạn thử xem một kiểu trang thứ hai, bạn thường sẽ được chuyển hướng đến một trang đăng nhập. Dưới đây là cách thực hiện điều đó trong Django:
from django.http import HttpResponse from django.contrib.auth.decorators import login_required # Public Pages def home(request): return HttpResponse("<b>Home</b>") def landing(request): return HttpResponse("<b>Landing</b>") # Authenticated Pages @login_required(login_url='/login') def dashboard(request): return HttpResponse("<b>Dashboard</b>") @login_required(login_url='/login') def profile_settings(request): return HttpResponse("<b>Profile Settings</b>")
Quan sát xem các chế độ view riêng tư gọn gàng như thế nào được đánh dấu bằng login_required
. Khi xem qua code, nó rất rõ ràng đối với người đọc mà các trang yêu cầu người dùng đăng nhập và những trang không yêu cầu.
Phần tóm tắt
Tôi hy vọng bạn đã có một buổi học thú vị về decorator bởi vì chúng đại diện cho một tính năng rất gọn gàng trong Python. Dưới đây là một số điều cần nhớ:
- Sử dụng và thiết kế decorator đúng cách có thể làm cho code của bạn trở nên tốt hơn, sạch sẽ hơn và đẹp hơn.
- Sử dụng decorator có thể giúp bạn DRY code—di chuyển các code giống nhau từ bên trong các hàm vào decorator.
- Khi bạn sử dụng các decorator nhiều hơn, bạn sẽ thấy những cách tốt hơn, phức tạp hơn để sử dụng chúng.
Hãy nhớ kiểm tra những gì mà chúng tôi đang bán và các tài liệu nghiên cứu trên Envato Market, và đừng ngần ngại đặt câu hỏi và cung cấp các phản hồi có giá trị của bạn trong phần bình luận bên dưới.
Như vậy, chúng ta đã tìm hiểu xong về decorator. Chúc bạn sử dụng tốt decorator!
Envato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post