c

codeye

V1

2022/10/27阅读:19主题:默认主题

10个美妙的Python装饰器

10个美妙的Python装饰器 对Python编程语言中我最喜欢的一些装饰器的概述。

简介

关于Python编程语言的伟大之处在于,它在一个小包里装了所有的功能,这些功能非常有用。很多特性可以完全改变Python代码的功能,这使得该语言更加通用。此外,如果使用得当,这些功能中的一些可以起到缩短编写有效软件的时间的作用。Python的装饰器是一个很好的例子,它很好地完成了这些目标。

装饰器是快速编程宏,可以用来改变 Python 对象的行为。它们可以应用于类和函数,并且实际上可以做很多非常有趣的事情。装饰器可以用来缩短代码,加快代码速度,并完全改变代码在Python中的行为方式。不用说,这肯定能派上用场! 今天我想展示一些我认为值得一看的装饰器。有很多装饰器,但我挑选了一些我认为功能最酷的。

№1: @lru_cache

这个列表中的第一个装饰器来自functools模块。这个模块包含在标准库中,而且非常容易使用。除了这个装饰器之外,它还包含了很多很酷的功能,但这个装饰器无疑是我最喜欢的。

@lru_cache装饰器可以用来加快函数和操作的连续运行速度,使用缓存。当然,在使用时要注意交换和缓存的一些注意事项,但在通用的使用情况下,大多数时候这个装饰器是值得使用的。如果你想了解更多关于Functools和我为什么喜欢它,实际上我写了一整篇文章,你可以在这里阅读。

FuncTools:一个被低估的Python包 用Functools把你的Python函数提升到一个新的水平!

能够用一个简单的装饰器来加快代码的速度是令人难以置信的。一个可以从这样的装饰器中受益的函数的很好的例子是递归函数,比如这个计算阶乘的函数。

def factorial(n):
    return n * factorial(n-1) if n else 1

递归对于计算时间来说是相当困难的,但是添加这个装饰器可以帮助大大加快这个函数的连续运行速度。

@lru_cache
def factorial(n):
    return n * factorial(n-1) if n else 1

现在,每当我们运行这个函数时,前几个阶乘的计算将被保存到缓存中。因此,下次我们再去调用这个函数时,只需要计算我们之前使用的阶乘之后的阶乘。当然,并不是所有的阶乘计算都会被保存,但是很容易看出为什么这是一个很好的应用,这个装饰器可以加速一些自然缓慢的代码。

№2: @jit

JIT是Just In Time编译的简称。通常我们在Python中运行一些代码时,首先发生的是编译。这种编译产生了一些开销,因为类型被分配了内存,并以未分配但命名的别名的形式存储。有了及时编译,我们在执行时就完成了大部分的工作。在很多方面,我们可以认为这类似于并行计算,Python 解释器同时处理两件事情,以节省一些时间。

Numba JIT编译器就是因为在Python中提供了这个概念而闻名。与@lru_cache类似,这个装饰器可以很容易地被调用,在你的代码中可以立即提升性能。Numba包提供了jit装饰器,这使得运行更密集的软件变得更容易,而不需要落入C语言。

from numba import jit
import random

@jit(nopython=True)
def monte_carlo_pi(nsamples):
    acc = 0
    for i in range(nsamples):
        x = random.random()
        y = random.random()
        if (x ** 2 + y ** 2) < 1.0:
            acc += 1
    return 4.0 * acc / nsamples

№3: @do_twice

do_twice装饰器的作用与它的名字差不多。这个装饰器可以被用来在一次调用中运行一个函数两次。这当然有一些用途,我发现它对调试特别有帮助。它的另一个用途是测量两个不同迭代的性能。以Functools为例,我们可以让一个函数运行两次,以便检查出现的改进。这个函数是由Python中的装饰器模块提供的,它在标准库中。

from decorators import do_twice
@do_twice
def timerfunc():
    %timeit factorial(15)

№4: @count_calls

与do_twice装饰器的简单性相一致的是count_calls装饰器。这个装饰器可以用来提供一个函数在软件中被使用多少次的信息。

和do_twice一样,这在调试时肯定会派上用场。当添加到一个给定的函数时,我们将收到一个输出,告诉我们该函数在每次运行时被运行了多少次。这个装饰器也在标准库的装饰器模块中。

from decorators import count_calls
@count_calls
def function_example():
    print("Hello World!"
function_example()
function_example()
function_example()

№5: @dataclass

为了在编写类时节省时间,我一直利用的最好的装饰器之一是数据类装饰器。这个装饰器可以用来快速编写常见的标准方法,这些方法通常在我们编写的类中出现。如果你想进一步了解这个装饰器,我也有一篇关于它的文章,你可以在这里阅读。

这个装饰器来自于dataclass模块。这个模块也在标准库中,所以没有必要用PIP来尝试这个例子

from dataclasses import dataclass@dataclass
class Food:
    name: str
    unit_price: float
    stock: int = 0
        
    def stock_value(self) -> float:
        return(self.stock * self.unit_price)

这段代码将自动创建一个初始化函数,__init__(),其中有必要的位置参数来填充我们类中的数据。它们也将自动提供给self,所以没有必要为了在类中获得一些数据参数而写一个很长的函数。

№6: @singleton

为了理解 @singleton装饰器的目的,我们首先需要理解什么是singleton。singleton在某种意义上是全局类型的一个版本。这意味着,这些类型被定义为只存在一次。

尽管这些类型在像 C++ 这样的语言中很常见,但在 Python 中却很少见到。对于singleton,我们创建一个只使用一次的类,并对该类进行突变,而不是初始化构造类型。在这种情况下,类型的作用不像是模板,而更像是一个单一的受控对象。

通常情况下,singleton装饰器是由用户制作的,事实上并没有导入。这是因为单子仍然是对我们的单子装饰器中提供的模板的引用。我们可以命名一个单子函数并编写一个包装器,以便在我们的类上使用这个装饰器。

def singleton(cls):
    instances = {}
    def wrapper(*args, **kwargs):
        if cls not in instances:
          instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return wrapper
@singleton
class cls:
    def func(self):

另一种方法是通过使用元类来解决这个问题。如果你想了解更多关于元类的信息,我在去年写了一篇文章,详细介绍了这些元类和它们的用途,你可以在这里查看。

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]
        
class Logger(object):
    __metaclass__ = Singleton

№7: @use_unit

对于科学计算来说,有一个装饰器可能经常派上用场,那就是use_unit装饰器。这个装饰器可以用来改变返回方法的输出。

这对于那些不想给他们的数据添加测量单位,但仍想让人们知道这些单位是什么的人来说是很有用的。这个装饰器在任何模块中都不是真正可用的,但它是非常常用的,对科学应用来说非常有用。

def use_unit(unit):
    "让一个函数返回一个给定单位的数量"
    use_unit.ureg = pint.UnitRegistry()
    def decorator_use_unit(func):
        @functools.wraps(func)
        def wrapper_use_unit(*args, **kwargs):
            value = func(*args, **kwargs)
            return value * use_unit.ureg(unit)
        return wrapper_use_unit
    return decorator_use_unit

@use_unit("meters per second")
def average_speed(distance, duration):
    return distance / duration

№7: @wraps

函数包装器 @wraps是一种设计模式,用于处理Python中相对复杂的函数。封装函数通常用来做一些可能被认为是更低级的、迭代的任务。封装函数的好处是,它可以用来极大地提高性能。和lru_cache装饰器一样,这个装饰器是由FuncTools包提供的。

import functools as ft

wraps装饰器本身只是一个方便的装饰器,用于更新一个给定函数的包装器。当这个装饰器被调用时,该函数将在每次使用时更新其包装器。这对提高性能有很大的帮助,FuncTools中的许多工具都是如此。

def my_decorator(f):
    @wraps(f)
    def wrapper(*args, **kwds):
        print('Calling decorated function')
        return f(*args, **kwds)
    return wrapper

@wraps本身就是典型的装饰器,wrapper装饰器的调用通常用于该装饰器调用中的一个包装器函数。写完这段代码后,我们就可以用那个包装器来装饰我们的新函数了。

@my_decorator
def func(x):
    print(x)   
  

№8: @staticmethod

在某些情况下,人们可能希望能够访问那些被私下定义的东西,就像在一个更全局的意义上。有时我们有一些包含在类中的函数,我们希望将其方法化,而这正是staticmethod装饰器的用途。

使用这个装饰器,我们可以使C++静态方法与我们的类一起工作。通常情况下,写在类的范围内的方法对该类来说是私有的,除非作为子类调用,否则无法访问。然而,在某些情况下,你可能希望采取一种更实用的方式来处理你的方法与数据的交互。使用这个装饰器,我们可以创建这两个选项,而不需要创建一个以上的函数。

class Example:
    @staticmethod
    def our_func(stuff):
        print(stuff)     

我们也不需要明确地提供我们的类作为一个参数。staticmethod装饰器为我们处理了这个问题。

№9: @singledispatch

FuncTools在这个列表中再次出击,推出了非常有用的singledispatch装饰器。单一调度是一种编程技术,在许多编程语言中都很常见,因为它是一种很好的编程方式。虽然我更倾向于多路调度,但我认为单路调度在很多方面都可以发挥同样的作用。

这个装饰器使得在 Python 中处理类型要容易得多。当我们在处理要通过同一个方法的多个类型时,情况更是如此。我在我的FuncTools文章中写了更多关于这个问题的内容,所以如果你对使用单一派发方法感兴趣,我确实推荐你使用(链接在#1。)

@singledispatch
def fun(arg, verbose=False):
        if verbose:
            print("Let me just say,", end=" ")
        print(arg)
@fun.register
def _(arg: int, verbose=False):
    if verbose:
        print("Strength in numbers, eh?", end=" ")
    print(arg)
@fun.register
def _(arg: list, verbose=False):
    if verbose:
        print("Enumerate this:")
    for i, elem in enumerate(arg):
        print(i, elem)

№10: @register

注册函数来自于模块atexit。考虑到该模块的名称和工作原理,你可能会想到这个装饰器可能与终止时执行某些动作有关。

register装饰器命名了一个终止时要运行的函数。例如,这将与一些需要在你退出时进行保存的软件一起工作。

from atexit import register
@register
def termin():
    print(" Goodbye!" )

总结

不用说,Python 的装饰器是非常有用的。它们不仅可以用来减慢编写一些代码的时间,而且对加快代码的速度也有很大的帮助。当你发现装饰器的时候,不仅是令人难以置信的有用,而且编写你自己的装饰器也是一个好主意。

这些装饰器之所以强大,是因为装饰器作为一种特性在 Python 中非常强大。感谢你阅读我的文章,我希望它能让你注意到Python的一些很酷的能力!

分类:

后端

标签:

后端

作者介绍

c
codeye
V1