Python tự trang trí phương thức lớp

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ớp

Sự 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.funcA[].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_change0 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_change1, bởi vì bạn thấy một trình mô tả thô bên trong lớp

khai báo bên ngoài

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_change3 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

Khai báo trong lớp bên trong

Bạn cũng có thể đặt atomic_rating_change5 ở chế độ riêng tư [bằng cách đổi tên nó thành atomic_rating_change6] 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ức

Nhờ phương thức ma thuật atomic_rating_change7 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_change5 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 decorator

Như 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[]
3

Là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àm

Do đó, 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 ta

Lư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ên

Tổ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à
    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
  • 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 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àm
  • complex_calculation took 0.5041 secs
    42
    9 là hàm được trả về bởi
    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 và là công cụ trang trí thực tế của chúng ta
  • 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 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]

Đó là định nghĩa của trang trí của chúng tôi

  • complex_calculation took 0.5041 secs
    42
    3 được tô điểm bằng một lệnh gọi hàm lần này, tôi. e.
    @timer
    class MyClass:
        def complex_calculation[self]:
            time.sleep[1]
            return 42
    
    my_obj = MyClass[]
    my_obj.complex_calculation[]
    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
    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à nó trả về trình trang trí thực tế
  • complex_calculation took 0.5041 secs
    42
    3 cuối cùng được trang trí bởi
    complex_calculation took 0.5041 secs
    42
    9 vì chức năng này là kết quả của cuộc gọi
    @timer
    class MyClass:
        def complex_calculation[self]:
            time.sleep[1]
            return 42
    
    my_obj = MyClass[]
    my_obj.complex_calculation[]
    4

Đâ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ước

Finished 'MyClass' in 0.0000 secs
0

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
0

Tuy 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

  • Finished 'MyClass' in 0.0000 secs
    8
  • Finished 'MyClass' in 0.0000 secs
    9
  • 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[]
    0
  • 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[]
    1
  • 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[]
    2

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 đầu

Không có

Finished 'MyClass' in 0.0000 secs
2

Với

Finished 'MyClass' in 0.0000 secs
2

Trang 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
0

Cá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

  • Called 1 times
    Called 2 times
    Called 3 times
    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
    4
  • Called 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ủa
    Called 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 DoubleUnderscore
    Called 1 times
    Called 2 times
    Called 3 times
    5 chịu trách nhiệm cho việc này

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

  • Called 1 times
    Called 2 times
    Called 3 times
    0 đang gọi người trang trí trước
  • 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[]]
    0 vá phương thức
    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[]]
    1 cho lớp
  • 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át

sử 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án

phươ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[]
0

Lư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ôi

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[]
1

Bâ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ần

lớ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à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[]
2

Tuy 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ại

Hãy xem nó trông như thế nào

Có hai điều rút ra ở đây

  1. trang trí có thể được lồng vào nhau. Thứ tự xuất hiện của họ là quan trọng
  2. người trang trí
    class C:
        @staticmethod
        def the_static_method[arg1, arg2]:
            return 42
    
    print[C.the_static_method[]]
    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ôi

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

Người trang trí có thể tự truy cập không?

Nếu bạn muốn truy cập self [ví dụ nơi trình trang trí được gọi từ đó] bạn có thể sử dụng args[0] bên trong trình trang trí .

Lớp học có thể được trang trí bằng Python không?

Trình trang trí lớp Python thêm một lớp vào một hàm và có thể đạt được điều đó mà không cần sửa đổi mã nguồn. Ví dụ: một hàm có thể được trang trí bằng lớp có thể chấp nhận đối số hoặc lớp không chấp nhận đối số . Tôi đã đơn giản hóa các ví dụ được trình bày để dễ hiểu hơn.

Trình trang trí @classmethod trong Python là gì?

Trong Python, trình trang trí @classmethod được dùng để khai báo một phương thức trong lớp dưới dạng một phương thức lớp có thể được gọi bằng Tên lớp. Tên phương thức[] . Phương thức lớp cũng có thể được gọi bằng cách sử dụng một đối tượng của lớp. @classmethod là một thay thế của hàm classmethod[].

Chúng ta có thể tạo trình trang trí của riêng mình bằng Python không?

Để tạo một hàm trang trí trong Python, tôi tạo một hàm bên ngoài lấy một hàm làm đối số . Ngoài ra còn có một chức năng bên trong bao bọc xung quanh chức năng được trang trí. Để sử dụng một trình trang trí, bạn đính kèm nó vào một chức năng như bạn thấy trong mã bên dưới.

Chủ Đề