Trình trang trí đối tượng Python

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ào

Giớ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 DecoratorClass0. Sau đó, chúng ta có thể gọi DecoratorClass1 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 DecoratorClass0, 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, DecoratorClass6 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à DecoratorClass8. Dòng DecoratorClass9 là cú pháp trang trí trong Python

Ghi chú. Cú pháp DecoratorClass9 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"]

Chủ Đề