• 装饰器是 Python 的一个重要部分,也是比较难理解和使用好的部分。下面对装饰器做一下简单整理

1. 前言

  • 装饰器实际上是应用了设计模式里,装饰器模式的思想:

    • 在不概念原有结构的情况下,添加新的功能
    • 类似于我们穿不同的衣服,可以先穿一件衬衫,再穿一件毛衣,再穿一件羽绒服
    • 但是毛衣不会影响羽绒服,羽绒服也不会影响衬衫
    • 随时更换,同一个人可以有不同的穿衣打扮
  • 对比之下,每一个装饰器就代表上述的一件衣服,我们可以根据功能需求,给一个函数本身加上不同的外套,也可以调整外套之间的顺序
  • 装饰器本质上就是一个个的 Python 函数对象,不改动代码的前提下增加功能,返回值也为是一个函数对象
  • 主要用于有切面需求的场景:记录日志、打点监控、权限校验、事务处理等场景

2. 函数

  • 理解装饰器首先要理解 Python 的函数

2.1 函数对象

  • Python 认为一切皆为对象:
def hi(name="yasoob"):
return "hi " + name print(hi())
# output: 'hi yasoob'
  • 可以将一个函数赋值给一个变量:
greet = hi
# 我们这里没有在使用小括号,因为我们并不是在调用hi函数
# 而是在将它放在greet变量里头。我们尝试运行下这个 print(greet())
# output: 'hi yasoob'
  • 可以删除一个函数:
del hi
print(hi())
#outputs: NameError print(greet())
#outputs: 'hi yasoob'

2.2 返回值类型为函数

  • 函数的返回值可以为函数对象:
def hi(name="yasoob"):
def greet():
return "now you are in the greet() function" def welcome():
return "now you are in the welcome() function" if name == "yasoob":
return greet
else:
return welcome a = hi()
print(a)
#outputs: <function greet at 0x7f2143c01500> print(a())
#outputs: now you are in the greet() function
  • a 为 hi 函数执行的返回值,此时 a 为一个函数对象,执行 a 得到返回值执行的结果

2.3 参数类型为函数

  • 既然函数对象可以作为返回值,那么不难理解也可以作为参数:
def hi():
return "hi yasoob!" def doSomethingBeforeHi(func):
print("I am doing some boring work before executing hi()")
print(func()) doSomethingBeforeHi(hi)
#outputs:I am doing some boring work before executing hi()
# hi yasoob!

3. 装饰器

  • 上文提到,装饰器本质也是一个返回值为函数类型的函数对象。通过装饰器可以抽离大量与函数本身无关的雷同代码进行复用
  • 一个简单例子,需要在代码中添加日志代码:
def foo1():
print('I am foo1')
logging.info('[foo1] Running')
  • 如果其他函数同样需要执行代码,为避免大量雷同代码,需要重新定义一个函数专门处理日志 ,日志处理完之后再执行真正的业务代码:
def use_loggine(func)
logging.info('[%s] Running' % func.__name__)
func() def foo2():
print('I am foo2') use_logging(foo2)
  • 使用上述方法实现了复用,但是也破坏了带结构,每次直接调用相应函数可以实现的功能,现在需要调用 use_logging 并传入相应函数做为参数,可读性也比较差。因此需要引入装饰器

3.1 简单装饰器

def use_logging(func):
def wrapper(*args, **kwargs):
logging.warn('[%s] Running' % func.__name__)
return func(*args, **kwargs)
return wrapper def foo3():
print('I am foo3') foo3 = use_logging(foo3)
foo3()
  • 函数 use_logging 就是装饰器,执行真正业务的方法 func 包裹在返回函数里,并重新对参数 foo3 赋值。相当于丰富了 foo3 的功能
  • 在这个例子中,函数进入和退出时 ,被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)
  • @ 符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作。其他函数也可以使用此装饰器包装,增加了代码可读性:
def use_logging(func):

    def wrapper(*args, **kwargs):
logging.warn('[%s] Running' % func.__name__)
return func(*args, **kwargs) return wrapper @use_logging
def foo4():
print('I am foo4') foo4()

3.2 带参数的装饰器

  • 还可以使用带参数的装饰器
  • 在上面的装饰器调用中,比如 @use_logging,该装饰器唯一的参数就是执行业务的函数
  • 装饰器的语法允许我们在调用时,提供其它参数,比如 @decorator(a):
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 foo5(name='foo'):
print('I am foo5') foo5()
  • 上述的 use_logging 是允许带参数的装饰器。它实际上是对原有装饰器的封装,并返回一个装饰器
  • 我们可以将它理解为一个含有参数的闭包。当我们调用 @use_logging(level=“warn”) 的时候,Python 能够发现这一层的封装,并把参数传递到装饰器的环境中

3.3 类装饰器

  • 相比函数装饰器,类装饰器具有灵活度大、高内聚和封装性等优点
  • 使用类装饰器还可以依靠类内部的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法:
class FooClass(object):
def __init__(self, func):
self._func = func def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending') @FooClass
def foo6():
print ('I am foo6') foo6()

3.4 带 functools.wraps 的装饰器

  • 使用装饰器复用了代码,但是有一个缺点就是原函数元信息不见了,如 docstring、__name、参数列表:
def logged(func):

    def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs) return with_logging @logged
def foo7(x):
"""do some math"""
return x + x * x foo7(8)
# prints 'foo7 was called\n72'
  • 上述代码等价于如下:
foo7 = logged(foo7)

print foo7.__name__        # prints 'with_logging'
print foo7.__doc__ # prints None
  • 可以看出,foo7 的信息会被 with_logging 替代,元信息会被 logged 的元信息替代
  • 使用 functools.wraps 可以把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数元信息与原函数一致:
from functools import wraps
def logged(func): @wraps(func)
def with_logging(*args, **kargs):
print(func.__name__ + " was called")
return func(*args, **kargs)
return with_logging @logged
def foo8(x):
"""do some math"""
return x + x * x print foo8.__name__ # prints 'f'
print foo8.__doc__ # prints 'do some math'

3.5 内置装饰器

  • 主要包括:@property、@staticmathod、@classmethod

3.5.1 @property

  • 对于 Class 的某个方法,可以通过 @property 装饰器将其伪装成一个属性,以 实例.方法 方式调用:
from math import pi
class Circle:
def __init__(self,r):
self.r = r
@property
def perimeter(self):
return 2*pi*self.r
@property
def area(self):
return self.r**2*pi c1 = Circle(5)
print(c1.area)
# prints '78.5398163397'
print(c1.perimeter)
# prints '31.4159265359'
  • 类中的方法名本来是不能用相同名字的,但只要使用 @property 加在 相应方法之前,并且在再次定义同名方法此前加上 @someone.setter、@someone.deleter:

    • 这样在类实例化化后,调用 实例.方法 就相当于调用 @property 包装的方法
    • 调用 实例.方法=val 就相当于调用 @someone.setter 下的方法,并且传进去一个参数 val
    • 调用 del 实例.方法 相当于调用 @someone.deleter 下的方法
class Person:
def __init__(self,name):
self.__name = name @property
def name(self):
return self.__name @name.deleter
def name(self):
del self.__name @name.setter
def name(self,new_name):
self.__name = new_name
  • property 函数可以起到和 @propery 装饰器相同的效果:
class C(object):
def __init__(self):
self._x = None def getx(self):
return self._x def setx(self, value):
self._x = value def delx(self):
del self._x x = property(getx, setx, delx, "I'm the 'x' property.") # 分别表示获取、设置和删除属性的函数以及 doc 信息 c = C()
c.x = 1
print(c.x)
# prints '1'

3.5.2 @classmethd

  • 把一个方法 变成一个类中的方法,这个方法就直接可以被类调用,不需要依托任何对象:
class Someone:
@classmethod
def set(cls, new_val):
cls.__val = new_val
@classmethod
def get(cls):
return cls.__val
__val = "abc" print(Someone.get())
# prints 'abc'
Someone.set('abcd')
print(Someone.get())
# prints 'abcd'
s1 = Someone()
s2 = Someone()
print(s1.get())
# prints 'abcd'
print(s2.get())
# prints 'abcd'
  • 当这个方法的操作只涉及静态属性的时候就应该使用 @classmethod 来装饰这个方法
  • 类似于实例方法要传个 self 代表实例对象,此类方法在传参时,要传一个形参代表当前类,默认 cls

3.5.3 @staticmethod

  • 如果一个函数既和对象没有关系,也和类没有关系 那么就用 @staticmethod 将这个函数变成一个静态方法:

class Someone:
@staticmethod
def set(new_val):
Someone.__val = new_val
@classmethod
def get(cls):
print("global __val: %s" % Someone.__val)
return cls.__val
__val = "abc"
  • 静态方法就是一个写在类里的普通函数
  • 对象可以调用类方法和静态方法,一般情况下推荐用类名调用
  • 类方法 有一个默认参数 cls 代表这个类 cls
  • 静态方法没有默认的参数,就象函数一样
  • 通过 类.静态方法 调用时不会实例化

3.5.4 @classmethod 和 @staticmethod 区别

  • @staticmethod  不需要表示自身对象的 self 和自身类的 cls 参数,就和使用普通的函数一样
  • @classmethod  不需要 self 参数,但是第一个参数需要表示自身类的 cls 参数
  • 如果在 @staticmethod 中要调用到这个类的一些属性方法,只能 类名.属性名 类名.方法名
  • 而 @classmethod 因为持有 cls 参数,可以来调用类的属性、类的方法、实例化对象等

3.6.装饰器执行顺序

@a
@b
@c
def foo9():
pass # 等效于 foo9 = a(b(c(foo9)))

4. 参考文献

Python(三)对装饰器的理解的更多相关文章

  1. python闭包和装饰器的理解

    闭包: 两个函数的嵌套,外部函数返回内部函数的引⽤,外部函数⼀定有参数 def 外部函数(参数): def 内部函数(): pass return 内部函数 他跟函数之间的区别: 1.格式两个函数嵌套 ...

  2. 从入门到自闭之python三大器--装饰器进阶

    装饰器的进阶 有参装饰器: # def warpper(func): # def inner(*args,**kwargs): # user = input("user:") # ...

  3. 从入门到自闭之python三大器--装饰器

    开放封闭原则:在不修改源代码及调用方式,对功能进行额外添加就是开放封闭原则 开放:对代码的扩展进行开发 封闭:修改源代码 装饰(额外功能) 器:工具(函数) 普通版: # print(time.tim ...

  4. python三大器(装饰器/生成器/迭代器)

    1装饰器 1.1基本结构 def 外层函数(参数): def 内层函数(*args,**kwargs); return 参数(*args,**kwargs) return 内层函数 @外层函数 def ...

  5. python装饰器的理解

    学习python,发现装饰器是一个比较难理解的地方. 下面用代码来说明. 装饰器的作用是为了切面编程(AOP).这种编程在java上有很多实现方式.下面直接说明吧: 1.作为装饰器的函数至少有两个de ...

  6. 转发对python装饰器的理解

    [Python] 对 Python 装饰器的理解的一些心得分享出来给大家参考   原文  http://blog.csdn.net/sxw3718401/article/details/3951958 ...

  7. Python基础(七) python自带的三个装饰器

    说到装饰器,就不得不说python自带的三个装饰器: 1.@property   将某函数,做为属性使用 @property 修饰,就是将方法,变成一个属性来使用. class A(): @prope ...

  8. python设计模式之装饰器详解(三)

    python的装饰器使用是python语言一个非常重要的部分,装饰器是程序设计模式中装饰模式的具体化,python提供了特殊的语法糖可以非常方便的实现装饰模式. 系列文章 python设计模式之单例模 ...

  9. python中自带的三个装饰器

    说到装饰器,就不得不说python自带的三个装饰器: 1.@property 将某函数,做为属性使用 @property 修饰,就是将方法,变成一个属性来使用. class A(): @propert ...

  10. 三分钟搞定Python中的装饰器

    python的装饰器是python的特色高级功能之一,言简意赅得说,其作用是在不改变其原有函数和类的定义的基础上,给他们增添新的功能. 装饰器存在的意义是什么呢?我们知道,在python中函数可以调用 ...

随机推荐

  1. ioc与bean管理

    IOC称之为控制反转,简单来说就是将对象的创建的权利和对象的声明周期的管理过程交给Spring框架来处理,在这个开发过程中不再需要关注对象的创建和生命周期的管理,而是在需要的时由Spring框架提供, ...

  2. 创建Maven项目时,GroupId和Artifact Id该怎么填写呢?

    1.什么是groupid和artifactId? groupid和artifactId被统称为“坐标”是为了保证项目唯一性而提出的,如果你要把你项目弄到maven本地仓库去,你想要找到你的项目就必须根 ...

  3. [转] Nginx配置性能优化

    大多数的Nginx安装指南告诉你如下基础知识——通过apt-get安装,修改这里或那里的几行配置,好了,你已经有了一个Web服务器了.而且,在大多数情况下,一个常规安装的nginx对你的网站来说已经能 ...

  4. 【转】Redis的各项功能解决了哪些问题?

    作者:Blackheart 出处:http://linianhui.cnblogs.com 先看一下Redis是一个什么东西.官方简介解释到:Redis是一个基于BSD开源的项目,是一个把结构化的数据 ...

  5. 2019-11-29-win10-UWP-Controls-by-function

    原文:2019-11-29-win10-UWP-Controls-by-function title author date CreateTime categories win10 UWP Contr ...

  6. delegate里的Invoke和BeginInvoke

    Invoke和BeginInvoke都是调用委托实体的方法,前者是同步调用,即它运行在主线程上,当Invode处理时间长时,会出现阻塞的情况,而BeginInvod是异步操作,它会从新开启一个线程,所 ...

  7. Linux 集群概念 , wsgi , Nginx负载均衡实验 , 部署CRM(Django+uwsgi+nginx), 部署学城项目(vue+uwsgi+nginx)

    Linux 集群概念 , wsgi , Nginx负载均衡实验 , 部署CRM(Django+uwsgi+nginx), 部署学城项目(vue+uwsgi+nginx) 一丶集群和Nginx反向代理 ...

  8. JAVA集合框架的特点及实现原理简介

    1.集合框架总体架构 集合大致分为Set.List.Queue.Map四种体系,其中List,Set,Queue继承自Collection接口,Map为独立接口 Set的实现类有:HashSet,Li ...

  9. js正则只能包含小写数字分割符,切不能以分割符开头和结尾

    const version = /^(?!_)(?!.*-$)[a-z0-9_]+$/; 1.一个正则表达式,只含有数字.小写字母.中划线不能以中划线开头和结尾: ^(?!-)(?!.*-$)[a-z ...

  10. c语言-数组、指针面试题

    转载 说明:所有题目均摘录于网络以及我所见过的面试题目,欢迎补充! 无特殊说明情况下,下面所有题s目都是linux下的32位C程序. 先来几个简单的热热身. 1.计算以下sizeof的值. char ...