Code前端首页关于Code前端联系我们

Python开发知识:理解和使用装饰器@decorator

terry 2年前 (2023-09-25) 阅读数 48 #后端开发

Python开发知识:理解和使用装饰器 @decorator

Python的装饰器是一个很棒的机制,也是熟练使用Python的必杀技之一。装饰器,顾名思义,就是用来装饰的。它装饰一个函数,保留被装饰函数原来的功能,装饰(然后在其中添加一些其他函数)并返回一个带有新函数的函数对象,所以装饰装饰器本质上是一个返回函数对象的函数(需要确切地说,装饰器必须是可检索的对象(除了函数之外,类也可以充当装饰器)。

在编程时,我们经常会遇到这样的场景:登录验证、权限验证、日志记录等,这些功能代码可能在不同的环节都需要,但它们非常相似,都是被装饰器抽象和剥离的。这部分代码可以很好的解决这类场景。

什么是装饰器?

要了解Python的装饰器,我们首先了解Python的函数对象。我们知道Python中的一切都是对象,函数也不例外。函数是第一类对象。它们可以分配给变量,也可以用作列表的元素,还可以作为参数传递给其他函数。 。

函数可以通过变量引用

定义一个简单的函数:


def say_hi():
    print('Hi!')
say_hi()
# Output: Hi!

我们可以通过另一个变量 Say_hi2 来引用函数 Say_hi:


say_hi2 = say_hi
print(say_hi2)
# Output: <function say_hi at 0x7fed671c4378>

say_hi2()
# Output: Hi!

在上面的语句中,Say_hi2 和 Say_hi 引用的是同一个函数定义。两者的执行结果也是一样的。

函数可以作为参数传递给其他函数


def say_more(say_hi_func):
    print('More')
    say_hi_func()

say_more(say_hi)
# Output:
#     More
#     Hi

在上面的示例中,我们将函数 Say_hi 作为参数传递给 Say_more 函数,Say_hi 由变量 Say_hi_func 引用。

该函数可以在其他函数中定义


def say_hi():
    print('Hi!')
    def say_name():
        print('Tom')
    say_name()

say_hi()
# Output:
#     Hi!
#     Tom

say_name() # 报错

在上面的代码中,我们在Say_hi()函数中定义了另一个函数Say_name()。 Say_name()仅在Say_hi函数内可见(即其作用域在Say_hi函数内),外包调用Say_hi时会出错。

函数可以返回对其他函数的引用


def say_hi():
    print('Hi!')
    def say_name():
        print('Tom')
    return say_name

say_name_func = say_hi()
# 打印Hi!,并返回say_name函数对象
# 并赋值给say_name_func

say_name_func()
# 打印 Tom

在上面的示例中,say_hi 函数返回对内部定义的函数 say_name 的引用。这样,say_name函数也可以在say_hi函数之外使用。

我们之前已经了解了函数,这将有助于我们了解装饰器。

装饰器

装饰器是用于修改函数或类的可调用对象。
可调用对象是可以接受某些参数并返回某些对象的对象。 Python 中的函数和类是可调用对象。

函数装饰器接受一个函数作为参数,包装函数参数,然后返回添加了包装器的函数,即生成一个新函数。

我们看下面的例子:


def decorator_func(some_func):
  # define another wrapper function which modifies some_func
  def wrapper_func():
    print("Wrapper function started")
    
    some_func()
    
    print("Wrapper function ended")
    
  return wrapper_func # Wrapper function add something to the passed function and decorator returns the wrapper function
    
def say_hello():
  print ("Hello")
  
say_hello = decorator_func(say_hello)

say_hello()

# Output:
#  Wrapper function started
#  Hello
#  Wrapper function ended

在上面的例子中,decorator_func是定义的装饰器函数,它接受some_func作为参数。它定义了一个调用 some_func 的wrapper_func 函数,但也添加了自定义代码。

上面代码中使用装饰器的方法看起来有点复杂。其实,装饰器真正的Python语法是这样的:

装饰器的Python语法


@decorator_func
def say_hi():
    print 'Hi!'

@ 一致性是装饰器的语法糖,在定义时使用say_hi函数来避免另一个赋值语句。
上面的语句等价于:


def say_hi():
    print 'Hi!'
say_hi = decorator_func(say_hi)

装饰器的顺序


@a
@b
@c
def foo():
    print('foo')

# 等同于:
foo = a(b(c(foo)))

带参数函数的装饰器


def decorator_func(some_func):
    def wrapper_func(*args, **kwargs):
        print("Wrapper function started")
        some_func(*args, **kwargs)
        print("Wrapper function ended")
    
    return wrapper_func

@decorator_func    
def say_hi(name):
    print ("Hi!" + name)

在上面的代码中,Say_hi 函数有一个参数。通常,不同功能的函数可以有不同的类别和不同数量的参数。在编写wrapper_func时,我们不确定参数的名称和数量。我们可以通过 *args 和 **kwargs 引用函数参数。

带参数的装饰器

不仅被装饰的函数可以带参数,装饰器本身也可以带参数。参考下面的例子:


def use_logging(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            if level == "warn":
                logging.warn("%s is running" % func.__name__)
            return func(*args)
        return wrapper

    return decorator

@use_logging(level="warn")
def foo(name='foo'):
    print("i am %s" % name)

简单来说,参数化装饰器就是在无参数装饰器外面嵌套一个参数的函数,并且该函数返回无参数装饰器。

作为装饰器的类

之前我们说过装饰器是可召唤的对象。在Python中,类不仅是函数,而且还是可调用的对象。使用课堂装饰器的优点是灵活性、高内聚性和封装性。通过在类中实现 __call__ 方法,当使用 @ 语法糖将装饰器附加到函数时,会调用此方法。


class Foo(object):
    def __init__(self, func):
    self._func = func

def __call__(self):
    print ('class decorator runing')
    self._func()
    print ('class decorator ending')

@Foo
def say_hi():
    print('Hi!')

say_hi()
# Output:
# class decorator running
# Hi!
# class decorator ending

functools.wraps

使用装饰器很大程度上重用了代码,但一个缺点是它缺少原始函数的元信息,例如函数的文档字符串、__name__ 和参数列表。考虑以下示例:


def decorator_func(some_func):
    def wrapper_func(*args, **kwargs):
        print("Wrapper function started")
        some_func(*args, **kwargs)
        print("Wrapper function ended")
    
    return wrapper_func

@decorator_func    
def say_hi(name):
    '''Say hi to somebody'''
    print ("Hi!" + name)

print(say_hi.__name__)  # Output: wrapper_func
print(say_hi.__doc__)   # Output: None

如您所见,Say_hi 函数已被wrapper_func 函数替换,其 __name__ 和 docstring 当然是wrapper_func 函数的那些。
不过不用担心,Python 有 functools.wraps。 Wraps自己也是一名装饰师。它的作用是将原函数的元信息复制到装饰器函数中,使得装饰器函数也具有与原函数相同的元信息。


from functools import wraps
def decorator_func(some_func):
    @wraps(func)
    def wrapper_func(*args, **kwargs):
        print("Wrapper function started")
        some_func(*args, **kwargs)
        print("Wrapper function ended")
    
    return wrapper_func

@decorator_func    
def say_hi(name):
    '''Say hi to somebody'''
    print ("Hi!" + name)

print(say_hi.__name__)  # Output: say_hi
print(say_hi.__doc__)   # Output: Say hi to somebody

类的内置装饰器

类属性@property
静态方法@staticmethod
类方法@classmethod

通常情况下,我们首先需要实例化类的对象,然后调用其方法。
如果类方法使用@staticmethod或@classmethod,则可以直接通过classname.methodname()调用,无需实例化。
从使用角度来看,@staticmethod 不需要 self 指向自己的对象,也不需要 cls 参数指向自己的类,就像使用常规函数一样。 @classmethod不需要self参数,但第一个参数必须是指向自己类的cls参数。如果想在@staticmethod中调用该类的一些属性方法,只能使用直接方法classname.propertyname或者classname.methodname。
因为@classmethod包含cls参数,所以可以调用类属性、类方法、实例化对象等。

总结

通过了解Python的函数,我们逐渐了解了装饰器的来龙去脉。装饰器是代码复用的一个很好的工具,可以在编程过程中在适当的场景中使用。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门