Khi làm code, dù biết hay không, chúng ta vẫn thường bắt gặp decorator design pattern. Đây là một kỹ thuật lập trình để mở rộng chức năng của các lớp hoặc hàm mà không sửa đổi chúng. Mẫu thiết kế trang trí cho phép chúng tôi trộn và kết hợp các tiện ích mở rộng một cách dễ dàng. Python có cú pháp trang trí bắt nguồn từ mẫu thiết kế trang trí. Biết cách tạo và sử dụng trình trang trí có thể giúp bạn viết mã hiệu quả hơn
Trong bài đăng này, bạn sẽ khám phá mẫu trang trí và các bộ trang trí chức năng của Python
Sau khi hoàn thành hướng dẫn này, bạn sẽ học
- Mẫu trang trí là gì và tại sao nó hữu ích
- Trình trang trí chức năng của Python và cách sử dụng chúng
Bắt đầu dự án của bạn với cuốn sách mới Python for Machine Learning của tôi, bao gồm các hướng dẫn từng bước và các tệp mã nguồn Python cho tất cả các ví dụ
Bắt đầu nàoGiới thiệu nhẹ nhàng về Trình trang trí trong Python
Ảnh của Olya Kobruseva. Một số quyền được bảo lưu
Tổng quan
Hướng dẫn này được chia thành bốn phần
- Mẫu trang trí là gì và tại sao nó hữu ích?
- Trình trang trí chức năng trong Python
- Các trường hợp sử dụng của trang trí
- Một số ví dụ thực tế về decorator
Mẫu trang trí là gì và tại sao nó hữu ích?
Mẫu trang trí là một mẫu thiết kế phần mềm cho phép chúng ta tự động thêm chức năng vào các lớp mà không cần tạo các lớp con và ảnh hưởng đến hành vi của các đối tượng khác trong cùng một lớp. Bằng cách sử dụng mẫu trang trí, chúng tôi có thể dễ dàng tạo các hoán vị khác nhau của chức năng mà chúng tôi có thể muốn mà không cần tạo số lượng lớp con tăng theo cấp số nhân, làm cho mã của chúng tôi ngày càng phức tạp và cồng kềnh
Trình trang trí thường được triển khai dưới dạng giao diện phụ của giao diện chính mà chúng tôi muốn triển khai và lưu trữ một đối tượng thuộc loại của giao diện chính. Sau đó, nó sẽ sửa đổi các phương thức mà nó muốn thêm chức năng nhất định bằng cách ghi đè các phương thức trong giao diện ban đầu và gọi các phương thức từ đối tượng được lưu trữ
Sơ đồ lớp UML cho mẫu trang trí
Trên đây là sơ đồ lớp UML cho mẫu thiết kế trang trí. Lớp trừu tượng trang trí chứa một đối tượng kiểu OriginalInterface
; . Để khởi tạo DecoratorClass
cụ thể của chúng tôi, chúng tôi sẽ cần chuyển vào một lớp cụ thể triển khai OriginalInterface,
và sau đó khi chúng tôi thực hiện các cuộc gọi phương thức tới DecoratorClass.method1[]
, thì DecoratorClass
của chúng tôi sẽ sửa đổi đầu ra từ đối tượng method1[]
Tuy nhiên, với Python, chúng tôi có thể đơn giản hóa nhiều mẫu thiết kế này do gõ động cùng với các hàm và lớp là đối tượng hạng nhất. Mặc dù việc sửa đổi một lớp hoặc một hàm mà không thay đổi cách triển khai vẫn là ý tưởng chính của các trình trang trí, chúng ta sẽ khám phá cú pháp trình trang trí của Python trong phần sau
Trình trang trí chức năng trong Python
Trình trang trí chức năng là một tính năng cực kỳ hữu ích trong Python. Nó được xây dựng dựa trên ý tưởng rằng các hàm và lớp là các đối tượng hạng nhất trong Python
Hãy xem xét một ví dụ đơn giản, đó là gọi một hàm hai lần. Vì một hàm Python là một đối tượng và chúng ta có thể truyền một hàm làm đối số cho một hàm khác, nên tác vụ này có thể được thực hiện như sau
1
2
3
4
5
6
7
8
def repeat[fn]:
fn[]
fn[]
def hello_world[].
in["Xin chào thế giới. "]
repeat[hello_world]
Một lần nữa, vì một hàm Python là một đối tượng, chúng ta có thể tạo một hàm để trả về một hàm khác, nghĩa là thực thi một hàm khác hai lần. Điều này được thực hiện như sau
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def repeat_decorator[fn]:
def decorated_fn[]:
fn[]
fn[]
# trả về một hàm
return decorated_fn
def hello_world[].
in ["Xin chào thế giới. "]
hello_world_twice = repeat_decorator[hello_world]
#gọi hàm
hello_world_twice[]
Hàm được trả về bởi repeat_decorator[]
ở trên được tạo khi nó được gọi, vì nó phụ thuộc vào đối số được cung cấp. Ở phần trên, chúng ta đã chuyển hàm hello_world
làm đối số cho hàm repeat_decorator[]
và nó trả về hàm decorated_fn
, được gán cho DecoratorClass
0. Sau đó, chúng ta có thể gọi DecoratorClass
1 vì bây giờ nó là một hàm
Ý tưởng về mẫu trang trí áp dụng ở đây. Nhưng chúng ta không cần định nghĩa rõ ràng giao diện và các lớp con. Trên thực tế, hello_world
là một tên được định nghĩa như một hàm trong ví dụ trên. Không có gì ngăn cản chúng ta định nghĩa lại tên này thành tên khác. Do đó chúng ta cũng có thể làm như sau
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def repeat_decorator[fn]:
def decorated_fn[]:
fn[]
fn[]
# trả về một hàm
return decorated_fn
def hello_world[].
in ["Xin chào thế giới. "]
hello_world = repeat_decorator[hello_world]
#gọi hàm
hello_world[]
Tức là, thay vì gán hàm mới tạo cho DecoratorClass
0, chúng ta ghi đè lên hello_world
thay thế. Trong khi tên hello_world
được gán lại cho một chức năng khác, chức năng trước đó vẫn tồn tại nhưng không được hiển thị cho chúng tôi
Thật vậy, đoạn mã trên có chức năng tương đương như sau
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# chức năng trang trí gọi chức năng hai lần
def repeat_decorator[fn]:
def decorated_fn[]:
fn[]
fn[]
# trả về một hàm
trả lại được trang trí_ fn
# sử dụng decorator trên hàm hello_world
@repeat_decorator
def hello_world[].
in ["Xin chào thế giới. "]
#gọi hàm
hello_world[]
Trong đoạn mã trên, DecoratorClass
6 trước định nghĩa hàm có nghĩa là chuyển hàm vào repeat_decorator[]
và gán lại tên của nó cho đầu ra. Đó là, có nghĩa là DecoratorClass
8. Dòng DecoratorClass
9 là cú pháp trang trí trong Python
Ghi chú. Cú pháp DecoratorClass
9 cũng được sử dụng trong Java nhưng có ý nghĩa khác khi đó là một chú thích về cơ bản là siêu dữ liệu chứ không phải là một công cụ trang trí
Chúng ta cũng có thể triển khai các trình trang trí nhận các đối số, nhưng điều này sẽ phức tạp hơn một chút vì chúng ta cần có thêm một lớp lồng nhau. Nếu chúng ta mở rộng ví dụ trên để xác định số lần lặp lại lời gọi hàm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def repeat_decorator[num_repeats = 2]:
# repeat_decorator sẽ trả về một hàm là trình trang trí
def inner_decorator[fn]:
def decorated_fn[]:
cho i trong phạm vi[num_repeats]:
fn[]
# trả lại hàm mới
trả lại được trang trí_ fn
# trả về trình trang trí thực sự lấy chức năng làm đầu vào
return inner_ trang trí
# sử dụng trình trang trí với đối số num_repeat được đặt là 5 để lặp lại lời gọi hàm 5 lần
@repeat_decorator[5]
def hello_world[].
in["Xin chào thế giới. "]
#gọi hàm
hello_world[]
repeat_decorator[]
nhận một đối số và trả về một hàm là công cụ trang trí thực tế cho hàm hello_world
[i. e. , gọi OriginalInterface,
3 trả về OriginalInterface,
4 với biến cục bộ OriginalInterface,
5 được đặt]. Đoạn mã trên sẽ in như sau
1
2
3
4
5
Xin chào thế giới.
Xin chào thế giới.
Xin chào thế giới.
Xin chào thế giới.
Xin chào thế giới.
Trước khi kết thúc phần này, chúng ta nên nhớ rằng các trình trang trí cũng có thể được áp dụng cho các lớp ngoài các hàm. Vì lớp trong Python cũng là một đối tượng, nên chúng ta có thể định nghĩa lại một lớp theo cách tương tự
Bạn muốn bắt đầu với Python cho Machine Learning?
Tham gia khóa học xử lý sự cố email miễn phí trong 7 ngày của tôi ngay bây giờ [có mã mẫu]
Nhấp để đăng ký và cũng nhận được phiên bản PDF Ebook miễn phí của khóa học
Tải xuống khóa học nhỏ MIỄN PHÍ của bạn
Các trường hợp sử dụng của trang trí
Cú pháp trang trí trong Python giúp việc sử dụng trang trí dễ dàng hơn. Có nhiều lý do chúng ta có thể sử dụng một trang trí. Một trong những trường hợp sử dụng phổ biến nhất là chuyển đổi dữ liệu ngầm. Ví dụ: chúng ta có thể định nghĩa một hàm giả định rằng tất cả các hoạt động đều dựa trên các mảng có nhiều mảng và sau đó tạo một trình trang trí để đảm bảo điều đó xảy ra bằng cách sửa đổi đầu vào
1
2
3
4
5
6
7
8
# chức năng trang trí để đảm bảo đầu vào gọn gàng
def ensure_numpy[fn]:
def decorated_function[dữ liệu]:
# chuyển đổi đầu vào thành mảng có nhiều mảng
mảng = np. sắp xếp[dữ liệu]
# gọi fn trên mảng numpy đầu vào
return fn[mảng]
return decorated_function
Chúng ta có thể thêm vào trình trang trí của mình bằng cách sửa đổi đầu ra của hàm, chẳng hạn như làm tròn các giá trị dấu phẩy động
1
2
3
4
5
6
7
8
# chức năng trang trí để đảm bảo đầu vào gọn gàng
# và làm tròn đầu ra đến 4 chữ số thập phân
def ensure_numpy[fn]:
def decorated_function[dữ liệu]:
mảng = np. sắp xếp[dữ liệu]
đầu ra = fn[array]
return np. xung quanh[đầu ra, 4]
return decorated_function
Hãy xem xét ví dụ tìm tổng của một mảng. Một mảng numpy có tích hợp sẵn OriginalInterface,
6, pandas DataFrame cũng vậy. Nhưng cái sau là tính tổng trên các cột chứ không phải tính tổng trên tất cả các phần tử. Do đó, một mảng có nhiều mảng sẽ tính tổng bằng một giá trị dấu phẩy động trong khi DataFrame sẽ tính tổng bằng một vectơ giá trị. Nhưng với trình trang trí ở trên, chúng ta có thể viết một hàm cung cấp cho bạn đầu ra nhất quán trong cả hai trường hợp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
nhập numpy as np
nhập gấu trúc as pd
# chức năng trang trí để đảm bảo đầu vào gọn gàng
# và làm tròn đầu ra đến 4 chữ số thập phân
def ensure_numpy[fn]:
def decorated_function[dữ liệu]:
mảng = np. sắp xếp[dữ liệu]
đầu ra = fn[array]
return np. xung quanh[đầu ra, 4]
trả về được trang trí_ chức năng
@ensure_numpy
def numpysum[mảng]:
trả về mảng. tổng[]
x = np. ngẫu nhiên. randn[10,3]
y = pd. Khung dữ liệu[x, cột=["A", "B", "C"]]
# đầu ra của numpy. hàm tổng []
in["x. Tổng[]. ", x. tổng[]]
in[]
# đầu ra của gấu trúc. hàm tổng []
in["y. Tổng[]. ", y. tổng[]]
in[y. tổng[]]
in[]
# gọi hàm numpysum được trang trí
in["numpysum[x]. ", numpysum[x]]
in["numpysum[y]. ", numpysum[y]]
Chạy đoạn mã trên cho chúng ta đầu ra
1
2
3
4
5
6
7
8
9
10
11
12
13
x. tổng[]. 0. 3948331694737762
y. tổng[]. A -1. 175484
B 2. 496056
C -0. 925739
dtype. float64
A -1. 175484
B 2. 496056
C -0. 925739
dtype. float64
numpysum[x]. 0. 3948
numpysum[y]. 0. 3948
Đây là một ví dụ đơn giản. Nhưng hãy tưởng tượng nếu chúng ta định nghĩa một hàm mới tính độ lệch chuẩn của các phần tử trong một mảng. Chúng ta chỉ cần sử dụng cùng một trình trang trí và sau đó hàm cũng sẽ chấp nhận DataFrame của gấu trúc. Do đó, tất cả mã để đánh bóng đầu vào được lấy ra khỏi các chức năng này bằng cách gửi chúng vào trình trang trí. Đây là cách chúng ta có thể sử dụng lại mã một cách hiệu quả
Một số ví dụ thực tế về Decorators
Bây giờ chúng ta đã học cú pháp decorator trong Python, hãy xem chúng ta có thể làm gì với nó
ghi nhớ
Có một số lệnh gọi hàm mà chúng ta thực hiện lặp đi lặp lại, nhưng ở đó các giá trị hiếm khi thay đổi, nếu có. Đây có thể là lệnh gọi đến máy chủ nơi dữ liệu tương đối tĩnh hoặc là một phần của thuật toán lập trình động hoặc hàm toán học chuyên sâu về tính toán. Chúng tôi có thể muốn ghi nhớ các cuộc gọi chức năng này, tôi. e. , lưu trữ giá trị đầu ra của chúng trên sổ ghi nhớ ảo để sử dụng lại sau này
Trình trang trí là cách tốt nhất để thực hiện chức năng ghi nhớ. Chúng ta chỉ cần nhớ đầu vào và đầu ra của hàm nhưng giữ nguyên hành vi của hàm. Dưới đây là một ví dụ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
nhập dưa chua
nhập hashlib
MEMO = {} # Để ghi nhớ chức năng
def ghi nhớ[fn]:
def _deco[*args, **kwargs]:
# chọn các đối số của hàm và lấy hàm băm làm khóa lưu trữ
phím = [fn.__name__, hashlib. md5[dưa chua. kết xuất[[args, . kwargs], 4]].hexdigest[]]
# kiểm tra xem khóa có tồn tại không
nếu phím trong MEMO:
ret = dưa chua. tải[MEMO[phím]]
khác.
ret = fn[*args, **kwargs]
MEMO[phím] = pickle.kết xuất[rút lui]
trả lại rút lại
return _deco
@ghi nhớ
def fibonacci[n]:
nếu n trong [0, 1]:
trả lại n
khác.
return fibonacci[n-1] + fibonacci[n-2]
in[fibonacci[40]]
in[MEMO]
Trong ví dụ này, chúng tôi đã triển khai OriginalInterface,
7 để làm việc với từ điển toàn cầu OriginalInterface,
8 sao cho tên của hàm cùng với các đối số trở thành khóa và kết quả trả về của hàm trở thành giá trị. Khi hàm được gọi, trình trang trí sẽ kiểm tra xem khóa tương ứng có tồn tại trong OriginalInterface,
8 hay không và giá trị được lưu trữ sẽ được trả về. Mặt khác, chức năng thực tế được gọi và giá trị trả về của nó được thêm vào từ điển
Chúng tôi sử dụng DecoratorClass.method1[]
0 để tuần tự hóa đầu vào và đầu ra và sử dụng DecoratorClass.method1[]
1 để tạo hàm băm của đầu vào vì không phải mọi thứ đều có thể là chìa khóa cho từ điển Python [e. g. , DecoratorClass.method1[]
2 là loại không thể băm; . Tuần tự hóa bất kỳ cấu trúc tùy ý nào thành một chuỗi có thể khắc phục điều này và đảm bảo rằng dữ liệu trả về là bất biến. Hơn nữa, việc băm đối số hàm sẽ tránh lưu trữ một khóa dài đặc biệt trong từ điển [ví dụ: khi chúng ta chuyển một mảng lớn có nhiều mảng cho hàm]
Ví dụ trên sử dụng DecoratorClass.method1[]
3 để chứng minh sức mạnh của việc ghi nhớ. Gọi DecoratorClass.method1[]
4 sẽ tạo ra số Fibonacci thứ n. Chạy ví dụ trên sẽ tạo ra kết quả sau, trong đó chúng ta có thể thấy số Fibonacci thứ 40 là 102334155 và cách từ điển OriginalInterface,
8 được sử dụng để lưu trữ các lệnh gọi khác nhau đến hàm
1
2
3
4
5
6
7
8
102334155
{['fibonacci', '635f1664f168e2a15b8e43f20d45154b']. b'\x80\x04K\x01. ',
['fibonacci', 'd238998870ae18a399d03477dad0c0a8']. b'\x80\x04K\x00. ',
['fibonacci', 'dbed6abf8fcf4beec7fc97f3170de3cc']. b'\x80\x04K\x01. ',
...
['fibonacci', 'b9954ff996a4cd0e36fffb09f982b08e']. b'\x80\x04\x95\x06\x00\x00\x00\x00\x00\x00\x00J]pT\x02. ',
['fibonacci', '8c7aba62def8063cf5afe85f42372f0d']. b'\x80\x04\x95\x06\x00\x00\x00\x00\x00\x00\x00J\xa2\x0e\xc5\x03. ',
['fibonacci', '6de8535f23d756de26959b4d6e1f66f6']. b'\x80\x04\x95\x06\x00\x00\x00\x00\x00\x00\x00J\xcb~\x19\x06. '}
Bạn có thể thử xóa dòng DecoratorClass.method1[]
6 trong đoạn mã trên. Bạn sẽ thấy chương trình mất nhiều thời gian hơn để chạy [vì mỗi lệnh gọi hàm gọi thêm hai lệnh gọi hàm nữa; do đó chương trình đang chạy trong O[2^n] thay vì O[n] như trong trường hợp được ghi nhớ], hoặc thậm chí bạn có thể
Ghi nhớ rất hữu ích cho các chức năng đắt tiền mà kết quả đầu ra không thay đổi thường xuyên, chẳng hạn như chức năng sau đây đọc một số dữ liệu thị trường chứng khoán từ Internet
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.. .
nhập pandas_datareader as pdr
@ghi nhớ
def get_stock_data[ticker]:
# lấy dữ liệu từ stooq
df = pdr. stooq. StooqDailyReader[ký hiệu=ticker . , start="1/1/00", end="31/12/21"].đọc[]
return df
#kiểm tra lệnh gọi chức năng
nhập CHồ sơ dưới dạng hồ sơ
nhập pstats
cho i trong phạm vi[1, 3]:
print[f"Run {i}"]
run_profile = profile. Hồ sơ[]
run_profile. bật[]
get_stock_data["^DJI"]