Giải thích (Có ví dụ và trường hợp sử dụng)

Trình trang trí Python là một cấu trúc cực kỳ hữu ích trong Python. Sử dụng các bộ trang trí trong Python, chúng ta có thể sửa đổi hành vi của một hàm bằng cách gói nó bên trong một hàm khác. Trình trang trí cho phép chúng tôi viết mã sạch hơn và chia sẻ chức năng. Bài viết này không chỉ hướng dẫn cách sử dụng các công cụ trang trí mà còn cả cách tạo ra chúng.
Mục lục
Kiến thức tiên quyết
Chủ đề về trình trang trí trong Python yêu cầu một số kiến thức cơ bản. Dưới đây, tôi đã liệt kê một số khái niệm mà bạn đã quen thuộc để hiểu được hướng dẫn này. Tôi cũng đã liên kết các tài nguyên để bạn có thể tìm hiểu các khái niệm nếu cần.
Python cơ bản
Chủ đề này là một chủ đề trung cấp/nâng cao hơn. Do đó, trước khi cố gắng học, bạn nên làm quen với những kiến thức cơ bản về Python, chẳng hạn như kiểu dữ liệu, hàm, đối tượng và lớp.
Bạn cũng nên hiểu một số khái niệm hướng đối tượng như getters, setters và constructors. Nếu bạn chưa quen với ngôn ngữ lập trình Python, đây là một số tài nguyên để giúp bạn bắt đầu.
Chức năng là công dân hạng nhất
Ngoài Python cơ bản, bạn cũng nên biết về khái niệm nâng cao hơn này trong Python. Các hàm và gần như mọi thứ khác trong Python, là các đối tượng như int hoặc string. Vì chúng là các đối tượng nên bạn có thể thực hiện một số việc với chúng, cụ thể là:
- Bạn có thể chuyển một hàm làm đối số cho một hàm khác giống như cách bạn chuyển một chuỗi hoặc int làm đối số hàm.
- Các hàm cũng có thể được trả về bởi các hàm khác giống như bạn sẽ trả về các giá trị chuỗi hoặc int khác.
- Các chức năng có thể được lưu trữ trong các biến
Trên thực tế, sự khác biệt duy nhất giữa các đối tượng chức năng và các đối tượng khác là các đối tượng chức năng chứa phương thức ma thuật __call__().
Hy vọng rằng, tại thời điểm này, bạn cảm thấy thoải mái với kiến thức tiên quyết. Chúng ta có thể bắt đầu thảo luận về chủ đề chính.
Trình trang trí Python là gì?
Trình trang trí Python chỉ đơn giản là một hàm nhận một hàm làm đối số và trả về một phiên bản đã sửa đổi của hàm được truyền vào. Nói cách khác, hàm foo là một trình trang trí nếu nó nhận hàm bar làm đối số và trả về một chức năng khác baz.
Hàm baz là một biến thể của bar theo nghĩa là trong phần thân của baz, có một lời gọi hàm bar. Tuy nhiên, trước và sau cuộc gọi đến thanh, baz có thể làm bất cứ điều gì. Đó là một câu cửa miệng; đây là một số mã để minh họa tình hình:
# Foo is a decorator, it takes in another function, bar as an argument def foo(bar): # Here we create baz, a modified version of bar # baz will call bar but can do anything before and after the function call def baz(): # Before calling bar, we print something print("Something") # Then we run bar by making a function call bar() # Then we print something else after running bar print("Something else") # Lastly, foo returns baz, a modified version of bar return baz
Làm cách nào để tạo Trình trang trí bằng Python?
Để minh họa cách các trình trang trí được tạo và sử dụng trong Python, tôi sẽ minh họa điều này bằng một ví dụ đơn giản. Trong ví dụ này, chúng ta sẽ tạo một chức năng trang trí nhật ký sẽ ghi lại tên của chức năng mà nó đang trang trí mỗi khi chức năng đó chạy.
Để bắt đầu, chúng tôi đã tạo chức năng trang trí. Trình trang trí lấy func làm đối số. func là chức năng chúng ta đang trang trí.
def create_logger(func): # The function body goes here
Bên trong hàm trang trí, chúng ta sẽ tạo hàm đã sửa đổi để ghi lại tên của func trước khi chạy func.
# Inside create_logger def modified_func(): print("Calling: ", func.__name__) func()
Tiếp theo, hàm create_logger sẽ trả về hàm đã sửa đổi. Do đó, toàn bộ hàm create_logger của chúng ta sẽ trông như thế này:
def create_logger(func): def modified_func(): print("Calling: ", func.__name__) func() return modified_function
Chúng ta đã hoàn thành việc tạo trang trí. Hàm create_logger là một ví dụ đơn giản về hàm trang trí. Nó nhận func, là chức năng chúng ta đang trang trí, và trả về một chức năng khác, mod_func. đầu tiên mod_func ghi lại tên của func, trước khi chạy func.
Cách sử dụng trình trang trí trong Python
Để sử dụng trình trang trí của chúng tôi, chúng tôi sử dụng cú pháp @ như vậy:
@create_logger def say_hello(): print("Hello, World!")
Bây giờ chúng ta có thể gọi say_hello() trong tập lệnh của mình và đầu ra phải là văn bản sau:
Calling: say_hello "Hello, World"
Nhưng @create_logger đang làm gì? Chà, nó đang áp dụng trình trang trí cho hàm say_hello của chúng ta. Để hiểu rõ hơn những gì đang làm, mã ngay bên dưới đoạn này sẽ đạt được kết quả tương tự như đặt @create_logger trước say_hello.
def say_hello(): print("Hello, World!") say_hello = create_logger(say_hello)
Nói cách khác, một cách để sử dụng các bộ trang trí trong Python là gọi một cách rõ ràng bộ trang trí truyền vào hàm như chúng ta đã làm trong đoạn mã trên. Một cách khác ngắn gọn hơn là sử dụng cú pháp @.
Trong phần này, chúng tôi đã đề cập đến cách tạo trình trang trí Python.
Ví dụ phức tạp hơn một chút
Ví dụ trên là một trường hợp đơn giản. Có những ví dụ phức tạp hơn một chút chẳng hạn như khi chức năng chúng ta đang trang trí có các đối số. Một tình huống khác phức tạp hơn là khi bạn muốn trang trí toàn bộ lớp học. Tôi sẽ đề cập đến cả hai tình huống này ở đây.
Khi hàm nhận đối số
Khi hàm bạn đang trang trí nhận các đối số, hàm đã sửa đổi sẽ nhận các đối số và chuyển chúng khi cuối cùng nó thực hiện lệnh gọi đến hàm chưa sửa đổi. Nếu điều đó nghe có vẻ khó hiểu, hãy để tôi giải thích bằng thuật ngữ foo-bar.
Nhớ lại rằng foo là chức năng trang trí, thanh là chức năng chúng tôi đang trang trí và baz là thanh trang trí. Trong trường hợp đó, thanh sẽ nhận các đối số và chuyển chúng tới baz trong khi gọi tới baz. Đây là một ví dụ mã để củng cố khái niệm:
def foo(bar): def baz(*args, **kwargs): # You can do something here ___ # Then we make the call to bar, passing in args and kwargs bar(*args, **kwargs) # You can also do something here ___ return baz
Nếu *args và **kwargs có vẻ lạ; chúng chỉ đơn giản là các con trỏ tương ứng tới các đối số vị trí và từ khóa.
Điều quan trọng cần lưu ý là baz có quyền truy cập vào các đối số và do đó có thể thực hiện một số xác thực đối số trước khi gọi bar.
Một ví dụ sẽ là nếu chúng ta có một hàm trang trí, ensure_string sẽ đảm bảo rằng đối số được truyền cho một hàm mà nó đang trang trí là một chuỗi; chúng tôi sẽ thực hiện nó như vậy:
def ensure_string(func): def decorated_func(text): if type(text) is not str: raise TypeError('argument to ' + func.__name__ + ' must be a string.') else: func(text) return decorated_func
Chúng ta có thể trang trí hàm say_hello như sau:
@ensure_string def say_hello(name): print('Hello', name)
Sau đó, chúng tôi có thể kiểm tra mã bằng cách này:
say_hello('John') # Should run just fine say_hello(3) # Should throw an exception
Và nó sẽ tạo ra đầu ra sau:
Hello John Traceback (most recent call last): File "/home/anesu/Documents/python-tutorial/./decorators.py", line 20, in <module> say hello(3) # should throw an exception File "/home/anesu/Documents/python-tu$ ./decorators.pytorial/./decorators.py", line 7, in decorated_func raise TypeError('argument to + func._name_ + must be a string.') TypeError: argument to say hello must be a string. $0
Như mong đợi, tập lệnh quản lý để in ‘Xin chào John’ vì ‘John’ là một chuỗi. Nó đã tạo ra một ngoại lệ khi cố gắng in ‘Xin chào 3’ vì ‘3’ không phải là một chuỗi. Trình trang trí ensure_string có thể được sử dụng để xác thực các đối số của bất kỳ hàm nào yêu cầu chuỗi.
trang trí lớp học
Ngoài chức năng chỉ trang trí, chúng ta còn có thể trang trí lớp học. Khi bạn thêm một công cụ trang trí vào một lớp, phương thức được trang trí sẽ thay thế phương thức khởi tạo/khởi tạo của lớp đó(__init__).
Quay trở lại foo-bar, giả sử foo là decorator của chúng ta và Bar là lớp chúng ta đang trang trí, thì foo sẽ trang trí cho Bar.__init__. Điều này sẽ hữu ích nếu chúng ta muốn làm bất cứ điều gì trước khi các đối tượng kiểu Bar được khởi tạo.
Điều này có nghĩa là đoạn mã sau
def foo(func): def new_func(*args, **kwargs): print('Doing some stuff before instantiation') func(*args, **kwargs) return new_func @foo class Bar: def __init__(self): print("In initiator")
Tương đương với
def foo(func): def new_func(*args, **kwargs): print('Doing some stuff before instantiation') func(*args, **kwargs) return new_func class Bar: def __init__(self): print("In initiator") Bar.__init__ = foo(Bar.__init__)
Trên thực tế, việc khởi tạo một đối tượng của lớp Bar, được định nghĩa bằng một trong hai phương thức, sẽ cho bạn kết quả giống nhau:
Doing some stuff before instantiation In initiator
Trình trang trí ví dụ trong Python
Mặc dù bạn có thể định nghĩa các trình trang trí của riêng mình, nhưng có một số trình trang trí đã được tích hợp sẵn trong Python. Dưới đây là một số trình trang trí phổ biến mà bạn có thể gặp trong Python:
@staticmethod
Phương thức tĩnh được sử dụng trên một lớp để chỉ ra rằng phương thức mà nó đang trang trí là một phương thức tĩnh. Các phương thức tĩnh là các phương thức có thể chạy mà không cần khởi tạo lớp. Trong ví dụ mã sau, chúng ta tạo một lớp Dog với một phương thức tĩnh sủa.
class Dog: @staticmethod def bark(): print('Woof, woof!')
Bây giờ phương thức vỏ cây có thể được truy cập như vậy:
Dog.bark()
Và chạy mã sẽ tạo ra đầu ra sau:
Woof, woof!
Như tôi đã đề cập trong phần Cách sử dụng Trình trang trí, trình trang trí có thể được sử dụng theo hai cách. Cú pháp @ ngắn gọn hơn là một trong hai. Phương pháp khác là gọi hàm trang trí, chuyển vào hàm chúng ta muốn trang trí làm đối số. Có nghĩa là đoạn mã trên đạt được điều tương tự như đoạn mã dưới đây:
class Dog: def bark(): print('Woof, woof!') Dog.bark = staticmethod(Dog.bark)
Và chúng ta vẫn có thể sử dụng phương pháp sủa theo cách tương tự
Dog.bark()
Và nó sẽ tạo ra cùng một đầu ra
Woof, woof!
Như bạn có thể thấy, phương thức đầu tiên sạch hơn và rõ ràng hơn là hàm này là một hàm tĩnh trước khi bạn bắt đầu đọc mã. Do đó, đối với các ví dụ còn lại, tôi sẽ sử dụng phương pháp đầu tiên. Nhưng chỉ cần nhớ phương pháp thứ hai là một thay thế.
@classmethod
Trình trang trí này được sử dụng để chỉ ra rằng phương thức mà nó đang trang trí là một phương thức của lớp. Các phương thức lớp tương tự như các phương thức tĩnh ở chỗ cả hai đều không yêu cầu lớp phải được khởi tạo trước khi chúng có thể được gọi.
Tuy nhiên, sự khác biệt chính là các phương thức lớp có quyền truy cập vào các thuộc tính của lớp trong khi các phương thức tĩnh thì không. Điều này là do Python tự động chuyển lớp làm đối số đầu tiên cho một phương thức lớp bất cứ khi nào nó được gọi. Để tạo một phương thức lớp trong Python, chúng ta có thể sử dụng trình trang trí classmethod.
class Dog: @classmethod def what_are_you(cls): print("I am a " + cls.__name__ + "!")
Để chạy mã, chúng ta chỉ cần gọi phương thức mà không khởi tạo lớp:
Dog.what_are_you()
Và đầu ra là:
I am a Dog!
@tài sản
Trình trang trí thuộc tính được sử dụng để gắn nhãn một phương thức dưới dạng trình thiết lập thuộc tính. Quay trở lại ví dụ về Con chó của chúng ta, hãy tạo một phương thức truy xuất tên của Con chó.
class Dog: # Creating a constructor method that takes in the dog's name def __init__(self, name): # Creating a private property name # The double underscores make the attribute private self.__name = name @property def name(self): return self.__name
Bây giờ chúng ta có thể truy cập tên của con chó giống như một thuộc tính bình thường,
# Creating an instance of the class foo = Dog('foo') # Accessing the name property print("The dog's name is:", foo.name)
Và kết quả của việc chạy mã sẽ là
The dog's name is: foo
@property.setter
Trình trang trí property.setter được sử dụng để tạo một phương thức setter cho các thuộc tính của chúng ta. Để sử dụng trình trang trí @property.setter, bạn thay thế thuộc tính bằng tên của thuộc tính mà bạn đang tạo trình thiết lập cho. Ví dụ: nếu bạn đang tạo một trình thiết lập cho phương thức cho thuộc tính foo, thì trình trang trí của bạn sẽ là @foo.setter. Đây là một ví dụ về Chó để minh họa:
class Dog: # Creating a constructor method that takes in the dog's name def __init__(self, name): # Creating a private property name # The double underscores make the attribute private self.__name = name @property def name(self): return self.__name # Creating a setter for our name property @name.setter def name(self, new_name): self.__name = new_name
Để kiểm tra setter, chúng ta có thể sử dụng đoạn mã sau:
# Creating a new dog foo = Dog('foo') # Changing the dog's name foo.name="bar" # Printing the dog's name to the screen print("The dog's new name is:", foo.name)
Chạy mã sẽ tạo ra đầu ra sau:
The dogs's new name is: bar
Tầm quan trọng của decorator trong Python
Bây giờ chúng ta đã biết decorator là gì và bạn đã thấy một số ví dụ về decorators, chúng ta có thể thảo luận về lý do tại sao decorator lại quan trọng trong Python. Trang trí rất quan trọng vì nhiều lý do. Một số trong số họ, tôi đã liệt kê dưới đây:
- Chúng kích hoạt khả năng sử dụng lại mã: Trong ví dụ ghi nhật ký ở trên, chúng tôi có thể sử dụng @create_logger trên bất kỳ chức năng nào chúng tôi muốn. Điều này cho phép chúng tôi thêm chức năng ghi nhật ký vào tất cả các chức năng của mình mà không cần ghi thủ công cho từng chức năng.
- Chúng cho phép bạn viết mã mô-đun: Một lần nữa, quay lại ví dụ ghi nhật ký, với các trình trang trí, bạn có thể tách chức năng cốt lõi, trong trường hợp này là say_hello khỏi chức năng khác mà bạn cần, trong trường hợp này là ghi nhật ký.
- Chúng tăng cường các khung và thư viện: Trình trang trí được sử dụng rộng rãi trong các khung và thư viện Python để cung cấp chức năng bổ sung. Ví dụ: trong các khung web như Flask hoặc Django, trình trang trí được sử dụng để xác định tuyến đường, xử lý xác thực hoặc áp dụng phần mềm trung gian cho các chế độ xem cụ thể.
Từ cuối cùng
Trang trí là vô cùng hữu ích; bạn có thể sử dụng chúng để mở rộng chức năng mà không làm thay đổi chức năng của chúng. Điều này hữu ích khi bạn muốn tính thời gian thực hiện các chức năng, ghi nhật ký bất cứ khi nào một chức năng được gọi, xác thực các đối số trước khi gọi một chức năng hoặc xác minh các quyền trước khi một chức năng được chạy. Một khi bạn hiểu về decorators, bạn sẽ có thể viết mã một cách rõ ràng hơn.
Tiếp theo, bạn có thể muốn đọc các bài viết của chúng tôi về bộ dữ liệu và cách sử dụng cURL trong Python.