装饰器python日志功能_python装饰器5个高级功能

装饰器python日志功能

Decorators are functions that modify other functions’ behaviors without changing their core operations. As indicated by the name, decorators only decorate other functions. You can think of other functions as plain donuts, and what decorators do is apply different coatings to the donuts. No matter what flavor you have (the decorators), donuts (the decorated functions) are still donuts.

装饰器是在不更改其核心操作的情况下修改其他功能的行为的功能。 如名称所示,装饰器仅装饰其他功能。 您可以将其他功能视为普通的甜甜圈,而装饰员的工作是在甜甜圈上施加不同的涂层。 无论您有什么味道(装饰器),甜甜圈(装饰功能)仍然是甜甜圈。

The following code shows you a basic decorator that logs the elapsed time of a function call. In essence, the decorator function accepts another function (i.e. the to-be-decorated function) as its input argument. It defines an inner function that actually provides decoration activities and returns the inner function as the output. To use the decorator, you simply place the decorator function name with an @ sign prefix above the function that you want to decorate with the decorator function.

以下代码显示了一个基本的装饰器,该装饰器记录了函数调用的经过时间。 本质上,装饰器函数接受另一个函数(即待装饰函数)作为其输入参数。 它定义了一个内部函数,该函数实际上提供装饰活动并返回内部函数作为输出。 要使用装饰器,只需将装饰器函数名称放在要使用装饰器函数装饰的函数上方的@符号前缀。

>>> import time
... def logging_time(func):
...     """Decorator that logs time"""
...     def logger():
...         """Function that logs time"""
...         start = time.time()
...         func()
...         print(f"Calling {func.__name__}: {time.time() - start:.5f}")
... 
...     return logger
... 
... @logging_time
... def calculate_sum():
...     return sum(range(10000))
... 
... calculate_sum()
... 
Calling calculate_sum: 0.00018

Now that you have a good understanding of the most basic form of decorators, it’s time to gain some more in-depth knowledge about them.

现在,您已经对装饰器的最基本形式有了很好的了解,是时候获得有关装饰器的一些更深入的知识了。

1.支持不同的功能签名 (1. Support Different Function Signatures)

There is a problem with the code snippet above: It assumes that the decorated functions don’t require any input arguments (Line 7). If we use the decorator in its current form with a function that takes an argument, it won’t work as you may expect:

上面的代码段存在问题:假定修饰的函数不需要任何输入参数(第7行)。 如果我们将装饰器以其当前形式与带有参数的函数一起使用,则它将无法正常工作:

>>> @logging_time
... def calculate_sum_n(n):
...     return sum(range(n))
... 
... calculate_sum_n(10000)
... 
Traceback (most recent call last):
  File "<input>", line 5, in <module>
TypeError: logger() takes 0 positional arguments but 1 was given

To address this issue, we should consider using *args and **kwargs with the decorator definition. These two terms are used to denote an undetermined (zero to more) number of positional and keyword arguments in functions. In other words, they can capture all kinds of function signatures. Let’s see the modified version and its improved compatibility:

为了解决这个问题,我们应该考虑将*args**kwargs与装饰器定义一起使用。 这两个术语用于表示函数中位置和关键字参数的数量不确定(从零到更多)。 换句话说,它们可以捕获各种功能签名。 让我们看看修改后的版本及其改进的兼容性:

>>> import time
... def logging_time(func):
...     """Decorator that logs time"""
...     def logger(*args, **kwargs):
...         """Function that logs time"""
...         start = time.time()
...         func(*args, **kwargs)
...         print(f"Calling {func.__name__}: {time.time() - start:.5f}")
... 
...     return logger
... 
... @logging_time
... def calculate_sum_n(n):
...     return sum(range(n))
... 
... @logging_time
... def say_hi(whom, greeting="Hello"):
...     print(f"{greeting}, {whom}!")
... 
... calculate_sum_n(100000)
... say_hi("John", greeting="Hi")
... 
Calling calculate_sum_n: 0.00187
Hi, John!
Calling say_hi: 0.00001

As you can see, the biggest change is that instead of assuming the function takes no arguments, the revised version supplies the function call with *args and **kwargs such that the decorator is more versatile now.

如您所见,最大的变化是,修订版提供了带有*args**kwargs的函数调用,而不是假定函数不带参数,从而使装饰器现在更加通用。

2.包装装饰功能 (2. Wrap the Decorated Function)

Some people may not know that the decoration will by default mess up the metadata of the decorated function, such as docstrings. Let’s look at the behavior of the current decorator:

某些人可能不知道默认情况下,修饰会弄乱修饰函数的元数据,例如docstrings。 让我们看一下当前装饰器的行为:

>>> @logging_time
... def say_hello(whom):
...     """Greet someone"""
...     print(f"Hello, {whom}!")
... 
... print(say_hello.__doc__)
... 
Function that logs time

As you can see, the docstrings show the inner function that we defined in the decorator function but not the decorated function say_hello. Under the hood, it’s all because the decoration process is to create a closure from the decorator function. In essence, the process of decoration is equivalent to calling say_hello = logging_time(say_hello). Thus, it’s not surprising that you’re getting the inner function’s docstrings with the decorated function.

如您所见,文档字符串显示了我们在装饰器函数中定义的内部函数,但没有装饰的函数say_hello 。 在幕后,这全是因为装饰过程是从装饰器函数创建一个闭合。 本质上,修饰的过程等效于调用say_hello = logging_time(say_hello) 。 因此,获得带有修饰函数的内部函数的文档字符串也就不足为奇了。

To solve this problem, we can use another decorator function (wraps) that is shipped in the standard Python library, as shown below:

为了解决这个问题,我们可以使用标准Python库中附带的另一个装饰器函数( wraps ),如下所示:

>>> import time
... from functools import wraps
... 
... def logging_time(func):
...     """Decorator that logs time"""
...     @wraps(func)
...     def logger(*args, **kwargs):
...         """Function that logs time"""
...         start = time.time()
...         func(*args, **kwargs)
...         print(f"Calling {func.__name__}: {time.time() - start:.5f}")
... 
...     return logger
... 
... 
... @logging_time
... def say_hello(whom):
...     """Greet someone"""
...     print(f"Hello, {whom}!")
... 
... print(say_hello.__doc__)
... 
Greet someone
  • Line 2: We import the wraps decorator function from the functools module.

    第2行:我们从functools模块导入wraps装饰器函数。

  • Line 6: We use the wraps decorator to decorate the inner function by wrapping the to-be-decorated function (the func argument).

    第6行:我们使用wraps装饰器通过包装要装饰的函数( func参数)来装饰内部函数。

  • Lines 21-23: The decorated function now has the correct docstrings.

    第21-23行:装饰的函数现在具有正确的文档字符串。

Besides the benefits of passing the expected docstrings for the decorated functions, the wraps decorator is necessary to have the decorated function show correct function annotations (e.g. argument types) and support pickling for data preservation.

除了为修饰的函数传递预期的文档字符串的好处外, wraps修饰器对于使修饰的函数显示正确的函数注释(例如,参数类型)并支持数据保存的酸洗也是必需的。

3.用参数定义装饰器 (3. Define Decorators With Arguments)

So far, our decorators have their decoration functionalities fixed. What if we want our decorators to behave differently based on the user’s preferences? In this case, we can consider defining decorators that accept arguments. Let’s continue with the decorator example of logging the elapsed time of functions. Suppose a trivial business need: Our decorators display the time in the unit that’s specified by the user (either in milliseconds or seconds). The following code shows you a possible solution:

到目前为止,我们的装饰器的装饰功能已固定。 如果我们希望装饰器根据用户的喜好行为有所不同怎么办? 在这种情况下,我们可以考虑定义接受参数的装饰器。 让我们继续装饰器示例,该示例记录函数的经过时间。 假设有一个琐碎的业务需求:我们的装饰器以用户指定的单位显示时间(以毫秒或秒为单位)。 以下代码为您显示了可能的解决方案:

>>> import time
... from functools import wraps
... 
... def logging_time(unit):
...     """Decorator that logs time"""
...     def logger(func):
...         @wraps(func)
...         def inner_logger(*args, **kwargs):
...             """Function that logs time"""
...             start = time.time()
...             func(*args, **kwargs)
...             scaling = 1000 if unit == "ms" else 1
...             print(f"Calling {func.__name__}: {(time.time() - start) * scaling:.5f} {unit}")
... 
...         return inner_logger
... 
...     return logger
...

As you can see, to allow the decorator to accept the unit parameter, we need to create another layer outside the decorator that we defined earlier. Let’s see whether it works as we’re expecting:

如您所见,要允许装饰器接受unit参数,我们需要在我们之前定义的装饰器之外创建另一个层。 让我们看看它是否如我们所期望的那样工作:

>>> @logging_time("ms")
... def calculate_sum_ms(n):
...     """Calculate sum of 0 to n-1"""
...     return sum(range(n))
... 
... @logging_time("s")
... def calculate_sum_s(n):
...     """Calculate sum of 0 to n-1"""
...     return sum(range(n))
... 
>>> calculate_sum_ms(100000)
Calling calculate_sum_ms: 1.87230 ms
>>> calculate_sum_s(100000)
Calling calculate_sum_s: 0.00191 s
  • We use two different settings for the decorator and both work as expected.

    我们为装饰器使用两种不同的设置,并且两种设置均可按预期工作。
  • The reason for adding another layer to get the decorator to accept arguments is that the decoration process is chaining the function call. Calling logging_time(“ms”) would allow us to get the logger function, which has exactly the same function signature as the decorator function that we defined earlier.

    添加另一层以使装饰器接受参数的原因是,装饰过程链接了函数调用。 调用logging_time(“ms”)将允许我们获取logger函数,该函数具有与我们先前定义的装饰器函数完全相同的函数签名。

Please note that the current definition of the decorators requires that we specify the unit for the decoration. If you want to make your arguments optional, it needs extra work. You can find the pertinent discussion in my earlier article on this topic.

请注意,当前装饰器的定义要求我们指定装饰的单位。 如果要将参数设为可选,则需要额外的工作。 您可以在我之前关于该主题的文章中找到相关的讨论。

4.多个装饰器 (4. Multiple Decorators)

The examples above have only used one decorator to decorate other functions. However, it’s possible to use multiple decorators to decorate functions at the same time. To do that, we can simply stack the decorators above the to-be-decorated function. The following code snippet shows you a trivial example:

上面的示例仅使用一个装饰器来装饰其他功能。 但是,可以同时使用多个装饰器来装饰功能。 为此,我们可以简单地将装饰器堆叠在要装饰的函数上方。 以下代码段显示了一个简单的示例:

>>> def repeat(func):
...     """Decorator that repeats function call twice"""
...     def repeater(*args, **kwargs):
...         func(*args, **kwargs)
...         func(*args, **kwargs)
... 
...     return repeater
... 
... 
... @logging_time("ms")
... @repeat
... def say_hi(whom):
...     print(f"Hi, {whom}!")
... 
... 
... @repeat
... @logging_time("ms")
... def say_hello(whom):
...     print(f"Hello, {whom}!")
...

The code above shows you that we created another decorator that simply calls the decorated function twice. Notably, we defined two functions that are decorated by two decorators. However, we applied the decorators in a different order, which will cause distinct effects:

上面的代码向您展示了我们创建了另一个装饰器,该装饰器简单地调用了装饰函数两次。 值得注意的是,我们定义了两个由两个装饰器装饰的功能。 但是,我们以不同的顺序应用装饰器,这将导致不同的效果:

>>> say_hi("John")
Hi, John!
Hi, John!
Calling repeater: 0.02098 ms
>>> say_hello("Aaron")
Hello, Aaron!
Calling say_hello: 0.06032 ms
Hello, Aaron!
Calling say_hello: 0.00405 ms
  • We notice that the say_hi function gets called twice and the time is only logged once. Meanwhile, the say_hello function gets called twice and the time is also logged twice.

    我们注意到, say_hi函数被调用两次,并且时间仅记录一次。 同时, say_hello函数被调用两次,时间也被记录两次。

  • When we have multiple decorators, the order of applying the decorators is based on proximity. In other words, the one that is right above the decorated function is to exert decoration first and so on. This is why the say_hi function’s time gets logged once — because the logging_time decorator is applied last. By contrast, the repeat decorator applies to the function that has already been decorated by logging_time, and thus the time for the say_hello function is logged twice.

    当我们有多个装饰器时,应用装饰器的顺序基于邻近度。 换句话说,恰好在装饰功能之上的是首先进行装饰,依此类推。 这就是为什么say_hi函数的时间被记录一次的原因-因为logging_time装饰器是最后应用的。 相比之下,重复装饰器适用于已经由logging_time装饰的函数,因此say_hello函数的时间记录了两次。

5.基于类的装饰器 (5. Class-Based Decorators)

We have been saying that decorators are functions. To be precise, these are higher-order functions, which means that these functions use other functions as input and/or output arguments. However, do you know that decorators can be implemented as a class? With the possibility of having decorators as classes, we should say that decorators are callables. In a previous article, I introduced callables. Please feel free to take a look at it if you don’t know about callables.

我们一直在说装饰器是功能。 确切地说,它们是高阶函数,这意味着这些函数将其他函数用作输入和/或输出参数。 但是,您知道装饰器可以作为一个类来实现吗? 在将装饰器作为类的可能性下,我们应该说装饰器是可调用的。 在上一篇文章中 ,我介绍了可调用对象。 如果您不了解可调用对象,请随时进行查看。

The following code shows you how we can define a decorator using a class:

以下代码向您展示了如何使用类来定义装饰器:

>>> class Repeat:
...     def __init__(self, n):
...         self.n = n
... 
...     def __call__(self, func):
...         def repeater(*args, **kwargs):
...             for _ in range(self.n):
...                 func(*args, **kwargs)
... 
...         return repeater
... 
... @Repeat(n=2)
... def morning_greet(person):
...     print(f"Good Morning, {person}!")
... 
... @Repeat(n=3)
... def afternoon_greet(person):
...     print(f"Good Afternoon, {person}!")
... 
... morning_greet("Jason")
... afternoon_greet("Kelly")
... 
Good Morning, Jason!
Good Morning, Jason!
Good Afternoon, Kelly!
Good Afternoon, Kelly!
Good Afternoon, Kelly!
  • The example shows you not the most basic form of decorators but decorators that can take arguments.

    该示例显示的不是装饰器的最基本形式,而是可以带参数的装饰器。
  • I’ll leave you the challenge of creating a class that serves as a decorator. The basic principle of implementing decorators using classes is the same as with regular decorator functions. You can think of classes as functions because the class above becomes callable by implementing the __call__ method.

    我将给您留下创建一个充当装饰器的类的挑战。 使用类实现装饰器的基本原理与常规装饰器功能相同。 您可以将类视为函数,因为可以通过实现__call__方法来调用上述类。

  • As you can see, by specifying the number of repeat times, the decorated function works as expected.

    如您所见,通过指定重复次数,装饰函数将按预期工作。

Although it’s possible to implement decorators using classes, it can be more complicated than introduced here if you want your decorators to be fully versatile. The example given is just to provide you a proof of concept. If you want to use these decorators with methods that are defined in a class, you have to consider the positional arguments related to the class (i.e. cls) or the instance (i.e. self). For more in-depth knowledge, you can refer to some good discussion on Stack Overflow.

尽管可以使用类来实现装饰器,但是如果您希望装饰器具有完全的通用性,则它可能比这里介绍的复杂。 给出的示例只是为了向您提供概念证明。 如果要将这些装饰器与类中定义的方法一起使用,则必须考虑与类(即cls )或实例(即self )相关的位置参数。 要获得更深入的知识,可以参考关于Stack Overflow的一些很好的讨论。

结论 (Conclusion)

In this article, we first reviewed the basic form of decorators and then learned five more advanced features of decorators. When you have a good understanding of decorators, you can define some custom ones (e.g. logging time, type checking) that will help you with routine work.

在本文中,我们首先回顾了装饰器的基本形式,然后学习了装饰器的五个更高级的功能。 对装饰器有很好的了解后,您可以定义一些自定义器(例如,记录时间,类型检查),这将有助于您进行常规工作。

翻译自: https://medium.com/better-programming/python-decorators-5-advanced-features-to-know-17dd9be7517b

装饰器python日志功能

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值