20.装饰器

20.1 函数基础知识

在Python中函数为一等公民,我们可以:

把函数赋值给变量

在函数中定义函数

在函数中返回函数

把函数传递给函数

20.1.1 把函数赋值给变量

在Python里,函数是对象,因此可以把它赋值给变量,如下所示:

def hello(name="Surpass"):

return "Hello,"+name.capitalize()

上述代码定义了一个函数hello(),具体功能是把对输入的姓名打招呼,并将姓名首字母转换为大写。下面将函数赋值给变量,如下所示:

func=hello

print(func)

其输出结果如下所示:

在上述代码中,将hello赋值给func并输出打印结果。从结果上来看,当不带括号使用hello时,仅输出函数对象,而非调用函数,当带上括号时,则表示调用函数,如下所示:

func=hello

print(func())

其输出结果如下所示:

Hello,Surpass

既然函数是对象,那是不是可以尝试删除对象,来看看以下代码:

def hello(name="Surpass"):

return "Hello,"+name.capitalize()

func=hello

del hello

try:

print(hello())

except Exception as ex:

print(ex)

print(func())

其输出结果如下所示:

name 'hello' is not defined

Hello,Surpass

在上面代码中,虽然hello函数已经删除,但在删除之前,已经赋值给变量func,所以不影响func的功能。因此可以得出以下总结:

函数是对象,可以将基赋值给其他变量variable,在调用时,需要添加括号variable()

20.1.2 在函数中定义函数

在Python中,可以在函数中定义函数,示例代码如下所示:

def hello(name="Surpass"):

def welcome(country="China"):

return "Hello,"+name.capitalize()+",Welcome to "+country

print(welcome())

hello()

其输出结果如下所示:

Hello,Surpass,Welcome to China

上述示例中,内部函数welcome表示欢迎来到某个国家,而且这个函数位于hello函数内部,只有通过调用hello函数时才能生效,测试代码如下所示:

def hello(name="Surpass"):

def welcome(country="China"):

return "Hello,"+name.capitalize()+",Welcome to "+country

print(welcome())

try:

print(welcome())

except Exception as ex:

print(ex)

其输出结果如下所示:

name 'welcome' is not defined

通过上面的示例代码可以得到以下总结:

在函数funcA中可以定义函数funcB,但funcB无法在函数funcA之外进行调用

20.1.3 在函数中返回函数

通过前面两个示例,函数即可以返回其值,也可以返回其函数对象,示例代码如下所示:

def hello(type=1):

def name(name="Surpass"):

return "My name is "+name

def welcome(country="China"):

return " Welcome to "+country

def occupation(occupation="engineer"):

return " I am "+occupation

if type==1:

return name

elif type==2:

return welcome

else:

return occupation

在函数hello中定义了3个函数,然后根据type参数来返回不同的信息,在hello函数中,返回都为函数对象,可视为变量,运行以下代码:

print(hello(1))

print(hello(1)())

func=hello(1)

print(func())

其输出结果如下所示:

.name at 0x000001D65783D5E8>

My name is Surpass

My name is Surpass

注意上面在写代码上的区别,如果不加括号,则代表仅返回函数对象,添加括号则代表调用函数,总结如下所示:

在函数funcA中可以定义多个函数funcB...,并且可以把funcB当成对象进行返回

20.1.4 把函数传递给函数

在Python中,既然一切都为对象,那函数也可以做参数传递给另一个函数,示例代码如下所示:

import datetime

import time

def hello(name="Surpass"):

print(f"Hello,{name}")

def testFunc(func):

print(f"current time is {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")

hello()

time.sleep(2)

print(f"current time is {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")

testFunc(hello)

其输出结果如下所示:

current time is 2020-05-17 14:58:56

Hello,Surpass

current time is 2020-05-17 14:58:58

在函数testFunc中接收参数为函数类型的函数hello,而且并未改变函数hello的任何内容,且实现在函数hello前后增加一些内容。因此我们可以总结如下所示:

函数funcA传递到函数funcB,函数funcB仅在函数funcA运行前后有操作,但不改变funcA,这就是装饰器的锥形。

20.2 装饰器

20.2.1 闭包

维基的解释如下所示:

在计算机科学中,闭包(Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

Python里面的闭包是一种高阶函数,返回值为函数对象,简单来讲就是一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外执行,这种函数称之为闭包。详细如下图所示:

在函数make_averager中,series为make_averager函数的局部变量,在averager函数中series称之为自由变量(未在本地作用域中绑定的变量)

示例如下所示:

def outter(name):

def inner():

print(f"para is {name}")

return inner

内部函数inner()可以使用外部函数outter()的参数name,最后返回内部函数对象inner。可以按照在函数中返回函数进行调用。现在我们将outter换成decorator,inner换成wrapper,如下所示:

def decorator(name):

def wrapper():

print(f"para is {name}")

return wrapper

以上这种形式就是装饰器,返回值为wrapper对象并等候调用,一旦被调用就运行 print(f"para is {name}")输出结果。对于装饰器而言,严格定义来讲,参数是函数而不是变量,常用形式如下所示:

def decorator(func):

def wrapper():

return func()

return wrapper

20.2.2 装饰器初体验

我们先来看看一个简单的示例,代码如下所示:

def hello():

print("call hello function")

def decorator(func):

def wrapper():

return func()

return wrapper

tmp=decorator(hello)

tmp()

其输出结果如下所示:

call hello function

装饰器的特性就是给原函数做装饰,但不改变原函数的内容,在以下代码中,希望在运行原函数func()之前,输出原函数的名字:

def hello():

print("call hello function")

def decorator(func):

def wrapper():

print(f"before call func name is {func.__name__}")

return func()

return wrapper

tmp=decorator(hello)

tmp()

其输出结果如下所示:

before call func name is hello

call hello function

经过前面的不断学习,相信已经初步掌握了装饰器的原理,如果每次都这样调用是不是太麻烦了,于是Python提供了一种语法糖@装饰函数写在被装饰函数上面即可。如下所示:

def decorator(func):

def wrapper():

print(f"before call func name is {func.__name__}")

return func()

return wrapper

@decorator

def hello():

print("call hello function")

hello()

其输出结果如下所示:

before call func name is hello

call hello function

上面这种写法等价于

tmp=decorator(hello)

tmp()

语法糖 (syntactic sugar):指计算机语言中添加的某种语法,对语言的功能没有影响,但是让程序员更方便地使用。

20.2.3 装饰器知识点

20.2.3.1 多个装饰器

即装饰器的特性是给函数进行装饰,那是不是一个函数可以被多个函数进行装饰,定义一个函数如下所示:

def hello(name="Surpass"):

return f"Hello,{name}"

针对以上这个函数,我们希望能完成以下功能:

字符串全部大写

将返回的字符串拆分成单个单词列表

为完成以上功能,我们定义两个装饰器如下所示:

# 装饰器一:

def toUpper(func):

def wrapper():

return func().upper()

return wrapper

# 装饰器二:

def splitStr(func):

def wrapper():

return func().split(",")

return wrapper

@splitStr

@toUpper

def hello(name="Surpass"):

return f"Hello,{name}"

print(hello())

其输出结果如下所示:

['HELLO', 'SURPASS']

以下代码等价于:

print(splitStr(toUpper(hello))())

总结一下:一个函数存在多个装饰器,顺序是按照就近原则,即哪个函数靠近被装饰函数,则优先进行装饰,上面示例中@toUpper离被装饰函数hello最近,优先运行,依次类推。

20.2.3.2 传递参数给装饰函数

装饰函数就是wrapper(),因为要调用原函数func,一旦它有参数,则需要将这些参数传递给wrapper(),示例如下所示:

1.没有参数的wrapper()

def decorator(func):

def wrapper():

funcName=func.__name__

print(f"Befor call {funcName}")

func()

print(f"After call {funcName}")

return wrapper

@decorator

def testFunc():

print(f"call function is testFunc")

testFunc()

其输出结果如下所示:

Befor call testFunc

call function is testFunc

After call testFunc

2.传固定参数个数的wrapper()

如果被装饰函数有传入参数,则在装饰函数添加参数即可。

def decorator(func):

def wrapper(args1,args2):

funcName=func.__name__

print(f"Befor call {funcName}")

func(args1,args2)

print(f"After call {funcName}")

return wrapper

@decorator

def testFunc(name,age):

print(f"Name is {name},age is {str(age)}")

testFunc("Surpass",28)

其输出结果如下所示:

Befor call testFunc

Name is Surpass,age is 28

After call testFunc

3.带返回值的wrapper()

如果被装饰函数有返回,则在装饰函数中将被装饰函数的结果赋给变量返回即可。

def decorator(func):

def wrapper(args1,args2):

funcName=func.__name__

print(f"Befor call {funcName}")

result=func(args1,args2)

print(f"After call {funcName}")

return result

return wrapper

@decorator

def testFunc(name,age):

return f"Name is {name},age is {str(age)}"

print(testFunc("Surpass",28))

其输出结果如下所示:

Befor call testFunc

After call testFunc

Name is Surpass,age is 28

4.无固定参数个数的wrapper()

在学习Python函数时,如果参数较多或无法确定参数个数,可以使用位置参数(*agrs)也可以使用关键字参数(**kwargs),示例代码如下所示:

def decorator(func):

def wrapper(*args,**kwargs):

funcName=func.__name__

print(f"Befor call {funcName}")

result=func(*args,**kwargs)

print(f"After call {funcName}")

return result

return wrapper

@decorator

def testFunc(*args,**kwargs):

sum,dicSum=0,0

for item in args:

sum+=item

for k,v in kwargs.items():

dicSum+=v

return sum,dicSum

print(testFunc(1,2,3,4,5,key1=1,key2=2,key3=5,key4=1000))

其输出结果如下所示:

Befor call testFunc

After call testFunc

(15, 1008)

20.2.3.3 functools.wrap

在装饰器中,装饰之后的函数名称会变乱,如一个函数为hello(),其名称为hello

def hello():

pass

hello.__name__

其最终的函数名为:

'hello'

而在装饰器中,在使用@装饰函数名称之后,其名称已经变成wrapper,原因也非常简单,因为我们最终的调用形式都是decorator(被装饰函数),而这个函数返回的是wrapper()函数,因此名称为wrapper。示例代码如下所示:

def decorator(func):

def wrapper():

return func()

return wrapper

@decorator

def testFunc():

pass

print(testFunc.__name__)

其输出结果如下所示:

wrapper

这种情况下,多种情况下应该不需要关注,但如果需要根据函数名来进行一些操作(如反射)则会出错。如果仍然希望保持函数的原名,可以使用functools.wraps,示例代码如下所示:

from functools import wraps

def decorator(func):

@wraps(func)

def wrapper():

return func()

return wrapper

@decorator

def testFunc():

pass

print(testFunc.__name__)

其输出结果如下所示:

testFunc

仔细看上面代码,是不是装饰函数又被装饰了一次?

20.2.3.4 传递参数给装饰器

除了可以传递参数给装饰函数(wapper),也可以传递参数给装饰函数(decorator)。来看看以下示例,在示例中,只有装饰函数有参数,装饰器将数值保留2位小数,如下所示:

def digitalFormat(func):

def wrapper(*args,**kwargs):

result=func(*args,**kwargs)

formatResult=round(result,2)

return formatResult

return wrapper

@digitalFormat

def add(a:float,b:float)->float:

return a+b

print(add(12.09,19.12345))

其输出结果如下所示:

31.21

通过装饰器,我们很快就达到要求。但有些情况,单纯的数值可能并没有太大意义,需要结合单位。假设上面示例返回为重量,则单位可能为g、kg等。那这种需求有没有解决办法了?示例代码如下所示:

def unit(unit:str)->str:

def digitalFormat(func):

def wrapper(*args,**kwargs):

result=func(*args,**kwargs)

formatResult=f"{round(result,2)} {unit}"

return formatResult

return wrapper

return digitalFormat

@unit("kg")

def add(a:float,b:float)->float:

return a+b

print(add(12.09,19.12345))

其输出结果如下所示:

31.21 kg

如果装饰饰器本身需要传递参数,那么再定义一层函数,将装饰的参数传入即可,是不是很简单。

20.2.3.5 类装饰器

1.使用类去装饰函数

前面实现的装饰器都是针对函数而言,在实际应用中,类也可以作为装饰器。在类装饰器中主要依赖函数__call__(),每调用一个类的示例时,函数__call__()就会被执行一次,示例代码如下所示:

class Count:

def __init__(self,func):

self.func=func

self.callCount=0

def __call__(self, *args, **kwargs):

self.callCount+=1

print(f"call count is {self.callCount}")

return self.func(*args,**kwargs)

@Count

def testSample():

print("Hello, Surpass")

for i in range(3):

print(f"first call {testSample()}")

其输出结果如下所示:

call count is 1

Hello, Surpass

first call None

call count is 2

Hello, Surpass

first call None

call count is 3

Hello, Surpass

first call None

2.使用函数去装饰类

def decorator(num):

def wrapper(cls):

cls.callCount=num

return cls

return wrapper

@decorator(10)

class Count:

callCount = 0

def __init__(self):

pass

def __call__(self, *args, **kwargs):

self.callCount+=1

print(f"call count is {self.callCount}")

return self.func(*args,**kwargs)

if __name__ == '__main__':

count=Count()

print(count.callCount)

其输出结果如下所示:

10

20.4.4 何时使用装饰器

这个需要据实际的需求而定。比如需要测试每个函数运行时间,此时可以使用装饰器,很轻松就能达到。另外装饰器也可应用到身价认证、日志记录和输入合理性检查等等。

20.4.5 装饰器实际案例

在实际工作中,装饰器经常会用来记录日志和时间,示例如下所示:

from functools import wraps

import logging

import time

def logType(logType):

def myLogger(func):

logging.basicConfig(filename=f"{func.__name__}.log",level=logging.INFO)

@wraps(func)

def wrapper(*args,**kwargs):

logging.info(f" {logType} Run with args:{args} and kwargs is {kwargs}")

return func(*args,**kwargs)

return wrapper

return myLogger

def myTimer(func):

@wraps(func)

def wrapper(*args,**kwargs):

startTime=time.time()

result=func(*args,**kwargs)

endTime=time.time()

print(f"{func.__name__} run time is {endTime-startTime} s ")

return result

return wrapper

@logType("Release - ")

@myTimer

def testWrapperA(name:str,color:str)->None:

time.sleep(5)

print(f"{testWrapperA.__name__} input paras is {(name,color)}")

@logType("Test - ")

@myTimer

def testWrapperB(name:str,color:str)->None:

time.sleep(5)

print(f"{testWrapperB.__name__} input paras is {(name,color)}")

if __name__ == '__main__':

testWrapperA("apple","red")

testWrapperB("apple", "red")

其输出结果如下所示:

testWrapperA input paras is ('apple', 'red')

testWrapperA run time is 5.000441789627075 s

testWrapperB input paras is ('apple', 'red')

testWrapperB run time is 5.000349521636963 s

日志记录信息如下所示:

20.4.6 小结

装饰器就是接受参数类型为函数或类并返回函数或类的函数,通过装饰器函数,在不改变原函数的基础上添加新的功能。示意图如下所示:

本文同步在微信订阅号上发布,如各位小伙伴们喜欢我的文章,也可以关注我的微信订阅号:woaitest,或扫描下面的二维码添加关注:

python装饰器-Python基础-20装饰器相关推荐

  1. python高阶函数闭包装饰器_Python_基础_(装饰器,*args,**kwargs,高阶函数,函数闭包,函数嵌套)...

    一,装饰器 装饰器:本质就是函数,功能是为其它的函数动态添加附加的功能 原则:对修改关闭对扩展开放 1.不修改被修饰函数的源代码 2.不修改被修改函数的调用方式 装饰器实现的知识储备:高阶函数,函数嵌 ...

  2. java基础类加载器_Java基础之类加载器

    1. 什么是类加载器? 加载类的工具. 2. Java虚拟机中可以安装多个类加载器,系统默认三个主要的类加载器,每个类负责加载特定位置的类: BootStrap,ExtClassLoader,AppC ...

  3. 电话拨号器java_Android基础--电话拨号器

    1.布局文件: activity_main.xml 2.MainActivity.java packagecom.itheima.callphone;importandroid.content.Int ...

  4. python中的装饰器(基础装饰器)

    文章目录 一 前置知识-高阶函数,闭包 1. 高阶函数 2. 闭包 二 函数装饰器 1. 什么是装饰器(原理)? 2. 装饰器的实现 3. 何时执行装饰器 4. wraps方法 三 类装饰器 一 前置 ...

  5. Python成长之路【第七篇】:Python基础之装饰器

    一.什么是装饰器 装饰:装饰既修饰,意指为其他函数添加新功能 器:器既函数 装饰器定义:本质就是函数,功能是为其他函数添加新功能 二.装饰器需要遵循的原则 1.不能修改装饰器的源代码(开放封闭原则) ...

  6. python装饰器_Python基础-装饰器

    作者:Zarten知乎专栏:Python基础深入详解知乎ID: Zarten简介: 互联网一线工作者,尊重原创并欢迎评论留言指出不足之处,也希望多些关注和点赞是给作者最好的鼓励 ! 概述 装饰器其实就 ...

  7. python进阶20装饰器

    原创博客地址:python进阶20装饰器 Nested functions Python允许创建嵌套函数,这意味着我们可以在函数内声明函数并且所有的作用域和声明周期规则也同样适用. 1 2 3 4 5 ...

  8. python基础学习-装饰器进阶

    #__author:"Feng Lin" #date: 2018/8/30 #装饰器进阶 # functool.wraps # 带参数的装饰器 # 多个装饰器装饰同一个函数from ...

  9. python三层装饰器-python三大神器===》装饰器

    1.认识装饰器 如果你经常看我的博客,你已经学会了python的前两大"神器'(迭代器,生成器),那么什么是装饰器呢?就如字面意义装饰器是对某个事物(通常指函数)进行装饰,让其在不修改任何内 ...

最新文章

  1. React 事件 4
  2. 航天智慧物流!智能汽车竞赛—航天赛道开始报名啦!
  3. paddle_ocr1.0入门踩坑
  4. dig+host+nslookup 域名解析命令
  5. 苹果风格ui_苹果如何使Soft-UI成为未来
  6. tensorflow源码编译教程_源码编译安装tensorflow 1.8
  7. centos tar安装mysql_centos系统通过tar.gz包安装mysql5.7.19
  8. 【Swift】iOS裁剪或者压缩后出现的白边问题
  9. Python产生随机数(转)
  10. 第二阶段团队冲刺(五)
  11. Git 存在多个漏洞,开发人员应及时更新
  12. ExecuteReader在执行有输出参数的存储过程时拿不到输出参数
  13. 【原译】一个可定制的WPF任务对话框
  14. 设计模式之----依赖倒置(Dependency inversion principle)的理解
  15. 一级域名是什么?和二级域名有什么区别?
  16. HDU-6148 Valley Numer(数位DP)
  17. abp生成proxy代理时的一些问题记录
  18. 外卖系统外卖O2O系统开发功能与开发难点介绍
  19. 最牛散户在暴跌中浮亏上亿元
  20. 无限法则服务器选择吗,PlayStation 4内置存储空间不够?可使用移动固态硬盘外置拓展...

热门文章

  1. dubbo协议原理机制 单一长连接
  2. 字符串操作:索引位置、去空格、替换字符串
  3. php一些单选、复选框的默认选择方法(示例)
  4. MS 的SPACE很不好的说
  5. [流媒体]实例解析MMS流媒体协议,下载LiveMediaVideo[4]
  6. MySQL数据库的账户管理
  7. java.lang.NoClassDefFoundError: org/apache/tomcat/util/res/StringManager
  8. Vue之vue-cli安装与简单调试
  9. JAVA中用 SQL语句操作小结
  10. [转]thinkphp 模板显示display和assign的用法