Chuyển đến nội dung chính

Cách sử dụng Python Decorators (Với ví dụ dựa trên Hàm và Lớp)

Học decorators trong Python qua ví dụ thực hành. Hiểu closures, decorators dựa trên hàm và lớp, và cách viết mã tái sử dụng, tinh gọn.
Đã cập nhật 5 thg 6, 2026  · 11 phút đọc

Decorators là một tính năng mạnh mẽ và tinh gọn trong Python, cho phép bạn sửa đổi hoặc mở rộng hành vi của hàm và phương thức mà không cần thay đổi mã nguồn của chúng.

Decorator là một mẫu thiết kế trong Python cho phép người dùng thêm chức năng mới vào một đối tượng hiện có mà không cần sửa đổi cấu trúc của nó. Decorator thường được áp dụng cho các hàm và đóng vai trò quan trọng trong việc tăng cường hoặc thay đổi hành vi của hàm. Theo cách truyền thống, decorators được đặt trước định nghĩa của hàm bạn muốn trang trí (decorate). Trong hướng dẫn này, chúng tôi sẽ minh họa cách sử dụng decorators hiệu quả trong các hàm Python.

Tóm tắt nhanh

  • Decorators cho phép bạn sửa đổi hoặc mở rộng hành vi của hàm mà không thay đổi mã gốc
  • Sử dụng cú pháp @decorator_name để áp dụng decorators một cách gọn gàng
  • Luôn dùng functools.wraps để giữ nguyên metadata của hàm khi viết decorators
  • Decorators dựa trên lớp cho phép hành vi có trạng thái qua nhiều lần gọi hàm
  • Các trường hợp sử dụng phổ biến gồm caching (@lru_cache), logging, xác thực, và kiểm tra đầu vào

Hàm là đối tượng hạng nhất

Các hàm trong Python là đối tượng hạng nhất. Điều này có nghĩa chúng hỗ trợ các thao tác như được truyền làm đối số, được trả về từ một hàm, được sửa đổi và gán cho một biến. Thuộc tính này rất quan trọng vì cho phép hàm được đối xử như bất kỳ đối tượng nào khác trong Python, giúp lập trình linh hoạt hơn.

Để tự mình chạy dễ dàng toàn bộ mã ví dụ trong hướng dẫn này, bạn có thể tạo miễn phí một workbook DataLab đã cài sẵn Python và chứa tất cả mẫu mã. Để luyện tập thêm về decorators, hãy xem bài tập thực hành này trên DataCamp.

Gán hàm cho biến

Để bắt đầu, chúng ta tạo một hàm sẽ cộng thêm một vào một số mỗi khi được gọi. Sau đó sẽ gán hàm cho một biến và dùng biến này để gọi hàm.

def plus_one(number):
    return number + 1

add_one = plus_one
add_one(5)
6

Định nghĩa hàm bên trong hàm khác

Định nghĩa hàm bên trong hàm khác là một tính năng mạnh mẽ trong Python — và là điều cốt lõi để xây dựng decorators. Hãy xem một ý tưởng trọng tâm khác: truyền hàm làm đối số. Điều này sẽ đưa chúng ta tiến gần hơn một bước đến việc viết decorators.

def plus_one(number):
    def add_one(number):
        return number + 1


    result = add_one(number)
    return result
plus_one(4)
5

Truyền hàm làm đối số cho hàm khác

Hàm cũng có thể được truyền làm tham số cho các hàm khác. Hãy minh họa điều đó bên dưới.

def plus_one(number):
    return number + 1

def function_call(function):
    number_to_add = 5
    return function(number_to_add)

function_call(plus_one)
6

Hàm trả về hàm khác

Một hàm cũng có thể sinh ra hàm khác. Chúng ta sẽ minh họa bên dưới bằng một ví dụ.

def hello_function():
    def say_hi():
        return "Hi"
    return say_hi
hello = hello_function()
hello()
'Hi'

Hàm trong (inner function) và Closures

Python cho phép một hàm lồng nhau truy cập phạm vi bên ngoài của hàm bao quanh nó. Đây là một khái niệm then chốt trong decorators, được gọi là closure.

Closure trong Python là một hàm ghi nhớ môi trường nơi nó được tạo ra, ngay cả khi môi trường đó không còn hoạt động. Điều này có nghĩa là một hàm lồng nhau có thể "đóng kín" các biến từ phạm vi bao quanh và tiếp tục sử dụng chúng.

Closures rất quan trọng để hiểu decorators vì decorators dựa vào khả năng của một hàm bọc (wrapper) lồng bên trong để truy cập và sửa đổi trạng thái của hàm decorator bao quanh.

Ví dụ về closure:

def outer_function(message):
    def inner_function():
        print(f"Message from closure: {message}")
    return inner_function

closure_function = outer_function("Hello, closures!")
closure_function()
# Output: Message from closure: Hello, closures!

Trong ví dụ này:

  • inner_function là một closure vì nó truy cập message, một biến từ phạm vi bao quanh (outer_function).
  • Mặc dù outer_function đã chạy xong, inner_function vẫn giữ quyền truy cập vào message.

Khi bạn tạo một decorator, hàm wrapper (bên trong decorator) là một closure. Nó giữ quyền truy cập vào hàm được trang trí và bất kỳ trạng thái hoặc đối số bổ sung nào được định nghĩa trong hàm decorator. Ví dụ:

def simple_decorator(func):
    def wrapper():
        print("Before the function call")
        func()
        print("After the function call")
    return wrapper

@simple_decorator
def greet():
    print("Hello!")

greet()
# Output:
# Before the function call
# Hello!
# After the function call

Ở đây, wrapper là một closure ghi nhớ hàm greet và thêm hành vi trước và sau khi nó thực thi.

Tạo Decorator đầu tiên của bạn

Bây giờ bạn đã hiểu closures — khả năng của một hàm ghi nhớ các biến từ phạm vi bao quanh — chúng ta sẵn sàng tạo decorator thực sự đầu tiên. Closures là “gia vị bí mật” giúp decorators hoạt động phía sau hậu trường.

Hãy tạo một decorator đơn giản chuyển một câu thành chữ in hoa. Ta làm điều này bằng cách định nghĩa một wrapper bên trong một hàm bao. Như bạn thấy, nó rất giống với hàm bên trong hàm khác mà ta đã tạo trước đó.

def uppercase_decorator(function):
    def wrapper():
        func = function()
        make_uppercase = func.upper()
        return make_uppercase

    return wrapper

Vì decorator của chúng ta nhận một hàm làm đối số, ta sẽ định nghĩa một hàm mới và truyền nó cho decorator. Như đã học trước đó, ta có thể gán một hàm cho một biến. Ta sẽ dùng mẹo đó để gọi hàm decorator.

def say_hi():
    return 'hello there'

decorate = uppercase_decorator(say_hi)
decorate()
'HELLO THERE'

Sử dụng cú pháp @

Tuy nhiên, Python cung cấp cách dễ hơn để áp dụng decorators. Ta chỉ cần dùng ký hiệu @ trước hàm muốn trang trí. Minh họa thực tế bên dưới.

@uppercase_decorator
def say_hi():
    return 'hello there'

say_hi()
'HELLO THERE'

Xếp chồng nhiều Decorator

Khi bạn đã quen dùng cú pháp @ cho một decorator, bạn có thể tiến xa hơn và xếp chồng nhiều decorator trên cùng một hàm. Hãy lưu ý: thứ tự rất quan trọng!

Bên dưới, ta sẽ định nghĩa một decorator khác tách câu thành danh sách. Sau đó áp dụng cả uppercase_decoratorsplit_string cho một hàm duy nhất.

import functools
def split_string(function):
    @functools.wraps(function)
    def wrapper():
        func = function()
        splitted_string = func.split()
        return splitted_string

    return wrapper 
@split_string
@uppercase_decorator
def say_hi():
    return 'hello there'
say_hi()
['HELLO', 'THERE']

Từ kết quả trên, ta thấy các decorator được áp dụng từ dưới lên. Nếu đảo thứ tự, ta sẽ gặp lỗi vì list không có thuộc tính upper. Câu được chuyển thành chữ in hoa trước rồi mới được tách thành danh sách.

Lưu ý: Khi xếp chồng decorators, thực hành phổ biến là sử dụng functools.wraps để đảm bảo metadata của hàm gốc được giữ nguyên xuyên suốt quá trình xếp chồng. Điều này giúp rõ ràng và nhất quán khi gỡ lỗi và hiểu các thuộc tính của hàm đã được trang trí.

Chấp nhận đối số trong Decorators

Đến giờ, ta đã thấy các decorator chỉ bọc một hàm. Nhưng nếu bạn muốn cấu hình chính decorator — như truyền tham số cho nó thì sao? Đó là lúc cần đến decorator factory.

def decorator_with_arguments(function):
    def wrapper_accepting_arguments(arg1, arg2):
        print("My arguments are: {0}, {1}".format(arg1,arg2))
        function(arg1, arg2)
    return wrapper_accepting_arguments


@decorator_with_arguments
def cities(city_one, city_two):
    print("Cities I love are {0} and {1}".format(city_one, city_two))

cities("Nairobi", "Accra")

My arguments are: Nairobi, Accra Cities I love are Nairobi and Accra

Lưu ý: Cần đảm bảo số lượng đối số trong decorator (
arg1, arg2 trong ví dụ này) khớp với số lượng đối số trong hàm được bọc (cities trong ví dụ này). Sự tương ứng này rất quan trọng để tránh lỗi và đảm bảo hoạt động đúng khi dùng decorators với đối số.

Decorators đa dụng với *args và **kwargs

Để định nghĩa một decorator đa dụng có thể áp dụng cho bất kỳ hàm nào, ta dùng *args**kwargs. *args**kwargs thu thập tất cả đối số vị trí và đối số từ khóa và lưu vào biến *args**kwargs. Chúng cho phép ta truyền bao nhiêu đối số tùy thích khi gọi hàm.

def a_decorator_passing_arbitrary_arguments(function_to_decorate):
    def a_wrapper_accepting_arbitrary_arguments(*args,**kwargs):
        print('The positional arguments are', args)
        print('The keyword arguments are', kwargs)
        function_to_decorate(*args)
    return a_wrapper_accepting_arbitrary_arguments

@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
    print("No arguments here.")

function_with_no_argument()
The positional arguments are ()
The keyword arguments are {}
No arguments here.

Hãy xem cách dùng decorator với các đối số vị trí.

@a_decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
    print(a, b, c)

function_with_arguments(1,2,3)
The positional arguments are (1, 2, 3)
The keyword arguments are {}
1 2 3

Đây là cách bạn có thể truyền đối số từ khóa cho một hàm đã được trang trí:

@a_decorator_passing_arbitrary_arguments
def function_with_keyword_arguments():
    print("This has shown keyword arguments")

function_with_keyword_arguments(first_name="Derrick", last_name="Mwiti")
The positional arguments are ()
The keyword arguments are {'first_name': 'Derrick', 'last_name': 'Mwiti'}
This has shown keyword arguments

Lưu ý: Việc sử dụng **kwargs trong decorator cho phép nó xử lý các đối số từ khóa. Điều này giúp decorator đa dụng có thể xử lý nhiều kiểu đối số trong các lời gọi hàm.

Truyền đối số cho Decorators

Bây giờ hãy xem cách chúng ta truyền đối số cho chính decorator. Để làm được điều này, ta định nghĩa một “trình tạo decorator” (decorator maker) chấp nhận các đối số rồi định nghĩa một decorator bên trong nó. Sau đó định nghĩa một hàm wrapper bên trong decorator như trước.

def decorator_maker_with_arguments(decorator_arg1, decorator_arg2, decorator_arg3):
    def decorator(func):
        def wrapper(function_arg1, function_arg2, function_arg3) :
            "This is the wrapper function"
            print("The wrapper can access all the variables\n"
                  "\t- from the decorator maker: {0} {1} {2}\n"
                  "\t- from the function call: {3} {4} {5}\n"
                  "and pass them to the decorated function"
                  .format(decorator_arg1, decorator_arg2,decorator_arg3,
                          function_arg1, function_arg2,function_arg3))
            return func(function_arg1, function_arg2,function_arg3)

        return wrapper

    return decorator

pandas = "Pandas"
@decorator_maker_with_arguments(pandas, "Numpy","Scikit-learn")
def decorated_function_with_arguments(function_arg1, function_arg2,function_arg3):
    print("This is the decorated function and it only knows about its arguments: {0}"
           " {1}" " {2}".format(function_arg1, function_arg2,function_arg3))

decorated_function_with_arguments(pandas, "Science", "Tools")
The wrapper can access all the variables
    - from the decorator maker: Pandas Numpy Scikit-learn
    - from the function call: Pandas Science Tools
and pass them to the decorated function
This is the decorated function, and it only knows about its arguments: Pandas Science Tools

Gỡ lỗi Decorators

Như ta đã thấy, decorators bọc các hàm. Tên hàm gốc, docstring và danh sách tham số đều bị ẩn bởi closure của wrapper: Ví dụ, khi cố truy cập metadata của decorated_function_with_arguments, ta sẽ thấy metadata của wrapper. Điều này gây khó khăn khi gỡ lỗi.

decorated_function_with_arguments.__name__
'wrapper'
decorated_function_with_arguments.__doc__
'This is the wrapper function'

Để giải quyết, Python cung cấp một decorator functools.wraps. Decorator này sao chép metadata bị mất từ hàm chưa được trang trí sang closure được trang trí. Hãy xem cách làm.

import functools

def uppercase_decorator(func):
    @functools.wraps(func)
    def wrapper():
        return func().upper()
    return wrapper
@uppercase_decorator
def say_hi():
    "This will say hi"
    return 'hello there'

say_hi()
'HELLO THERE'

Bây giờ, khi kiểm tra metadata của say_hi, ta thấy nó phản ánh đúng hàm gốc — không phải wrapper.

say_hi.__name__
'say_hi'
say_hi.__doc__
'This will say hi'

Nên và tốt nhất là luôn dùng functools.wraps khi định nghĩa decorators. Điều này sẽ giúp bạn bớt rất nhiều đau đầu khi gỡ lỗi.

Decorators dựa trên Lớp (Class-Based)

Mặc dù decorators dựa trên hàm rất phổ biến, Python cũng cho phép bạn tạo decorators dựa trên lớp, mang lại tính linh hoạt và khả năng bảo trì cao hơn, đặc biệt cho các trường hợp phức tạp. Nếu bạn mới làm quen với lập trình hướng đối tượng trong Python, ôn lại các kiến thức cơ bản sẽ giúp bạn hiểu phần này tốt hơn. Một decorator dựa trên lớp là một lớp có phương thức __call__ cho phép nó hành xử như một hàm.

class UppercaseDecorator:
    def __init__(self, function):
        self.function = function

    def __call__(self, *args, **kwargs):
        result = self.function(*args, **kwargs)
        return result.upper()

@UppercaseDecorator
def greet():
    return "hello there"

print(greet())
# Output: HELLO THERE

Cách hoạt động:

  1. Phương thức __init__ khởi tạo decorator với hàm sẽ được trang trí.
  2. Phương thức __call__ được gọi khi hàm đã trang trí được gọi, cho phép decorator sửa đổi hành vi của nó.

Ưu điểm của decorators dựa trên lớp:

  • Decorators có trạng thái: Decorators dựa trên lớp có thể duy trì trạng thái bằng biến thực thể, không như decorators dựa trên hàm vốn cần closures hoặc biến toàn cục.
  • Dễ đọc: Với các decorators phức tạp, đóng gói logic trong một lớp có thể giúp mã tổ chức tốt hơn và dễ hiểu hơn.

Ví dụ về decorator có trạng thái:

class CallCounter:
    def __init__(self, function):
        self.function = function
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"Function {self.function.__name__} has been called {self.count} times.")
        return self.function(*args, **kwargs)

@CallCounter
def say_hello():
    print("Hello!")

say_hello()
say_hello()
# Output:
# Function say_hello has been called 1 times.
# Hello!
# Function say_hello has been called 2 times.
# Hello!

Tình huống sử dụng Decorator trong thực tế: Caching

Decorator lru_cache là một công cụ tích hợp sẵn trong Python, dùng để lưu đệm kết quả của các lời gọi hàm tốn kém. Điều này cải thiện hiệu năng bằng cách tránh tính toán lặp lại cho các đầu vào trùng nhau.

Ví dụ:

from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(50))  # Subsequent calls with the same argument are much faster

Các cách dùng phổ biến khác cho decorators:

  • Logging: Theo dõi lời gọi hàm, đối số và giá trị trả về để gỡ lỗi hoặc kiểm toán.

  • Xác thực: Thực thi kiểm soát truy cập trong các ứng dụng web như Flask hoặc Django.

  • Đo thời gian thực thi: Đo và tối ưu thời gian chạy của hàm cho các tác vụ quan trọng về hiệu năng.

  • Cơ chế thử lại: Tự động thử lại các lời gọi hàm thất bại, hữu ích trong các tác vụ mạng.

  • Kiểm tra đầu vào: Xác thực đối số hàm trước khi thực thi.

Tóm tắt về Python Decorators

Decorators thay đổi động chức năng của một hàm, phương thức hoặc lớp mà không cần trực tiếp dùng kế thừa con hoặc thay đổi mã nguồn của hàm được trang trí. Sử dụng decorators trong Python cũng giúp mã của bạn tuân thủ DRY (Don’t Repeat Yourself — Đừng lặp lại chính mình). Decorators có nhiều trường hợp sử dụng như:

  • Phân quyền trong các framework Python như Flask và Django
  • Ghi log
  • Đo thời gian thực thi
  • Đồng bộ hóa

Để tìm hiểu thêm về Python decorators, hãy xem Thư viện Decorator của Python.

Câu hỏi thường gặp

Có lưu ý nào về hiệu năng khi sử dụng decorators không?

Có, decorators có thể tạo ra chi phí bổ sung vì chúng giới thiệu thêm các lần gọi hàm. Khi hiệu năng là yếu tố then chốt, điều quan trọng là cân nhắc chi phí này, đặc biệt nếu hàm được trang trí được gọi thường xuyên trong bối cảnh nhạy cảm về hiệu năng.

Decorators có thể dùng với phương thức lớp không, và dùng như thế nào?

Có, decorators có thể được áp dụng cho phương thức lớp tương tự như các hàm thông thường. Decorator sẽ nhận phương thức làm đối số và trả về một phương thức mới hoặc phiên bản đã được sửa đổi. Điều này thường được dùng cho logging, kiểm soát truy cập hoặc áp đặt các điều kiện tiên quyết.

Decorators có thể dùng cho mục đích logging như thế nào?

Decorators có thể được dùng để ghi log các lời gọi hàm, đối số và giá trị trả về bằng cách bọc việc thực thi hàm bằng mã ghi lại các chi tiết này vào hệ thống logging. Điều này giúp việc truy vết và gỡ lỗi.

Ý nghĩa của ký hiệu @ trong decorators là gì?

Ký hiệu @ là cú pháp rút gọn trong Python giúp đơn giản hóa việc áp dụng decorator cho một hàm. Nó cho phép bạn áp dụng decorator ngay phía trên định nghĩa hàm, giúp mã gọn gàng và dễ đọc hơn.

Decorator có thể sửa đổi giá trị trả về của hàm không, và hoạt động như thế nào?

Có, một decorator có thể sửa đổi giá trị trả về của một hàm bằng cách thay đổi câu lệnh trả về trong hàm wrapper. Ví dụ, nó có thể biến đổi kiểu dữ liệu đầu ra, định dạng lại, hoặc thêm xử lý trước khi trả về kết quả cuối cùng.

Python xử lý phạm vi biến như thế nào khi một hàm lồng nhau truy cập biến từ hàm bao quanh?

Python sử dụng quy tắc phạm vi LEGB (Local, Enclosing, Global, Built-in). Với các hàm lồng nhau, hàm lồng có thể truy cập các biến từ phạm vi của hàm bao quanh, nhờ đó tạo ra closures nơi hàm trong giữ quyền truy cập vào biến của hàm ngoài ngay cả sau khi hàm ngoài đã kết thúc.

Sự khác nhau giữa closure và decorator là gì?

Một closure là hàm ghi nhớ các biến từ phạm vi bên ngoài của nó. Một decorator là một hàm dùng closures để tăng cường hoặc bọc các hàm khác.

Khi nào tôi nên dùng functools.wraps?

Luôn dùng functools.wraps khi viết decorators. Nó giữ lại metadata của hàm gốc (như tên và docstring), hữu ích cho gỡ lỗi và tài liệu hóa.

Decorators có thể nhận đối số không?

Có! Bạn có thể truyền đối số cho decorators bằng cách bọc chúng trong một hàm khác (decorator factory). Cách này cho phép bạn tùy biến hành vi của decorator.

Ưu điểm của decorators dựa trên lớp là gì?

Decorators dựa trên lớp cho phép bạn duy trì trạng thái qua nhiều lần gọi hàm và tổ chức logic phức tạp hơn theo hướng đối tượng.

Decorators chỉ dành cho hàm thôi sao?

Không. Bạn cũng có thể dùng decorators cho lớp và phương thức. Trong các framework như Flask và Django, decorators thường được dùng cho routes, views và models.

Chủ đề

Tìm hiểu thêm về Python

Courses

Giới thiệu về Functions trong Python

3 giờ
466.3K
Học cách viết các hàm của riêng bạn trong Python, cũng như các khái niệm quan trọng như phạm vi và xử lý lỗi.
Xem chi tiếtRight Arrow
Bắt đầu khóa học
Xem thêmRight Arrow