广告
返回顶部
首页 > 资讯 > 后端开发 > Python >python装饰器3:进阶
  • 754
分享到

python装饰器3:进阶

进阶python 2023-01-30 23:01:25 754人浏览 八月长安

Python 官方文档:入门教程 => 点击学习

摘要

装饰器1:函数装饰器 装饰器2:类装饰器 装饰器3:进阶 函数装饰器装饰方法 函数装饰器装饰普通函数已经很容易理解了: @decorator def func():... #等价于 def func():... func = dec

  • 装饰器1:函数装饰器
  • 装饰器2:类装饰器
  • 装饰器3:进阶

函数装饰器装饰方法

函数装饰器装饰普通函数已经很容易理解了:

@decorator
def func():...

#等价于
def func():...
func = decorator(func)

如果装饰器是带参装饰器,那么等价的形式大概是这样的(和装饰器的编码有关,但最普遍的编码形式如下):

@decorator(x, y, z)
def func():...

# 等价于
def func():...
func = decorator(x, y, z)(func)

这样的函数装饰器也可以去装饰类中的方法。看下面的方法装饰形式:

class cls:
    @decorator
    def method(self,arg1,arg2):
        ...

它等价于:

class cls:
    def method(self,arg1,arg2):
        ...
    method = decorator(method)

在decorator的编码中,仍然像普通的函数装饰器一样编写即可。例如:

def decorator(F):
    @wraps(F)
    def wrapper(*args, **kwargs):
        ...    # args[0] = self_instance
               # args[1]开始才是手动传给method的参数
    return wrapper

但必须要考虑到method的第一个参数self,所以包装器wrapper()的第一个参数也是self。

如此一来,函数装饰器既可以装饰函数,又可以装饰方法。

下面是一个示例:

from functools import wraps

def decorator(F):
    @wraps(F)
    def wrapper(*args, **kwargs):
        result = F(*args, **kwargs)
        print(args)
        return result
    return wrapper

@decorator
def func(x,y):
    return x + y

print(func(3, 4))

print("-" * 30)

class cls:
    @decorator
    def method(self, x, y):
        return x + y

c = cls()
print(c.method(3, 4))

输出结果:

(3, 4)
7
------------------------------
(<__main__.cls object at 0x01DF1C50>, 3, 4)
7

让类称为装饰器

不仅函数可以作为装饰器,类也可以作为装饰器去装饰其它对象。

如何让类作为装饰器

要让类作为装饰器,先看装饰的形式:

class Decorator:
    ...

@Decorator
def func():
    ...

func(arg1, arg2)

如果成功装饰,那么它等价于:

def func(): ...
func = Decorator(func)

func(arg1, arg2)

这和函数装饰器看上去是一样的,但区别在于Decorator这里是一个类,而不是函数,且Decorator(func)表示的是创建一个Decorator类的实例对象,所以这里赋值符号左边的func是一个对象。所有后面的func(arg1, arg2)是调用对象,而不是调用函数。

要让实例对象成为可调用对象,它必须实现__call__方法,所以应该在Decorator类中定义一个__call__。而且每次调用实例对象的时候,都是在调用__call__,这里的__call__对等于函数装饰器中的包装器wrapper,所以它的参数和逻辑应当和wrapper一样。

如下:

class Decorator():
    def __call__(self, *args, **kwargs):
        ...

再看func = Decorator(func),func是Decorator类创建实例的参数,所以Decorator类还必须实现一个__init__方法,接受func作为参数:

class Decorator:
    def __init__(self, func):
        ...
    def __call__(self, *args, **kwargs):
        ...

元数据问题

这样的装饰器已经能正常工作了,但是会丢失func的元数据信息。所以,必须使用functools的wraps()保留func的元数据:

from functools import wraps

class Decorator:
    def __init__(self, func):
        wraps(func)(self)
        ...
    def __call__(self, *args, **kwargs):
        ...

为什么是wraps(func)(self)?这里显然不能@wraps(func)的方式装饰包装器,所以只能使用wraps()的原始函数形式。在wraps()装饰函数包装器wrapper的时候,@wraps(func)等价于wrapper = wraps(func)(wrapper),所以这里wraps(func)(self)的作用也是很明显的:保留func的元数据,并装饰self。被装饰的self是什么?是Decorator的实例对象,因为Decorator类实现了__call__,所以self是可调用的,所以这里的self类似于函数装饰器返回的wrapper函数(实际上self是Decorator(func)返回的各个实例对象)。

类作为装饰器的参数问题

虽然self是Decorator的可调用实例对象,但是上面的代码中self并不具有func属性,也就是说无法从self去调用func()函数,这似乎使得整个过程都崩塌了:废了老大的劲去解决各种装饰器上的问题,结果却不能调用被装饰的函数。

有两种方式可以解决这个问题:

  1. __init__中使用self.func = func保留func对象作为装饰器的一个属性
  2. 在使用wraps()后直接在包装器__call__中使用__wrapped__调用原始func函数

这两种方式其实是等价的,因为self.func__wrapped__都指向原始的函数。

def __init__(self,func):
    wraps(func)(self)
    self.func = func
def __call__(self, *args, **kwargs):
    result = self.func(*args, **kwargs)

#-------------------------------

def __init__(self, func):
    wraps(func)(self)
def __call__(self, *args, **kwargs):
    result = self.__wrapped__(*args, **kwargs)

但这两种方式都有缺陷,缺陷在于装饰类中方法时。(注:在装饰普通函数、类方法的时候,上面的方式不会出错)

class cls:
    @decorator
    def method(self, x, y):...

因为self.func__wrapped__装饰cls中的方法时指向的都是cls的类变量(只不过这个属性是装饰器类decorator的实例对象而已),作为类变量,它无法保存cls的实例对象,也就是说method(self, x, y)的self对装饰器是不可见的。

用一个示例解释更容易:

import types
from functools import wraps

# 作为装饰器的类
class decorator:
    def __init__(self,func):
        self.func = func
    def __call__(self, *args, **kwargs):
        print("(1): ",self)      # (1)
        print("(2): ",self.func) # (2)
        print("(3): ",args)      # (3)
        return self.func(*args, **kwargs)

class cls:
    @decorator
    def method(self, x, y):
        return x + y

c = cls()
print("(4): ",c.method)      # (4)
print(c.method(3, 4))

输出结果:

(4):  <__main__.decorator object at 0x03261630>
(1):  <__main__.decorator object at 0x03261630>
(2):  <function cls.method at 0x032C2738>
(3):  (3, 4)
Traceback (most recent call last):  File "g:/pycode/scope.py", line 21, in <module>
    print(c.method(3, 4))
  File "g:/pycode/scope.py", line 12, in __call__    return self.func(*args, **kwargs)
TypeError: method() missing 1 required positionalargument: 'y'

注意观察上面__call__中输出的几个对象:

  • self对应的是decorator的实例对象method,而非cls的实例对象c,看输出结果的前两行即可知
  • self.func指向的是原始方法method,它是类变量,是类方法(函数),是装饰器赋予它作为函数的。也就是说,self.func指向的不是对象方法,而是类方法,类方法不会自动传递实例对象
  • args中保存的参数列表是(3, 4),但是cls.method中多了一个self位置参数,使得3赋值给了self,4被赋值给了x,y成了多余的,所以最后报错需要位置参数y。

如果将上面的method()的定义修改一下,把self去掉,将会正确执行:

class cls:
    @decorator
    def method(x, y):
        return x + y

执行结果:

(4):  <__main__.decorator object at 0x03151630>
(1):  <__main__.decorator object at 0x03151630>
(2):  <function cls.method at 0x031B2738>
(3):  (3, 4)
7

因此参数问题必须解决。解决方案是进行判断:如果是通过实例对象触发的方法调用(即c.method()),就将外部函数通过types.MethodType()链接到这个实例对象中,否则就返回原始self(因为它指向被装饰的原始对象)。

这需要借助描述符来实现,关于这一段的解释,我觉得直接看代码自行脑部更佳。

class decorator:
    def __init__(self,func):
        wraps(func)(self)
        self.func = func
    def __call__(self, *args, **kwargs):
        return self.func(*args, **kwargs)
    def __get__(self, instance, owner):
        # 如果不是通过对象来调用的
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)

class cls:
    @decorator
    def method(self, x, y):
        return x + y

c = cls()
print(c.method(3, 4))  # 调用__get__后调用__call__

对于__wrapped__也一样可行:

class decorator():
    def __init__(self, func):
        wraps(func)(self)
    def __call__(self, *args, **kwargs):
        return self.__wrapped__(*args, **kwargs)
    def __get__(self, instance, owner):
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)

装饰时是否带参数

如果要让作为装饰器的类在装饰时带参数,就像函数装饰器带参一样decorator(x,y,z)(func),可以将参数定义在__init__上进行处理,然后在__call__中封装一层。

class Decorator:
    def __init__(self, *args, **kwargs):
        ... do something with args ...
    def __call__(self, func):
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper

和函数装饰器一样,如果想要达到下面这种既能无参装饰,又能带参装饰:

@Decorator         # 无参装饰
@Decorator(x,y,z)  # 带参装饰
@Decorator()       # 带参装饰,只不过没给参数

可以直接在__init__上进行参数有无的判断:

import types
from functools import wraps, partial

class Decorator:
    def __init__(self, func=None, arg1=1, arg2=2, arg3=3):
        # 带参装饰器
        if func is None:
            self.func = partial(Decorator, arg1=arg1, arg2=arg2, arg3=arg3)
        else:       # 无参装饰器
            wraps(func)(self)
            self.func = func

    def __call__(self, *args, **kwargs):
        return self.func(*args, **kwargs)

    def __get__(self, instance, owner):
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)

这样的限制是装饰器如果带参数时,必须使用keyWord方式指定参数。例如:

# 带参装饰普通函数,使用keywords参数方式
@Decorator(arg1=1, arg2=3, arg3=5)
def func(x, y):
    return x + y

print(func(11, 22))

print('-' * 30)

# 无参装饰普通函数
@Decorator
def func1(x, y):
    return x + y

print(func1(111, 22))

print('-' * 30)

# 无参装饰方法
class cls:
    @Decorator
    def method(self, x, y):
        return x + y

c = cls()
print(c.method(3, 4))

print('-' * 30)

# 带参装饰方法
class cls1:
    @Decorator(arg1=1, arg2=3, arg3=5)
    def method(self, x, y):
        return x + y

cc = cls1()
print(cc.method(3, 4))

总结:类作为装饰器的通用格式

如果不考虑装饰时是否带参数的问题,根据上面的一大堆分析,类作为装饰器时的通用代码格式如下:

import types
from functools import wraps

class Decorator:
    def __init__(self, func):
        wraps(func)(self)
        # self.func = func

    def __call__(self, *args, **kwargs):
        # return self.func(*args, **kwargs)
        # return self.__wrapped__(*args, **kwargs)

    def __get__(self, instance, owner):
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)

至于选择self.func的方式,还是self.__wrapped__的方式,随意。

如果需要考虑装饰时带参数问题,那么参考上一小节内容。

选择类谁作为装饰器?

函数可以作为装饰器,类也可以作为装饰器。它们也都能处理处理各种需求,但是类作为装饰器的时候解释了好大一堆,非常麻烦。所以,如果可以的话,选择函数作为装饰器一般会是最佳方案。

--结束END--

本文标题: python装饰器3:进阶

本文链接: https://www.lsjlt.com/news/180637.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • python装饰器3:进阶
    装饰器1:函数装饰器 装饰器2:类装饰器 装饰器3:进阶 函数装饰器装饰方法 函数装饰器装饰普通函数已经很容易理解了: @decorator def func():... #等价于 def func():... func = dec...
    99+
    2023-01-30
    进阶 python
  • python装饰器进阶
    目录 装饰器进阶 1. 被装饰的函数有多个参数。 2. 被装饰的函数有返回值 3.在函数中嵌入装饰器 4. 装饰器类 ...
    99+
    2023-01-30
    进阶 python
  • python进阶之装饰器
    一.无参装饰器 问题:如何计算一段程序的运行时间? 先看一段简单代码: 1 import time 2 def func(): 3 start = time.time() # 记录程序开始时间 4 time.sleep(...
    99+
    2023-01-30
    进阶 python
  • python 进阶学习之python装饰器小结
    装饰器总结 什么是装饰器?处理函数的函数,加一个功能,但是不影响原来函数的内部结构生活中的例子:给手机加一个外壳,外壳保护了手机 装饰器有什么用?增强函数的功能 装饰器使用场景增加被...
    99+
    2022-11-12
  • 老生常谈Python进阶之装饰器
    函数也是对象 要理解Python装饰器,首先要明白在Python中,函数也是一种对象,因此可以把定义函数时的函数名看作是函数对象的一个引用。既然是引用,因此可以将函数赋值给一个变量,也可以把函数作为一个参数...
    99+
    2022-06-04
    进阶 老生常谈 Python
  • Python 3 之 装饰器详解
    ------------ 装饰器 -----------------------------------------------------什么是装饰器装饰器是为函数和类指定管理代码的一种方式。装饰器本身的形式是处理其他的可调用对象的可调用...
    99+
    2023-01-31
    详解 Python
  • Python 3 进阶 —— prin
    在 Python 中,print 可以打印所有变量数据,包括自定义类型。在 2.x 版本中,print 是个语句,但在 3.x 中却是个内置函数,并且拥有更丰富的功能。 参数选项 可以用 help(print) 来查看 print 函数...
    99+
    2023-01-31
    进阶 Python prin
  • Python 3 进阶 —— 使用 P
    PyMySQL 是一个纯 Python 实现的 MySQL 客户端操作库,支持事务、存储过程、批量执行等。 PyMySQL 遵循 Python 数据库 API v2.0 规范,并包含了 pure-Python MySQL 客户端库。 安装...
    99+
    2023-01-31
    进阶 Python
  • python 3层装饰器及应用场景
    #!/usr/bin/env python# -*- coding:utf-8 -*-# author: Changhua Gongimport time, functools'''实现装饰器:1.函数即变量;2.高阶函数;    1)把一...
    99+
    2023-01-31
    场景 器及 python
  • python装饰器2:类装饰器
    装饰器1:函数装饰器 装饰器2:类装饰器 装饰器3:进阶 本文是装饰器相关内容的第二篇,关于类装饰器。 "类装饰器"有两种解读方式:用来装饰类的装饰器;类作为装饰器装饰其它东西。你如何认为取决于你,两种说法都有出现在其它的文章中。我的...
    99+
    2023-01-30
    python
  • python 装饰器
    装饰器本质上是一个Python函数,它可以让其他函数在不雲要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面雲求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳...
    99+
    2023-01-30
    python
  • python-装饰器
    装饰器简介:给被装饰的函数在不更改代码的基础上增加新的功能;多个装饰器的执行顺序:从最靠近原始函数的装饰器开始执行,最后执行原始函数; 直接上个简单的例子就懂了: 一 最简单的装饰器:#!/usr/bin/python def deco(f...
    99+
    2023-01-31
    python
  • python装饰器
    什么是装饰器:   装饰器就是python中的一个语法糖。作用就是在不改动之前代码的情况下给某个函数增加相应想要增加的功能。 假设需求:   我在几个函数内分别放了一部电影,代码如下: 1 def mv1(): 2 print(...
    99+
    2023-01-30
    python
  • Python装饰器-闭包与函数装饰器
    一、闭包在学习装饰器前,需要先了解闭包的概念。形成闭包的要点:函数嵌套将内部函数作为外部函数的返回值内部函数必须要使用到外部函数的变量下面以一个计算列表平均值的案例来讲解闭包:def make_average(): # 创建一个列表,用来保...
    99+
    2023-05-14
    Python 函数 装饰器
  • python装饰器1:函数装饰器详解
    装饰器1:函数装饰器 装饰器2:类装饰器 装饰器3:进阶 先混个眼熟 谁可以作为装饰器(可以将谁编写成装饰器): 函数 方法 实现了__call__的可调用类 装饰器可以去装饰谁(谁可以被装饰): 函数 方法 类 基础...
    99+
    2023-01-30
    详解 函数 python
  • Python之装饰器
    在Python中一切皆对象,函数是一等对象。这意味着可以通过名字引用函数。>>> a=123 >>> a 123 >>> name='zeng' >>> name 'z...
    99+
    2023-01-31
    Python
  • Python装饰器与类的装饰器怎么实现
    这篇“Python装饰器与类的装饰器怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Python装饰器与类的装饰器怎么...
    99+
    2023-06-29
  • Python——装饰器(无参)
    装饰器装饰器可以叠加使用,执行是从下到上执行的无参装饰器 @logger1.它是一个语法糖2.函数作为它 的形参3.返回值也是一个函数4.可以使用 @function 方式,简化调用注意: 此处的装饰器的定义并不准确,只是方便理解装饰器和高...
    99+
    2023-01-31
    Python
  • Python装饰器详情
    目录1、装饰器1.1 应用场景2、万能装饰器3、多层装饰器4、带参数的装饰器1、装饰器 装饰器(Decorator):从字面上理解,就是装饰对象的器件。可以在不修改原有代码的情况下,...
    99+
    2022-11-12
  • Python带参装饰器
    装饰器(无参)  它是一个函数;  函数作为它的形参;  返回值也是一个函数;  可以使用@functionname方式,简化调用;装饰器和高阶函数  装饰器是高阶函数,但装饰器是对传入函数的功能的装饰(功能增强)import dateti...
    99+
    2023-01-31
    Python
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作