美文网首页
在 10 分钟内学习 Python Decorators从基础到

在 10 分钟内学习 Python Decorators从基础到

作者: BlueSocks | 来源:发表于2022-09-23 16:01 被阅读0次

    让我们了解一下python装饰器。这是我喜欢在 python 中使用的模式之一,它被高级程序员广泛使用。

    什么是Decorators🤔

    python中的Decorators是一种设计模式,它允许我们在不修改函数本身的情况下扩展函数的功能。我们可以通过用另一个函数包装我们想要扩展功能的函数来做到这一点。它一点也不复杂,这是一个非常简单的模式。在底层,我们只是将一个函数作为参数传递给另一个函数,该函数将返回一个函数,我们可以调用它来调用最初作为参数传递的函数。

    Decorators背后的模式

    在python中,我们可以嵌套函数。内部函数可以访问封闭函数的外部范围。这意味着如果我们在封闭的函数中定义一个变量,内部函数可以访问它。这种模式称为闭包。这个概念很重要,它是python Decorators的核心概念。这个概念也用于其他语言,包括 JavaScript、Golang 等。

    Decorators编码

    让我们深入研究一些 Python 代码,这些代码将帮助我们理解上述所有理论和行话。

    Decorators功能

    假设我们有一个calculate_number返回数字的函数5,但随后出现了一些要求,我们需要将该函数的输出增加1,现在我们可以创建另一个函数increase_number来帮助我们做到这一点。这只是一个示例,我们将在教程结束时介绍复杂的用例。

    我们有返回数字的函数

    def calculate_number():
        return 5
    
    

    我们这里还有一个函数,我们关注一下这个函数

    # Decorator to increase function output by one
    def increase_number(func):
    
        def wrapper():
            """
            Calls the function passed as an argument in the outer
            scope and adds one to the output
            """
            return func() + 1
    
        return wrapper
    
    

    所以让我们分解一下;

    • 看看increase_number功能,

      def increase_number(func):
      
        def wrapper():
            ...
      
        return wrapper
      
      

      我们需要将一个函数作为参数传递给这个函数。increase_number在被调用的函数中定义了一个函数wrapper,我们可以将其命名为任何名称,并且函数在不调用的情况下返回。这意味着如果increase_number调用函数,它将返回一个函数作为响应,wrapper也可以调用它。

      # Pass `calculate_number` as an argument
      var_a = increase_number(calculate_number)
      # Call the function returned above
      inner_function_response = var_a()
      
      

      var_a从上面将包含wrapper等待调用的函数。然后它被调用并将其返回值分配给变量inner_function_response

    • 在上面的嵌套函数wrapper中,我们调用了作为参数传递给封闭函数的函数,并添加1到它并返回答案,

      def wrapper():
        """
        Calls the function passed as an argument in the outer
        scope and adds one to the output
        """
        return func() + 1
      
      

      我们可以调用作为参数传递的函数,因为它在封闭函数的范围内,而嵌套函数作为对外部范围的访问。这个概念称为闭包。我们也可以在调用函数之前和调用函数之后做一些事情。另外,在定义函数之前和定义函数之后做一些事情wrapper。试试下面的例子;

      def outer_function(func):
    
          print('Before defining inner function')
    
          def inner_function():
    
              print('Do something before calling function')
    
              func()  # Call function passed as argument in outer function
    
              print('Do something after calling function')
    
          print('Do something before returning the inner function')
    
          return inner_function
    
      def sample():
          print('I was decorated')
    
      outer_function(sample)()
    
    

    输出

    Before defining inner function
    Do something before returning the inner function
    Do something before calling function
    I was decorated
    Do something after calling function
    
    

    完整代码:增加数字Decorators

    def increase_number(func):
    
        def wrapper():
            return func() + 1
    
        return wrapper
    
    def calculate_number():
        return 5
    
    var_a = increase_number(calculate_number)
    inner_function_response = var_a()
    print(inner_function_response)
    
    

    运行上面的代码应该输出6.

    总结再次发生的事情;

    • 函数calculate_number返回 5,
    • 该函数increase_number是一个Decorators,它接收一个函数并用另一个函数包装它,然后返回包装器,允许我们操纵作为参数传递的函数的返回值,该参数将成为 function calculate_number
    • wrapper函数内部的嵌套函数 ,increase_number调用传递给 function 的函数increase_number并将返回值加 1,然后返回答案。
    • 嵌套函数 ,wrapper由函数 返回increase_number,因此我们可以用它做一些事情,大多数时候我们最终只是像普通函数一样调用它,就像我们在这里所做的那样var_a。我说大部分时间是因为我们也可以将变量中返回的函数var_a作为参数传递给另一个装饰器函数。

    使用Decorators的 Pythonic 语法

    Decorators在 python 中被大量使用,甚至还有内置的Decorators,如 staticmethod、classmethod、property 等。因此,您可以使用 Pythonic 语法来装饰函数。假设我们想calculate_number使用当前的方式将多个Decorators应用到上面代码中的函数,当代码中存在错误或只是阅读代码时,对于您或其他人来说理解代码太复杂了。

    Pythonic 代码:增加数量

    def increase_number(func):
    
        def wrapper():
            return func() + 1
    
        return wrapper
    
    @increase_number
    def calculate_number():
        return 5
    
    print(calculate_number())
    
    

    这很漂亮,我喜欢这种语法。这里发生的事情很简单,我们calculate_number用函数 来装饰函数,方法increase_number是把它放在前面calculate_number@符号的函数的正上方。这告诉python这是一个装饰器,所以当这个函数calculate_number被调用时,它会自动将函数传递给Decorators并调用包装函数。所以现在我们可以调用函数本身,它会被装饰。现在我们可以轻松地将多个Decorators添加到一个函数中,而不会使它变得太复杂而难以理解。

    多个Decorators

    多个Decorators可以应用于单个函数,只需使用Decorators语法将Decorators堆叠在一起即可@decorator_function

    def split_string(func):
        def inner():
            return func().split()
        return inner
    
    def uppercase_string(func):
        def inner():
            return func().upper()
        return inner
    
    @split_string
    @uppercase_string
    def speak_python():
        return "I love speaking Python"
    
    print(speak_python())
    
    

    输出

    ['I', 'LOVE', 'SPEAKING', 'PYTHON']
    
    

    你可能会想到的第一个问题是,这个装饰器在调用时应用到这个函数的顺序是什么?答案是从下往上,uppercase_string然后split_string。所以从上面的输出中你可以看出字符串首先被转换为大写,然后它被拆分返回一个大写字符串的列表。

    你也可以测试一下,尝试切换Decorators的位置,@uppercase_string放在@split_string.

    代码

    @uppercase_string
    @split_string
    def speak_python():
        return "I love speaking Python"
    
    

    输出

    ...
    AttributeError: 'list' object has no attribute 'upper'
    
    

    从上面的输出中,有一个错误,因为@uppercase_string Decorators试图调用upper列表上的方法。发生这种情况是因为@split_stringDecorators拆分字符串首先返回一个字符串列表,然后 @uppercase_string Decorators尝试upper在该列表上应用该方法,这是不可能的,因为 python 列表没有upper内置方法。

    装饰接受参数的函数

    如果我们的函数接受参数,不用担心,我们只需要更新我们的Decorators以接受参数并在调用它时将它们传递给装饰函数。让我们修改speak_python上面的函数,接受一种语言作为参数,用字符串格式化,然后返回。

    错误代码:

    @split_string
    @uppercase_string
    def speak_language(language):
        return f"I love speaking {language}"
    
    print(speak_language("Python"))
    
    

    输出:

    ...
    TypeError: inner() takes 0 positional arguments but 1 was given
    
    

    我们收到错误是因为参数Python被传递给Decorators的内部函数,但这些内部函数不接受任何参数、位置参数或关键字参数。所以我们必须更新Decorators以确保内部函数接受参数。

    好代码:

    def split_string(func):
        def inner(a):
            return func(a).split()
        return inner
    
    def uppercase_string(func):
        def inner(a):
            return func(a).upper()
        return inner
    
    @split_string
    @uppercase_string
    def speak_language(language):
        return f"I love speaking {language}"
    
    print(speak_language("Python"))
    
    

    输出:

    ['I', 'LOVE', 'SPEAKING', 'PYTHON']
    
    

    现在它正在工作,发生了什么;

    • 内部函数现在接受一个参数,
    • 然后将参数传递给装饰函数。

    通用Decorators

    我们还可以创建一个Decorators,它接受任意数量的参数、位置参数和关键字参数,并将它们传递给装饰函数。假设我们更新了speak_language函数以接受更多参数,我们将不得不更新所有Decorators以接受这些参数。想象一下,我们有一个包含很多Decorators的复杂代码库,很难更新所有Decorators。所以我们应该确保我们的Decorators将来可以处理多个参数。

    最佳代码:

    def split_string(func):
        def inner(*args, **kwargs):
            return func(*args, **kwargs).split()
        return inner
    
    def uppercase_string(func):
        def inner(*args, **kwargs):
            return func(*args, **kwargs).upper()
        return inner
    
    @split_string
    @uppercase_string
    def speak_language(word, language):
        return f"I {word} speaking {language}"
    
    print(speak_language("hate", "Python"))
    
    

    输出:

    ['I', 'HATE', 'SPEAKING', 'PYTHON']
    
    

    *args 和 ** kwargs 允许您将多个参数或关键字参数传递给函数。所以我们在内部函数中使用它来接受任意数量的参数,并将它们全部传递给装饰函数。现在无论speak_language将来向函数传递多少参数,我们都不需要更新装饰器。少了一个需要考虑的问题。😁

    Decorator Factory

    让我们回到我们的 increase_number 函数代码:

    def increase_number(func):
    
        def wrapper():
            return func() + 1
    
        return wrapper
    
    @increase_number
    def calculate_number():
        return 5
    
    print(calculate_number())
    
    

    这个Decorators只增加这个数字1,如果我们想应用增加 10 怎么办?我们可以编辑Decorators将其增加 10。如果我们有另一个函数需要Decorators将其增加 5,我们可以创建另一个类似的Decorators increase_number将其增加 5。这不好,因为我们在重复代码,我们应该尽量不要在任何可能的地方重复代码,因为如果代码中存在错误,我们将不得不更改所有已复制代码的地方。

    Decorators也可以。通过对闭包的理解,我们知道嵌套函数可以访问外部函数的作用域。然后我们可以创建一个返回Decorators的函数。这样做意味着如果我们将参数传递给创建Decorators的函数,Decorators将可以访问传递给其外部函数的参数,并且Decorators的内部函数也应该可以访问这些参数。让我们看一下下面的代码。

    代码:

    def A(arg1):
        def B():
            def C():
                def D():
                    def E():
                        print(arg1)
                    return E
                return D
            return C
        return B
    
    A("Good Python")()()()()
    
    

    输出:

    Good Python
    
    

    我认为通过这段代码,我已经公平地解释了嵌套函数总是可以访问外部函数作用域的事实。FunctionE嵌套了四层,但仍然可以访问 function 的范围A

    这与Decorators工厂背后的想法相同,这是一个返回Decorators的函数。因此,为了使应用的增加动态化,我们可以创建一个函数,该函数接受要增加的数字作为参数,然后返回一个可以访问该数字的Decorators。代码:

    def apply_increase(increase):
    
        def increase_number(func):
    
            def wrapper():
                return func() + increase
    
            return wrapper
    
        return increase_number
    
    @apply_increase(10)
    def calculate_number():
        return 5
    
    @apply_increase(1)
    def myother_number():
        return 1
    
    print(calculate_number())
    print(myother_number())
    
    

    输出:

    15
    2
    
    

    apply_increase函数接受数字作为参数,然后返回Decorators。由于上述每个函数返回的Decorators函数都可以访问其外部函数的范围,因此我们得到了唯一的Decorators,因为apply_increase(10)apply_increase(1)中的数字不同。对于返回的两个Decorators,它们的increase论点会有所不同。

    结论👍

    Decorators是python中一个非常神奇的模式,用它我们可以扩展被包装函数的功能。我们在本文中介绍了很多内容,但还有其他内容我们没有介绍。类装饰器、方法装饰器和 python functools 模块将在另一篇文章中介绍。感谢阅读,阿里加托。✌️

    如果你喜欢这篇文章,你可以给这篇文章点赞,如果你有任何问题或看法,可以在下方评论,并关注我获取更多关于软件编程的更新。

    相关文章

      网友评论

          本文标题:在 10 分钟内学习 Python Decorators从基础到

          本文链接:https://www.haomeiwen.com/subject/rwenortx.html