个性化阅读
专注于IT技术分析

Python中的装饰器

如果你想了解函数, 请参加srcmini的Python数据科学工具箱(第1部分)课程。

装饰器是Python中的一种设计模式, 允许用户在不修改其结构的情况下向现有对象添加新功能。装饰器通常在要装饰的函数的定义之前调用。在本教程中, 我们将向读者展示他们如何在Python函数中使用装饰器。

Python中的函数是一等公民。这意味着它们支持诸如作为参数传递, 从函数返回, 修改并分配给变量的操作。这是在我们深入研究创建Python装饰器之前要理解的基本概念。

将函数分配给变量

为使我们开始, 我们创建了一个函数, 该函数将在调用数字时将其加1。然后, 我们将函数分配给一个变量, 并使用此变量来调用该函数。

def plus_one(number):
    return number + 1

add_one = plus_one
add_one(5)
6

在其他函数中定义函数

接下来, 我们将说明如何在Python中的另一个函数内定义一个函数。待在我身边, 我们很快就会发现这一切与在Python中创建和理解装饰器有何关系。

def plus_one(number):
    def add_one(number):
        return number + 1


    result = add_one(number)
    return result
plus_one(4)
5

将函数作为参数传递给其他函数

函数也可以作为参数传递给其他函数。让我们在下面说明。

def plus_one(number):
    return number + 1

def function_call(function):
    number_to_add = 5
    return function(number_to_add)

function_call(plus_one)
6

返回其他函数的函数

一个函数还可以生成另一个函数。我们将在下面通过示例展示。

def hello_function():
    def say_hi():
        return "Hi"
    return say_hi
hello = hello_function()
hello()
'Hi'

嵌套函数可以访问封闭函数的变量范围

Python允许嵌套函数访问封闭函数的外部范围。这是装饰器中的关键概念-这种模式称为”关闭”。

def print_message(message):
    "Enclosong Function"
    def message_sender():
        "Nested Function"
        print(message)

    message_sender()

print_message("Some random message")
Some random message

创建装饰器

有了这些先决条件, 让我们继续创建一个简单的装饰器, 它将一个句子转换为大写。我们通过在一个封闭函数内定义一个包装器来实现。如你所见, 它与我们之前创建的另一个函数内部的函数非常相似。

def uppercase_decorator(function):
    def wrapper():
        func = function()
        make_uppercase = func.upper()
        return make_uppercase

    return wrapper

我们的装饰器函数接受一个函数作为参数, 因此, 我们将定义一个函数并将其传递给我们的装饰器。我们早先了解到我们可以将函数分配给变量。我们将使用该技巧来调用装饰器函数。

def say_hi():
    return 'hello there'

decorate = uppercase_decorator(say_hi)
decorate()
'HELLO THERE'

但是, Python为我们提供了应用装饰器的简便得多的方法。我们只需在要装饰的函数之前使用@符号。让我们在下面的实践中证明这一点。

@uppercase_decorator
def say_hi():
    return 'hello there'

say_hi()
'HELLO THERE'

将多个装饰器应用于单个功能

我们可以对单个函数使用多个装饰器。但是, 装饰器将按照我们称为装饰器的顺序进行应用。在下面, 我们将定义另一个装饰器, 该装饰器将句子分成列表。然后, 我们将uppercase_decorator和split_string装饰器应用于单个函数。

def split_string(function):
    def wrapper():
        func = function()
        splitted_string = func.split()
        return splitted_string

    return wrapper
@split_string
@uppercase_decorator
def say_hi():
    return 'hello there'
say_hi()
['HELLO', 'THERE']

从上面的输出中, 我们注意到装饰器的应用是自下而上的。如果我们交换了订单, 由于列表没有上层属性, 我们将看到一个错误。该句子首先被转换为大写, 然后被拆分为一个列表。

接受装饰器函数中的参数

有时我们可能需要定义一个接受参数的装饰器。我们通过将参数传递给包装函数来实现这一点。然后, 参数将传递给调用时正在修饰的函数。

def decorator_with_arguments(function):
    def wrapper_accepting_arguments(arg1, arg2):
        print("My arguments are: {0}, {1}".format(arg1, arg2))
        function(arg1, arg2)
    return wrapper_accepting_arguments


@decorator_with_arguments
def cities(city_one, city_two):
    print("Cities I love are {0} and {1}".format(city_one, city_two))

cities("Nairobi", "Accra")
My arguments are: Nairobi, Accra
Cities I love are Nairobi and Accra

定义通用装饰器

为了定义可以应用于任何功能的通用装饰器, 我们使用args和** kwargs。 args和** kwargs收集所有位置和关键字参数, 并将它们存储在args和kwargs变量中。 args和kwargs允许我们在函数调用期间传递尽可能多的参数。

def a_decorator_passing_arbitrary_arguments(function_to_decorate):
    def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):
        print('The positional arguments are', args)
        print('The keyword arguments are', kwargs)
        function_to_decorate(*args)
    return a_wrapper_accepting_arbitrary_arguments

@a_decorator_passing_arbitrary_arguments
def function_with_no_argument():
    print("No arguments here.")

function_with_no_argument()
The positional arguments are ()
The keyword arguments are {}
No arguments here.

让我们看看如何使用带有位置参数的装饰器。

@a_decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
    print(a, b, c)

function_with_arguments(1, 2, 3)
The positional arguments are (1, 2, 3)
The keyword arguments are {}
1 2 3

关键字参数是使用关键字传递的。如下所示。

@a_decorator_passing_arbitrary_arguments
def function_with_keyword_arguments():
    print("This has shown keyword arguments")

function_with_keyword_arguments(first_name="Derrick", last_name="Mwiti")
The positional arguments are ()
The keyword arguments are {'first_name': 'Derrick', 'last_name': 'Mwiti'}
This has shown keyword arguments

将参数传递给装饰器

现在, 让我们看看如何将参数传递给装饰器本身。为了实现这一点, 我们定义了一个装饰器制造商, 该制造商接受参数, 然后在其中定义一个装饰器。然后, 我们像之前所做的那样在装饰器中定义包装函数。

def decorator_maker_with_arguments(decorator_arg1, decorator_arg2, decorator_arg3):
    def decorator(func):
        def wrapper(function_arg1, function_arg2, function_arg3) :
            "This is the wrapper function"
            print("The wrapper can access all the variables\n"
                  "\t- from the decorator maker: {0} {1} {2}\n"
                  "\t- from the function call: {3} {4} {5}\n"
                  "and pass them to the decorated function"
                  .format(decorator_arg1, decorator_arg2, decorator_arg3, function_arg1, function_arg2, function_arg3))
            return func(function_arg1, function_arg2, function_arg3)

        return wrapper

    return decorator

pandas = "Pandas"
@decorator_maker_with_arguments(pandas, "Numpy", "Scikit-learn")
def decorated_function_with_arguments(function_arg1, function_arg2, function_arg3):
    print("This is the decorated function and it only knows about its arguments: {0}"
           " {1}" " {2}".format(function_arg1, function_arg2, function_arg3))

decorated_function_with_arguments(pandas, "Science", "Tools")
The wrapper can access all the variables
    - from the decorator maker: Pandas Numpy Scikit-learn
    - from the function call: Pandas Science Tools
and pass them to the decorated function
This is the decorated function, and it only knows about its arguments: Pandas Science Tools

调试装饰器

正如我们已经注意到的, 装饰器包装函数。原始函数名称, 它的文档字符串和参数列表都被包装封包隐藏:例如, 当我们尝试访问decorated_function_with_arguments元数据时, 我们将看到包装封包的元数据。这在调试时提出了挑战。

decorated_function_with_arguments.__name__
'wrapper'
decorated_function_with_arguments.__doc__
'This is the wrapper function'

为了解决这一难题, Python提供了functools.wraps装饰器。该装饰器将丢失的元数据从未装饰的函数复制到装饰的闭包中。让我们展示一下如何做到这一点。

import functools

def uppercase_decorator(func):
    @functools.wraps(func)
    def wrapper():
        return func().upper()
    return wrapper
@uppercase_decorator
def say_hi():
    "This will say hi"
    return 'hello there'

say_hi()
'HELLO THERE'

当我们检查say_hi元数据时, 我们注意到它现在是在指功能的元数据, 而不是包装器的元数据。

say_hi.__name__
'say_hi'
say_hi.__doc__
'This will say hi'

在定义装饰器时始终使用functools.wraps是明智的做法, 也是一种好的做法。这将使你免于调试的很多麻烦。

Python装饰器摘要

装饰器可以动态更改功能, 方法或类的功能, 而不必直接使用子类或更改要修饰的功能的源代码。在Python中使用装饰器还可以确保你的代码是DRY(请勿重复自己)。装饰器有几种用例, 例如:

  • Python框架(例如Flask和Django)中的授权
  • 记录中
  • 测量执行时间
  • 同步化

要了解有关Python装饰器的更多信息, 请查看Python的装饰器库。

赞(0)
未经允许不得转载:srcmini » Python中的装饰器

评论 抢沙发

评论前必须登录!