函数装饰器装饰方法

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

@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__的方式,随意。

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

选择类谁作为装饰器?

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

python装饰器3:进阶的更多相关文章

  1. Python装饰器的进阶

    带参数的装饰器 示例一:Python自带的装饰器函数 from functools import wraps import time def Time(func1): @wraps(func1) de ...

  2. Python 装饰器(进阶篇)

    装饰器是什么呢? 我们先来打一个比方,我写了一个python的插件,提供给用户使用,但是在使用的过程中我添加了一些功能,可是又不希望用户改变调用的方式,那么该怎么办呢? 这个时候就用到了装饰器.装饰器 ...

  3. (转)python装饰器进阶一

    Python装饰器进阶之一 先看例子 网上有很多装饰器的文章,上来说半天也没让人看明白装饰器到底是个什么,究竟有什么用,我们直接来看几个例子. Python递归求斐波那契数列 def fibonacc ...

  4. (转)python装饰器二

    Python装饰器进阶之二 保存被装饰方法的元数据 什么是方法的元数据 举个栗子 def hello(): print('Hello, World.') print(dir(hello)) 结果如下: ...

  5. 五分钟学会Python装饰器,看完面试不再慌

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是Python专题的第12篇文章,我们来看看Python装饰器. 一段囧事 差不多五年前面试的时候,我就领教过它的重要性.那时候我Pyt ...

  6. Python 装饰器填坑指南 | 最常见的报错信息、原因和解决方案

    本文为霍格沃兹测试学院学员学习笔记. Python 装饰器简介 装饰器(Decorator)是 Python 非常实用的一个语法糖功能.装饰器本质是一种返回值也是函数的函数,可以称之为“函数的函数”. ...

  7. 关于python装饰器

    关于python装饰器,不是系统的介绍,只是说一下某些问题 1 首先了解变量作用于非常重要 2 其次要了解闭包 def logger(func): def inner(*args, **kwargs) ...

  8. python装饰器通俗易懂的解释!

    1.python装饰器 刚刚接触python的装饰器,简直懵逼了,直接不懂什么意思啊有木有,自己都忘了走了多少遍Debug,查了多少遍资料,猜有点点开始明白了.总结了一下解释得比较好的,通俗易懂的来说 ...

  9. Python 装饰器学习

    Python装饰器学习(九步入门)   这是在Python学习小组上介绍的内容,现学现卖.多练习是好的学习方式. 第一步:最简单的函数,准备附加额外功能 1 2 3 4 5 6 7 8 # -*- c ...

  10. python 装饰器修改调整函数参数

    简单记录一下利用python装饰器来调整函数的方法.现在有个需求:参数line范围为1-16,要求把9-16的范围转化为1-8,即9对应1,10对应2,...,16对应8. 下面是例子: def fo ...

随机推荐

  1. 学以致用三十六-----弄懂python装饰器

    看了海峰老师讲解的装饰器视频,讲解的非常棒.根据视频,记录笔记如下: 装饰器: 1.本质是函数,用def来定义.功能就是用来(装饰)其他函数,为其他函数添加附加功能 现有两个函数如下, def tes ...

  2. intent和手势探测

    一.三种启动方法 setComponent ComponentName comp = new ComponentName( this, SecondActivity.class); Intent in ...

  3. [小结] 中山纪念中学2018暑期训练小结(划掉)(颓废记)-Day10

    [小结] 中山纪念中学2018暑期训练小结(划掉)(颓废记)-Day10 各位看众朋友们,你们好,今天是2018年08月14日,星期二,农历七月初四,欢迎阅看今天的颓废联编节目 最近发生的灵异事件有 ...

  4. php倒计时

    <form name="form1"> <div align="center" align="center"> &l ...

  5. SJCP认证题前五十题填坑

    在做Java的SJCP认证试题时自己整理了一些Java基础细节知识点,以下是知识点陈列 1.标签机制:标签起作用的唯一的地方刚好在迭代语句之前(不然编译错误) continue label1 直接转到 ...

  6. JAVA课程设计---学生基本信息管理系统

    1.团队课程设计博客链接 http://www.cnblogs.com/zyjjj/p/7061880.html 2.个人负责模块或任务说明 函数 功能说明 Search 查找学生信息,分为两种查找方 ...

  7. VSCode插件开发全攻略(八)代码片段、设置、自定义欢迎页

    更多文章请戳VSCode插件开发全攻略系列目录导航. 代码片段 代码片段,也叫snippets,相信大家都不陌生,就是输入一个很简单的单词然后一回车带出来很多代码.平时大家也可以直接在vscode中创 ...

  8. 新手入门:目前为止最透彻的的Netty高性能原理和框架架构解析

    1.引言 Netty 是一个广受欢迎的异步事件驱动的Java开源网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端. 本文基于 Netty 4.1 展开介绍相关理论模型,使用场景,基本组件 ...

  9. MySQL如何使用索引

    初始化测试数据 创建一个测试用的表 create table dept(id int primary key auto_increment , deptName varchar(32) not nul ...

  10. 第68节:Java中的MYSQL运用从小白到大牛

    第68节:Java中的MYSQL运用从小白到大牛 前言 学习java必备要求,学会运用!!! 常见关系化数据库 BootStrap是轻量级开发响应式页面的框架,全局css组件,js插件.栅格系统是将页 ...