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

2022-09-23  本文已影响0人  BlueSocks

让我们了解一下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

所以让我们分解一下;

  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.

总结再次发生的事情;

使用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 模块将在另一篇文章中介绍。感谢阅读,阿里加托。✌️

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

上一篇下一篇

猜你喜欢

热点阅读