Chúng ta có thể dễ dàng tạo các bộ trang trí bên trong một lớp và nó có thể dễ dàng truy cập đối với các lớp con của nó. Trong quá trình tạo Trình trang trí, chúng ta phải lưu ý rằng hàm mà chúng ta đang xác định bên trong trình trang trí phải lấy tham chiếu đối tượng hiện tại [bản thân] làm tham số và trong khi chúng ta đang truy cập trình trang trí đó từ lớp con, chúng ta phải gọi trình trang trí đó bằng cách sử dụng lớp
ví dụ 1. Ở đây trong ví dụ này, chúng tôi đang tạo một chức năng trang trí bên trong Lớp A. Bên trong Class A “fun1” Instance Method đang gọi hàm decorator “Decorators” bên trong Class B “fun2”. Phương thức sơ thẩm đang gọi chức năng trang trí của Lớp A. Để sử dụng trình trang trí của Lớp A, chúng tôi phải yêu cầu sử dụng tên Lớp trong đó trình trang trí có mặt, đó là lý do tại sao chúng tôi sử dụng “@A. Trang trí” tại đây
Giả sử chúng ta cần một decorator mà chúng ta sẽ chỉ sử dụng bên trong một lớp cụ thể. Một cái gì đó như thế này
Nơi để xác định trang trí này?
Chức năng bên trong lớpSự thôi thúc ngay lập tức là đưa atomic_rating_change
vào chính lớp học
Điều đó thực sự sẽ hiệu quả, nhưng có một lỗ hổng lớn trong cách tiếp cận này. atomic_rating_change
trở thành một phương thức thể hiện của lớpUser
. Điều đó không có ý nghĩa gì. Hơn thế nữa, nó thậm chí không hoạt động như một phương pháp. nếu bạn gọi nó, tham số decorated
sẽ được sử dụng như self
Lưu ý rằng trong thân lớp atomic_rating_change
vẫn là một hàm đơn giản. Nó chỉ trở thành một phương thức sau khi lớp thực sự được tạo và tại thời điểm này, trình trang trí đã được áp dụng thành công
Đây là một ví dụ nhỏ có thể giúp bạn hiểu khái niệm này
Bạn thực sự có thể thấy rằng func
là một hàm bên trong thân lớp, nhưng nó là một phương thức bên ngoài lớp. Ngoài ra, các giá trị A.func
và A[].func
cũng khác nhau, nhờ phép thuật bộ mô tả Python [nếu bạn không quen với các bộ mô tả trong Python, bạn nên kiểm tra liên kết]. Trong Python 3, A.func
chỉ là một hàm đơn giản
Chúng ta có thể làm gì để điều này hoạt động bình thường?
Làm cho nó tĩnh [không]Điều đơn giản bạn làm mỗi khi muốn lưu trữ một hàm đơn giản trong một lớp là biến nó thành một phương thức tĩnh
Bạn có thể nghĩ rằng nó giải quyết hoàn toàn mọi vấn đề, nhưng thật đáng buồn là không phải vậy. atomic_rating_change
0 là một bộ mô tả khác và các bộ mô tả hoạt động khác nhau bên trong và bên ngoài một lớp [vâng, một lần nữa]
Vì vậy, khi bạn áp dụng trình trang trí này cho một phương thức nào đó, bạn sẽ nhận được atomic_rating_change
1, bởi vì bạn thấy một trình mô tả thô bên trong lớp
Giải pháp đơn giản nhất mà bạn có thể đã nghĩ đến là khai báo hàm bên ngoài lớp
Nhược điểm duy nhất là hàm không còn bị ràng buộc với lớp nữa
Khai báo trong một lớp khácĐể khắc phục điều này, chúng ta có thể khai báo một lớp trợ giúp chứa tất cả các bộ trang trí cho User
atomic_rating_change
3 cũng không bị ràng buộc với lớp ban đầu [lưu tên], nhưng vì nó không còn là một hàm nữa, nên chúng ta có thể đặt nó trong User
Bạn cũng có thể đặt atomic_rating_change
5 ở chế độ riêng tư [bằng cách đổi tên nó thành atomic_rating_change
6] nếu bạn không thích các lớp khác sử dụng công cụ trang trí của mình
Giải pháp này khá chắc chắn và được khuyên dùng, nhưng có một phương pháp hấp dẫn mà tôi biết
Tạo lớp, không phải phương thứcNhờ phương thức ma thuật atomic_rating_change
7 của Python, bạn có thể khai báo bất kỳ trình trang trí nào dưới dạng một lớp, không phải dưới dạng một hàm. Hãy nhớ lại, vấn đề ban đầu của chúng ta là các hàm hoạt động khác nhau bên trong và bên ngoài thân lớp, nhưng điều đó hoàn toàn không áp dụng cho các lớp bên trong thân lớp [xem atomic_rating_change
5 trong các ví dụ trước. nó hoạt động tốt]. Vì vậy, việc chuyển đổi trình trang trí từ ví dụ đầu tiên sang một lớp sẽ hoạt động hoàn hảo
Sự thật thú vị. cả hai giải pháp hoạt động đều liên quan đến việc thay đổi hàm bên trong thành lớp bên trong [do hành vi cụ thể của các thuộc tính hàm, duh]
Lưu ý rằng vấn đề được mô tả không có bản chất khái niệm, nó chỉ là một cách để chống lại thực tế là một hàm không thể là một thuộc tính lớp đơn giản trong Python
Trình trang trí là các trình bao bọc xung quanh các hàm [hoặc lớp] Python thay đổi cách các lớp này hoạt động. Một trang trí trừu tượng hóa chức năng của chính nó càng xa càng tốt. Ký hiệu Decorator được thiết kế để ít xâm lấn nhất có thể. Nhà phát triển có thể phát triển mã của mình trong miền của mình khi anh ta đã quen và chỉ sử dụng công cụ trang trí để mở rộng chức năng. Bởi vì điều này nghe có vẻ rất trừu tượng, hãy xem xét một số ví dụ
Trong Python, các bộ trang trí được sử dụng chủ yếu để trang trí các hàm [hoặc phương thức tương ứng]. Có lẽ, một trong những decorator được sử dụng phổ biến nhất là
def retry[max_retries]:
def retry_decorator[func]:
def _wrapper[*args, **kwargs]:
for _ in range[max_retries]:
try:
func[*args, **kwargs]
except:
time.sleep[1]
return _wrapper
return retry_decorator
@retry[2]
def might_fail[]:
print["might_fail"]
raise Exception
might_fail[]
3 decoratorNhư bạn thấy ở dòng cuối cùng, bạn có thể truy cập vào
def retry[max_retries]:
def retry_decorator[func]:
def _wrapper[*args, **kwargs]:
for _ in range[max_retries]:
try:
func[*args, **kwargs]
except:
time.sleep[1]
return _wrapper
return retry_decorator
@retry[2]
def might_fail[]:
print["might_fail"]
raise Exception
might_fail[]
4 trong số def retry[max_retries]:
def retry_decorator[func]:
def _wrapper[*args, **kwargs]:
for _ in range[max_retries]:
try:
func[*args, **kwargs]
except:
time.sleep[1]
return _wrapper
return retry_decorator
@retry[2]
def might_fail[]:
print["might_fail"]
raise Exception
might_fail[]
5 của chúng tôi giống như một thuộc tính, tôi. e. , bạn không cần phải gọi phương thức def retry[max_retries]:
def retry_decorator[func]:
def _wrapper[*args, **kwargs]:
for _ in range[max_retries]:
try:
func[*args, **kwargs]
except:
time.sleep[1]
return _wrapper
return retry_decorator
@retry[2]
def might_fail[]:
print["might_fail"]
raise Exception
might_fail[]
4. Thay vào đó, khi truy cập vào def retry[max_retries]:
def retry_decorator[func]:
def _wrapper[*args, **kwargs]:
for _ in range[max_retries]:
try:
func[*args, **kwargs]
except:
time.sleep[1]
return _wrapper
return retry_decorator
@retry[2]
def might_fail[]:
print["might_fail"]
raise Exception
might_fail[]
4 giống như một thuộc tính [không có def retry[max_retries]:
def retry_decorator[func]:
def _wrapper[*args, **kwargs]:
for _ in range[max_retries]:
try:
func[*args, **kwargs]
except:
time.sleep[1]
return _wrapper
return retry_decorator
@retry[2]
def might_fail[]:
print["might_fail"]
raise Exception
might_fail[]
8], phương thức này được gọi hoàn toàn bởi vì trình trang trí def retry[max_retries]:
def retry_decorator[func]:
def _wrapper[*args, **kwargs]:
for _ in range[max_retries]:
try:
func[*args, **kwargs]
except:
time.sleep[1]
return _wrapper
return retry_decorator
@retry[2]
def might_fail[]:
print["might_fail"]
raise Exception
might_fail[]
3Làm thế nào nó hoạt động?
Viết
def retry[max_retries]:
def retry_decorator[func]:
def _wrapper[*args, **kwargs]:
for _ in range[max_retries]:
try:
func[*args, **kwargs]
except:
time.sleep[1]
return _wrapper
return retry_decorator
@retry[2]
def might_fail[]:
print["might_fail"]
raise Exception
might_fail[]
3 trước định nghĩa hàm tương đương với viết import functools
import time
def timer[func]:
@functools.wraps[func]
def _wrapper[*args, **kwargs]:
start = time.perf_counter[]
result = func[*args, **kwargs]
runtime = time.perf_counter[] - start
print[f"{func.__name__} took {runtime:.4f} secs"]
return result
return _wrapper
@timer
def complex_calculation[]:
"""Some complex calculation."""
time.sleep[0.5]
return 42
print[complex_calculation[]]
1. Nói cách khác. import functools
import time
def timer[func]:
@functools.wraps[func]
def _wrapper[*args, **kwargs]:
start = time.perf_counter[]
result = func[*args, **kwargs]
runtime = time.perf_counter[] - start
print[f"{func.__name__} took {runtime:.4f} secs"]
return result
return _wrapper
@timer
def complex_calculation[]:
"""Some complex calculation."""
time.sleep[0.5]
return 42
print[complex_calculation[]]
2 là một hàm nhận một hàm khác làm đối số và trả về một hàm thứ ba. Và đây chính xác là những gì người trang trí làmDo đó, các bộ trang trí thay đổi hành vi của chức năng được trang trí
Viết trang trí tùy chỉnh
Thử lại Trình trang trí
Với định nghĩa mơ hồ đó, hãy viết các bộ trang trí của riêng chúng ta để hiểu cách chúng hoạt động
Giả sử chúng ta có một chức năng mà chúng ta muốn thử lại nếu nó không thành công. Chúng tôi cần một chức năng [trình trang trí của chúng tôi] gọi chức năng của chúng tôi một hoặc hai lần [tùy thuộc vào lần đầu tiên nó có bị lỗi hay không]
Theo định nghĩa ban đầu của chúng tôi về một trình trang trí, chúng tôi có thể viết trình trang trí đơn giản này như thế này
def retry[func]:
def _wrapper[*args, **kwargs]:
try:
func[*args, **kwargs]
except:
time.sleep[1]
func[*args, **kwargs]
return _wrapper
@retry
def might_fail[]:
print["might_fail"]
raise Exception
might_fail[]
import functools
import time
def timer[func]:
@functools.wraps[func]
def _wrapper[*args, **kwargs]:
start = time.perf_counter[]
result = func[*args, **kwargs]
runtime = time.perf_counter[] - start
print[f"{func.__name__} took {runtime:.4f} secs"]
return result
return _wrapper
@timer
def complex_calculation[]:
"""Some complex calculation."""
time.sleep[0.5]
return 42
print[complex_calculation[]]
3 là tên của trình trang trí của chúng tôi, nó chấp nhận bất kỳ chức năng nào làm đối số [import functools
import time
def timer[func]:
@functools.wraps[func]
def _wrapper[*args, **kwargs]:
start = time.perf_counter[]
result = func[*args, **kwargs]
runtime = time.perf_counter[] - start
print[f"{func.__name__} took {runtime:.4f} secs"]
return result
return _wrapper
@timer
def complex_calculation[]:
"""Some complex calculation."""
time.sleep[0.5]
return 42
print[complex_calculation[]]
4]. Bên trong decorator, một chức năng mới [_______8_______5] được xác định và trả về. Thoạt nhìn có thể hơi lạ lẫm khi định nghĩa một hàm bên trong một hàm khác. Tuy nhiên, điều này hoàn toàn ổn về mặt cú pháp và có lợi thế là hàm import functools
import time
def timer[func]:
@functools.wraps[func]
def _wrapper[*args, **kwargs]:
start = time.perf_counter[]
result = func[*args, **kwargs]
runtime = time.perf_counter[] - start
print[f"{func.__name__} took {runtime:.4f} secs"]
return result
return _wrapper
@timer
def complex_calculation[]:
"""Some complex calculation."""
time.sleep[0.5]
return 42
print[complex_calculation[]]
5 của chúng ta chỉ hợp lệ bên trong không gian tên của trình trang trí import functools
import time
def timer[func]:
@functools.wraps[func]
def _wrapper[*args, **kwargs]:
start = time.perf_counter[]
result = func[*args, **kwargs]
runtime = time.perf_counter[] - start
print[f"{func.__name__} took {runtime:.4f} secs"]
return result
return _wrapper
@timer
def complex_calculation[]:
"""Some complex calculation."""
time.sleep[0.5]
return 42
print[complex_calculation[]]
3 của chúng taLưu ý rằng trong ví dụ này, chúng tôi đã trang trí chức năng của mình chỉ bằng
import functools
import time
def timer[func]:
@functools.wraps[func]
def _wrapper[*args, **kwargs]:
start = time.perf_counter[]
result = func[*args, **kwargs]
runtime = time.perf_counter[] - start
print[f"{func.__name__} took {runtime:.4f} secs"]
return result
return _wrapper
@timer
def complex_calculation[]:
"""Some complex calculation."""
time.sleep[0.5]
return 42
print[complex_calculation[]]
8. Không có dấu ngoặc đơn [def retry[max_retries]:
def retry_decorator[func]:
def _wrapper[*args, **kwargs]:
for _ in range[max_retries]:
try:
func[*args, **kwargs]
except:
time.sleep[1]
return _wrapper
return retry_decorator
@retry[2]
def might_fail[]:
print["might_fail"]
raise Exception
might_fail[]
8] sau trang trí import functools
import time
def timer[func]:
@functools.wraps[func]
def _wrapper[*args, **kwargs]:
start = time.perf_counter[]
result = func[*args, **kwargs]
runtime = time.perf_counter[] - start
print[f"{func.__name__} took {runtime:.4f} secs"]
return result
return _wrapper
@timer
def complex_calculation[]:
"""Some complex calculation."""
time.sleep[0.5]
return 42
print[complex_calculation[]]
8. Do đó, khi gọi hàm complex_calculation took 0.5041 secs
42
1 của chúng ta, trình trang trí import functools
import time
def timer[func]:
@functools.wraps[func]
def _wrapper[*args, **kwargs]:
start = time.perf_counter[]
result = func[*args, **kwargs]
runtime = time.perf_counter[] - start
print[f"{func.__name__} took {runtime:.4f} secs"]
return result
return _wrapper
@timer
def complex_calculation[]:
"""Some complex calculation."""
time.sleep[0.5]
return 42
print[complex_calculation[]]
3 được gọi với hàm [complex_calculation took 0.5041 secs
42
3] làm đối số đầu tiênTổng cộng, chúng tôi xử lý ba chức năng ở đây
Trong một số trường hợp, chúng ta cần decorator chấp nhận các đối số. Trong trường hợp của chúng tôi, chúng tôi có thể biến số lần thử lại thành tham số. Tuy nhiên, một trình trang trí phải lấy chức năng trang trí của chúng tôi làm đối số đầu tiên. Hãy nhớ rằng chúng ta không cần gọi decorator khi trang trí một chức năng với nó, tôi. e. chúng tôi vừa viết
import functools
import time
def timer[func]:
@functools.wraps[func]
def _wrapper[*args, **kwargs]:
start = time.perf_counter[]
result = func[*args, **kwargs]
runtime = time.perf_counter[] - start
print[f"{func.__name__} took {runtime:.4f} secs"]
return result
return _wrapper
@timer
def complex_calculation[]:
"""Some complex calculation."""
time.sleep[0.5]
return 42
print[complex_calculation[]]
8 thay vì complex_calculation took 0.5041 secs
42
5 trước định nghĩa chức năng được trang trí của chúng tôi- Trình trang trí không gì khác hơn là một hàm [chấp nhận một hàm khác làm đối số]
- Trình trang trí được sử dụng bằng cách đặt nó trước định nghĩa hàm mà không gọi nó
Do đó, chúng ta có thể giới thiệu một hàm thứ tư chấp nhận tham số mà chúng ta muốn làm cấu hình và trả về một hàm thực sự là một hàm trang trí [chấp nhận một hàm khác làm đối số]
Chúng ta hãy cố gắng này
def retry[max_retries]:
def retry_decorator[func]:
def _wrapper[*args, **kwargs]:
for _ in range[max_retries]:
try:
func[*args, **kwargs]
except:
time.sleep[1]
return _wrapper
return retry_decorator
@retry[2]
def might_fail[]:
print["might_fail"]
raise Exception
might_fail[]
Xé nát cái đó
- Ở cấp độ đầu tiên, chúng ta có một chức năng gọi là
3import functools import time def timer[func]: @functools.wraps[func] def _wrapper[*args, **kwargs]: start = time.perf_counter[] result = func[*args, **kwargs] runtime = time.perf_counter[] - start print[f"{func.__name__} took {runtime:.4f} secs"] return result return _wrapper @timer def complex_calculation[]: """Some complex calculation.""" time.sleep[0.5] return 42 print[complex_calculation[]]
3 chấp nhận một đối số tùy ý [trong trường hợp của chúng ta là ____19_______8] và trả về một hàmimport functools import time def timer[func]: @functools.wraps[func] def _wrapper[*args, **kwargs]: start = time.perf_counter[] result = func[*args, **kwargs] runtime = time.perf_counter[] - start print[f"{func.__name__} took {runtime:.4f} secs"] return result return _wrapper @timer def complex_calculation[]: """Some complex calculation.""" time.sleep[0.5] return 42 print[complex_calculation[]]
9 là hàm được trả về bởicomplex_calculation took 0.5041 secs 42
3 và là công cụ trang trí thực tế của chúng taimport functools import time def timer[func]: @functools.wraps[func] def _wrapper[*args, **kwargs]: start = time.perf_counter[] result = func[*args, **kwargs] runtime = time.perf_counter[] - start print[f"{func.__name__} took {runtime:.4f} secs"] return result return _wrapper @timer def complex_calculation[]: """Some complex calculation.""" time.sleep[0.5] return 42 print[complex_calculation[]]
5 hoạt động theo cách tương tự như trước đây [hiện tại nó chỉ tuân theo số lần thử lại tối đa]import functools import time def timer[func]: @functools.wraps[func] def _wrapper[*args, **kwargs]: start = time.perf_counter[] result = func[*args, **kwargs] runtime = time.perf_counter[] - start print[f"{func.__name__} took {runtime:.4f} secs"] return result return _wrapper @timer def complex_calculation[]: """Some complex calculation.""" time.sleep[0.5] return 42 print[complex_calculation[]]
Đó là định nghĩa của trang trí của chúng tôi
3 được tô điểm bằng một lệnh gọi hàm lần này, tôi. e.complex_calculation took 0.5041 secs 42
3@timer class MyClass: def complex_calculation[self]: time.sleep[1] return 42 my_obj = MyClass[] my_obj.complex_calculation[]
4 khiến hàm@timer class MyClass: def complex_calculation[self]: time.sleep[1] return 42 my_obj = MyClass[] my_obj.complex_calculation[]
3 được gọi và nó trả về trình trang trí thực tếimport functools import time def timer[func]: @functools.wraps[func] def _wrapper[*args, **kwargs]: start = time.perf_counter[] result = func[*args, **kwargs] runtime = time.perf_counter[] - start print[f"{func.__name__} took {runtime:.4f} secs"] return result return _wrapper @timer def complex_calculation[]: """Some complex calculation.""" time.sleep[0.5] return 42 print[complex_calculation[]]
3 cuối cùng được trang trí bởicomplex_calculation took 0.5041 secs 42
9 vì chức năng này là kết quả của cuộc gọicomplex_calculation took 0.5041 secs 42
4@timer class MyClass: def complex_calculation[self]: time.sleep[1] return 42 my_obj = MyClass[] my_obj.complex_calculation[]
Đây là một ví dụ khác về một trang trí hữu ích. Hãy tạo một trình trang trí để đo thời gian chạy của các chức năng được trang trí với nó
________số 8_______
đầu ra
complex_calculation took 0.5041 secs
42
Như chúng ta thấy, trình trang trí
@timer
class MyClass:
def complex_calculation[self]:
time.sleep[1]
return 42
my_obj = MyClass[]
my_obj.complex_calculation[]
9 thực thi một số mã trước và sau chức năng được trang trí và hoạt động theo cách chính xác như trong ví dụ trướcFinished 'MyClass' in 0.0000 secs
0
Finished 'MyClass' in 0.0000 secs
Bạn có thể nhận thấy rằng bản thân chức năng
import functools
import time
def timer[func]:
@functools.wraps[func]
def _wrapper[*args, **kwargs]:
start = time.perf_counter[]
result = func[*args, **kwargs]
runtime = time.perf_counter[] - start
print[f"{func.__name__} took {runtime:.4f} secs"]
return result
return _wrapper
@timer
def complex_calculation[]:
"""Some complex calculation."""
time.sleep[0.5]
return 42
print[complex_calculation[]]
5 được trang trí bằng Finished 'MyClass' in 0.0000 secs
2. Điều này không làm thay đổi logic hoặc chức năng của trình trang trí @timer
class MyClass:
def complex_calculation[self]:
time.sleep[1]
return 42
my_obj = MyClass[]
my_obj.complex_calculation[]
9 của chúng tôi theo bất kỳ cách nào. Bạn cũng có thể quyết định không sử dụng Finished 'MyClass' in 0.0000 secs
0Tuy nhiên, vì trang trí
Finished 'MyClass' in 0.0000 secs
5 của chúng tôi cũng có thể được viết là. Finished 'MyClass' in 0.0000 secs
6, decorator nhất thiết phải thay đổi hàm Finished 'MyClass' in 0.0000 secs
7 của chúng ta. Đặc biệt, nó thay đổi một số thuộc tính phản xạ ma thuật
8Finished 'MyClass' in 0.0000 secs
9Finished 'MyClass' in 0.0000 secs
0class MyDecorator: def __init__[self, function]: self.function = function self.counter = 0 def __call__[self, *args, **kwargs]: self.function[*args, **kwargs] self.counter+=1 print[f"Called {self.counter} times"] @MyDecorator def some_function[]: return 42 some_function[] some_function[] some_function[]
1class MyDecorator: def __init__[self, function]: self.function = function self.counter = 0 def __call__[self, *args, **kwargs]: self.function[*args, **kwargs] self.counter+=1 print[f"Called {self.counter} times"] @MyDecorator def some_function[]: return 42 some_function[] some_function[] some_function[]
2class MyDecorator: def __init__[self, function]: self.function = function self.counter = 0 def __call__[self, *args, **kwargs]: self.function[*args, **kwargs] self.counter+=1 print[f"Called {self.counter} times"] @MyDecorator def some_function[]: return 42 some_function[] some_function[] some_function[]
Khi sử dụng
Finished 'MyClass' in 0.0000 secs
2, các thuộc tính này được đặt lại về giá trị ban đầuKhông có
Finished 'MyClass' in 0.0000 secs
2Với
Finished 'MyClass' in 0.0000 secs
2Trang trí lớp học
Cho đến nay, chúng ta chỉ xem xét các bộ trang trí cho các chức năng. Tuy nhiên, cũng có thể trang trí các lớp học
Hãy lấy trang trí
@timer
class MyClass:
def complex_calculation[self]:
time.sleep[1]
return 42
my_obj = MyClass[]
my_obj.complex_calculation[]
9 từ ví dụ trên. Hoàn toàn ổn khi bọc một lớp với trình trang trí này như vậy@timer
class MyClass:
def complex_calculation[self]:
time.sleep[1]
return 42
my_obj = MyClass[]
my_obj.complex_calculation[]
Kết quả?
Finished 'MyClass' in 0.0000 secs
Vì vậy, rõ ràng là không có thời gian in cho phương pháp
Finished 'MyClass' in 0.0000 secs
7 của chúng tôi. Hãy nhớ rằng ký hiệu class MyDecorator:
def __init__[self, function]:
self.function = function
self.counter = 0
def __call__[self, *args, **kwargs]:
self.function[*args, **kwargs]
self.counter+=1
print[f"Called {self.counter} times"]
@MyDecorator
def some_function[]:
return 42
some_function[]
some_function[]
some_function[]
8 chỉ tương đương với cách viết class MyDecorator:
def __init__[self, function]:
self.function = function
self.counter = 0
def __call__[self, *args, **kwargs]:
self.function[*args, **kwargs]
self.counter+=1
print[f"Called {self.counter} times"]
@MyDecorator
def some_function[]:
return 42
some_function[]
some_function[]
some_function[]
9, i. e. , trình trang trí sẽ chỉ được gọi khi bạn “gọi” lớp. Gọi một lớp có nghĩa là khởi tạo nó, vì vậy bộ đếm thời gian chỉ được thực hiện tại dòng Called 1 times
Called 2 times
Called 3 times
0Các phương thức lớp không được trang trí tự động khi trang trí một lớp. Nói một cách đơn giản, sử dụng một trình trang trí bình thường để trang trí một lớp bình thường trang trí hàm tạo của nó [phương thức
Called 1 times
Called 2 times
Called 3 times
1], chỉTuy nhiên, bạn có thể thay đổi toàn bộ hành vi của một lớp bằng cách sử dụng một dạng khác của hàm tạo. Tuy nhiên, trước tiên hãy xem liệu các nhà trang trí có thể làm việc theo cách khác hay không, tôi. e. liệu chúng ta có thể trang trí một chức năng với một lớp. Hóa ra chúng ta có thể
class MyDecorator:
def __init__[self, function]:
self.function = function
self.counter = 0
def __call__[self, *args, **kwargs]:
self.function[*args, **kwargs]
self.counter+=1
print[f"Called {self.counter} times"]
@MyDecorator
def some_function[]:
return 42
some_function[]
some_function[]
some_function[]
đầu ra
Called 1 times
Called 2 times
Called 3 times
cách này hoạt động
1 được gọi khi trang tríCalled 1 times Called 2 times Called 3 times
3. Một lần nữa, hãy nhớ rằng trang trí cũng giống nhưCalled 1 times Called 2 times Called 3 times
4Called 1 times Called 2 times Called 3 times
5 được gọi khi một thể hiện của một lớp được sử dụng, giống như gọi một hàm. VìCalled 1 times Called 2 times Called 3 times
3 hiện là một phiên bản củaCalled 1 times Called 2 times Called 3 times
7 nhưng chúng tôi vẫn muốn sử dụng nó như một hàm, nên phương thức ma thuật DoubleUnderscoreCalled 1 times Called 2 times Called 3 times
5 chịu trách nhiệm cho việc nàyCalled 1 times Called 2 times Called 3 times
Mặt khác, trang trí một lớp trong Python hoạt động bằng cách thay đổi lớp từ bên ngoài [i. e. , từ người trang trí]
Xem xét điều này
def add_calc[target]:
def calc[self]:
return 42
target.calc = calc
return target
@add_calc
class MyClass:
def __init__[]:
print["MyClass __init__"]
my_obj = MyClass[]
print[my_obj.calc[]]
đầu ra
Một lần nữa, nếu chúng ta tóm tắt lại định nghĩa của một người trang trí, mọi thứ xảy ra ở đây đều tuân theo cùng một logic
0 đang gọi người trang trí trướcCalled 1 times Called 2 times Called 3 times
- trình trang trí
0 vá phương thứcdef add_calc[target]: def calc[self]: return 42 target.calc = calc return target @add_calc class MyClass: def __init__[]: print["MyClass __init__"] my_obj = MyClass[] print[my_obj.calc[]]
1 cho lớpdef add_calc[target]: def calc[self]: return 42 target.calc = calc return target @add_calc class MyClass: def __init__[]: print["MyClass __init__"] my_obj = MyClass[] print[my_obj.calc[]]
- cuối cùng, lớp được khởi tạo bằng cách sử dụng hàm tạo
Bạn có thể sử dụng các công cụ trang trí để thay đổi các lớp theo cách kế thừa sẽ làm. Nếu đây là một lựa chọn tốt hay không phụ thuộc nhiều vào kiến trúc của toàn bộ dự án Python của bạn. Trình trang trí
def add_calc[target]:
def calc[self]:
return 42
target.calc = calc
return target
@add_calc
class MyClass:
def __init__[]:
print["MyClass __init__"]
my_obj = MyClass[]
print[my_obj.calc[]]
2 của thư viện tiêu chuẩn là một ví dụ tuyệt vời về cách sử dụng hợp lý khi chọn trình trang trí thay vì kế thừa. Chúng ta sẽ thảo luận về điều đó trong giây látsử dụng trang trí
decorators trong thư viện chuẩn của Python
Trong các phần tiếp theo, chúng ta sẽ làm quen với một số decorator phổ biến nhất và hữu ích nhất đã có sẵn trong thư viện chuẩn.
tài sản
Như đã thảo luận, trình trang trí
def retry[max_retries]:
def retry_decorator[func]:
def _wrapper[*args, **kwargs]:
for _ in range[max_retries]:
try:
func[*args, **kwargs]
except:
time.sleep[1]
return _wrapper
return retry_decorator
@retry[2]
def might_fail[]:
print["might_fail"]
raise Exception
might_fail[]
3 có lẽ là một trong những trình trang trí được sử dụng phổ biến nhất trong Python. Mục đích của nó là bạn có thể truy cập kết quả của một phương thức giống như một thuộc tính. Tất nhiên, cũng có một đối tác của def retry[max_retries]:
def retry_decorator[func]:
def _wrapper[*args, **kwargs]:
for _ in range[max_retries]:
try:
func[*args, **kwargs]
except:
time.sleep[1]
return _wrapper
return retry_decorator
@retry[2]
def might_fail[]:
print["might_fail"]
raise Exception
might_fail[]
3 để bạn có thể gọi một phương thức đằng sau hậu trường khi thực hiện một thao tác gánphương pháp tĩnh
Một người trang trí quen thuộc khác là
def add_calc[target]:
def calc[self]:
return 42
target.calc = calc
return target
@add_calc
class MyClass:
def __init__[]:
print["MyClass __init__"]
my_obj = MyClass[]
print[my_obj.calc[]]
5. Trình trang trí này được sử dụng khi bạn muốn gọi một hàm được định nghĩa bên trong một lớp mà không khởi tạo lớp đóclass C:
@staticmethod
def the_static_method[arg1, arg2]:
return 42
print[C.the_static_method[]]
Khi bạn xử lý các hàm thực hiện một phép tính phức tạp, bạn có thể muốn lưu kết quả của nó vào bộ nhớ cache
Bạn có thể làm một cái gì đó như thế này
def retry[max_retries]:
def retry_decorator[func]:
def _wrapper[*args, **kwargs]:
for _ in range[max_retries]:
try:
func[*args, **kwargs]
except:
time.sleep[1]
return _wrapper
return retry_decorator
@retry[2]
def might_fail[]:
print["might_fail"]
raise Exception
might_fail[]
0Lưu trữ một biến toàn cục như
def add_calc[target]:
def calc[self]:
return 42
target.calc = calc
return target
@add_calc
class MyClass:
def __init__[]:
print["MyClass __init__"]
my_obj = MyClass[]
print[my_obj.calc[]]
6, kiểm tra biến đó để tìm def add_calc[target]:
def calc[self]:
return 42
target.calc = calc
return target
@add_calc
class MyClass:
def __init__[]:
print["MyClass __init__"]
my_obj = MyClass[]
print[my_obj.calc[]]
7 và đặt kết quả thực tế vào biến đó nếu không có là các tác vụ lặp đi lặp lại. Điều này làm cho một ứng cử viên lý tưởng cho một trang trí. May mắn thay, có một trình trang trí trong thư viện chuẩn của Python thực hiện chính xác điều này cho chúng tôidef retry[max_retries]:
def retry_decorator[func]:
def _wrapper[*args, **kwargs]:
for _ in range[max_retries]:
try:
func[*args, **kwargs]
except:
time.sleep[1]
return _wrapper
return retry_decorator
@retry[2]
def might_fail[]:
print["might_fail"]
raise Exception
might_fail[]
1Bây giờ, bất cứ khi nào bạn gọi
def add_calc[target]:
def calc[self]:
return 42
target.calc = calc
return target
@add_calc
class MyClass:
def __init__[]:
print["MyClass __init__"]
my_obj = MyClass[]
print[my_obj.calc[]]
8, Python sẽ kiểm tra kết quả được lưu trong bộ nhớ cache trước khi nó gọi def add_calc[target]:
def calc[self]:
return 42
target.calc = calc
return target
@add_calc
class MyClass:
def __init__[]:
print["MyClass __init__"]
my_obj = MyClass[]
print[my_obj.calc[]]
9. Nếu có một kết quả trong bộ đệm, thì def add_calc[target]:
def calc[self]:
return 42
target.calc = calc
return target
@add_calc
class MyClass:
def __init__[]:
print["MyClass __init__"]
my_obj = MyClass[]
print[my_obj.calc[]]
9 sẽ không được gọi hai lầnlớp dữ liệu
Trong phần về các bộ trang trí lớp, chúng ta đã thấy rằng các bộ trang trí có thể được sử dụng để sửa đổi hành vi của các lớp giống như cách thừa kế sẽ thay đổi nó
Mô-đun dataclasses trong thư viện chuẩn là một ví dụ điển hình khi sử dụng trình trang trí tốt hơn sử dụng tính kế thừa. Trước tiên hãy xem cách sử dụng
class C:
@staticmethod
def the_static_method[arg1, arg2]:
return 42
print[C.the_static_method[]]
1 trong thực tếNgay từ cái nhìn đầu tiên, người trang trí
class C:
@staticmethod
def the_static_method[arg1, arg2]:
return 42
print[C.the_static_method[]]
2 chỉ thêm một hàm tạo cho chúng tôi, vì vậy chúng tôi đã tránh mã tấm nồi hơi như thế nàydef retry[max_retries]:
def retry_decorator[func]:
def _wrapper[*args, **kwargs]:
for _ in range[max_retries]:
try:
func[*args, **kwargs]
except:
time.sleep[1]
return _wrapper
return retry_decorator
@retry[2]
def might_fail[]:
print["might_fail"]
raise Exception
might_fail[]
2Tuy nhiên, nếu bạn quyết định xây dựng một REST-API cho dự án Python của mình và cần chuyển đổi các đối tượng Python thành các chuỗi JSON
Có một gói tên là
class C:
@staticmethod
def the_static_method[arg1, arg2]:
return 42
print[C.the_static_method[]]
3 [không có trong thư viện chuẩn] trang trí các lớp dữ liệu và cung cấp khả năng tuần tự hóa và giải tuần tự hóa các đối tượng thành chuỗi JSON và ngược lạiHãy xem nó trông như thế nào
Có hai điều rút ra ở đây
- trang trí có thể được lồng vào nhau. Thứ tự xuất hiện của họ là quan trọng
- người trang trí
4 đã thêm một phương thức gọi làclass C: @staticmethod def the_static_method[arg1, arg2]: return 42 print[C.the_static_method[]]
5 vào lớp của chúng tôiclass C: @staticmethod def the_static_method[arg1, arg2]: return 42 print[C.the_static_method[]]
Tất nhiên, chúng ta có thể đã viết một lớp mixin thực hiện công việc nặng nhọc là triển khai một phương thức
class C:
@staticmethod
def the_static_method[arg1, arg2]:
return 42
print[C.the_static_method[]]
5 an toàn cho kiểu dữ liệu và sau đó để lớp class C:
@staticmethod
def the_static_method[arg1, arg2]:
return 42
print[C.the_static_method[]]
7 của chúng ta kế thừa từ mixin đóTuy nhiên, trong trường hợp hiện tại, trình trang trí chỉ thêm một chức năng kỹ thuật [trái ngược với phần mở rộng trong miền chủ đề]. Do đó, chúng ta có thể chỉ cần bật và tắt trình trang trí mà ứng dụng miền của chúng ta không thay đổi hành vi của nó. Hệ thống phân cấp lớp “tự nhiên” của chúng tôi được giữ nguyên và không cần thực hiện thay đổi nào đối với mã thực tế. Chúng tôi cũng có thể thêm trình trang trí
class C:
@staticmethod
def the_static_method[arg1, arg2]:
return 42
print[C.the_static_method[]]
3 vào dự án mà không thay đổi nội dung phương thức hiện cóTrong trường hợp như vậy, việc thay đổi một lớp bằng một trình trang trí sẽ thanh lịch hơn nhiều [vì nó mang tính mô-đun hơn] so với việc kế thừa hoặc sử dụng mixin