【python学习笔记】装饰器、functools.wraps装饰器和functools.partial偏函数

感情迁移 提交于 2020-01-07 16:26:28

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

1、装饰器

      不修改被装饰函数的定义,但是可以在代码运行期间动态增加功能的方式,称之为“装饰器“,
      本质上,装饰器decorator就是一个返回函数的高阶函数

#!/usr/bin/env python
#-*- coding:utf-8 -*-
import math# 导入数学公式模块
from collections import Iterable# 判断是否为可迭代对象
import os# 导入os模块
import functools# 导入functools.wraps



# Python将一切视为object的子类,即一切都是对象,当然函数也是一个对象,可以像变量一样被传递和指向
def foo():
    print "I`m foo"
f = foo
f()
# 函数对象有一个__name__的属性,可以得到函数的名字
print foo.__name__
print f.__name__

# ★不修改被装饰函数的定义,但是可以在代码运行期间动态增加功能的方式,称之为“装饰器“,
# 例子:在函数调用前后自动打印日志
def logging1(func):# 装饰器函数logging1
    def wrapper(*args, **kw):# 可变参数和关键字参数
        print '%s is running...' % func.__name__
        return func(*args, **kw)
    return wrapper
# logging1是一个装饰器,它能接收一个函数做参数,并返回一个函数
@logging1
def foo():
    print "I`m foo"
# @符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作
foo()# 调用foo()函数
# @logging1放到foo()函数的定义处,调用被装饰函数foo,不仅会执行被装饰函数foo,还会执行logging1装饰器
# @logging1相当于执行了foo = logging1(foo)语句

# 分析:由于logging1() 是一个decorator,返回一个函数,原来的foo()函数仍然存在,只是现在同名的foo变量指向了新的函数,即在logging1()函数中返回的wrapper()函数;于是调用foo()将执行新wrapper()函数
#       wrapper()函数的参数定义是(*args, **kw),因此,wrapper() 函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数foo()

# ★有参数的装饰器,需要编写一个返回decorator的高阶函数
# 装饰器
def logging2(info):
    def decorator(func):
        def wrapper(*args, **kw):
            print '%s,%s is running...' % (info, func.__name__)
            return func(*args, **kw)
        return wrapper
    return decorator
# 用法
@logging2('Waiting')
def foo():
    print "I`m foo"
# 调用foo()函数
foo()
# 这种三层嵌套结构的装饰器,执行过程是foo = logging2('Waiting')(foo)
# 分析:首先执行logging2('Waiting'),返回的是decorator函数,再调用返回的decorator函数,参数是now函数,最终返回值是wrapper函数

运行效果:
2、内置的functools.wraps装饰器

设计模式之装饰模式decorator

使用Python内置的functools.wraps,在实现之前加上functools的wraps,它能保留原有函数的名称和docstring
Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。

# ★函数也是对象,它有__name__ 等属性,但上面两个例子经过decorator装饰之后的函数,它们的__name__ 已经从原来的'now' 变成了'wrapper'
print u'装饰后函数的名字:',foo.__name__
# 因为返回的那个函数是wrapper() ,所以,结果就是'wapper',现在需要把原始函数的__name__等属性复制到wrapper() 函数中,否则,有些依赖函数签名的代码执行就会出错。
# 可以使用wrapper.__name__ = func.__name__
def logging3(info):
    def decorator(func):
        def wrapper(*args, **kw):
            wrapper.__name__ = func.__name__
            print '%s,%s is running...' % (info, func.__name__)
            return func(*args, **kw)
        return wrapper
    return decorator

@logging3('Waiting')
def foo():
    print "I`m foo"

foo()
print u'装饰后函数的名字:',foo.__name__

# 无参装饰器使用functools.wraps
def logging4(func):# 装饰器函数logging4
    @functools.wraps(func)# 使用functools.wraps保留原来函数的信息
    def wrapper(*args, **kw):# 可变参数和关键字参数
        print '%s is running...' % func.__name__
        return func(*args, **kw)
    return wrapper
    
@logging4
def foo():
    print "I`m foo"
    
foo()
print u'无参装饰器使用functools.wraps:',foo.__name__
# 有参装饰器使用functools.wraps
def logging5(info):
    def decorator(func):
        @functools.wraps(func)# 使用functools.wraps保留原来函数的信息
        def wrapper(*args, **kw):
            print '%s,%s is running...' % (info, func.__name__)
            return func(*args, **kw)
        return wrapper
    return decorator

@logging5('Waiting')
def foo():
    print "I`m foo"

foo()
print u'有参装饰器使用functools.wraps:',foo.__name__


# ★编写一个decorator,能在函数调用的前后打印出'begin call' 和'end call' 的日志
def logging6(info):
    def decorator(func):
        @functools.wraps(func)# 使用functools.wraps保留原来函数的信息
        def wrapper(*args, **kw):
            print '%s...,begin call %s()' % (info, func.__name__)# 调用前
            result = func(*args, **kw)# 因为不能直接返回,所以需要暂存结果
            print 'end call %s()' % (func.__name__)# 调用后
            return result
        return wrapper
    return decorator
# 组建装饰器
@logging6('Waiting')
def foo():
    print "I`m foo"

foo()

# ★写出一个@log的decorator。使它既支持:@log,又支持:@log('execute')
def logging7(info):
    if isinstance(info,str):   # 有参装饰器
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*args,**kw):
                print u'%s,有参%s is running...' %(info,func.__name__)
                func(*args,**kw)
                print u'有参%s is enddning...' %(func.__name__)
            return wrapper
        return decorator
    else:                      # 无参装饰器
        @functools.wraps(info) # 此时info就是被装饰函数
        def wrapper(*args,**kw):
            print u'请等待,无参%s is running...' %(info.__name__)
            info(*args,**kw)
            print u'无参%s is enddning...' %(info.__name__)
        return wrapper
# 组建无参装饰器
@logging7
def foo():
    print "I`m foo"
    
foo()
# 组建有参装饰器
@logging7('Waiting')
def foo():
    print "I`m foo"
    
foo()

运行效果:

3、functools.partial偏函数

偏函数:通过设定参数的默认值,降低函数调用的难度

functools模块除了wraps装饰器外,还有好多其他函数,例如偏函数functools.partial,区别于数学意义上的偏函数
 

#!/usr/bin/env python
#-*- coding:utf-8 -*-
import math# 导入数学公式模块
from collections import Iterable# 判断是否为可迭代对象
import os# 导入os模块
import functools# 导入functools.wraps

 

# int():默认参数base=10
print u'默认十进制', int('12')      # 必选参数 # 结果:123456
print u'八进制数:', int ('12',base = 8)# 默认参数 # 结果:10
# base表示该字符串转换为整数时,采用的进制。而输出的是十进制
# 而如果转换大量的八进制字符串,每次都传入int (x,base = 8)是比较麻烦的,为此我们可以定义int8(x,base = 8)
def int8(x,base = 8):
    return int(x,base)
print u'八进制数:', int8('12')
# 也可以使用functools.partial,这样就不用重新def定义新函数int8()了
int8 = functools.partial(int ,base = 8)
print u'使用functools.partial的八进制数:',int8('15')

# int8函数把参数重新设定默认值为8 ,但也可以在函数调用时传入其他值。例如:int8('15',base = 16)
# 创建偏函数时,实际上可以接收函数对象、*args 和**kw 这3个参数
# int8 = functools.partial(int ,base = 8)实际上是固定了关键字参数的值为8,等效于kw = {'base' : 8}  int('15', **kw)
# max_ex = functools.partial(max ,10,12)# 会把10,12作为可变参数自动添加到右边,max_ex(1,2,3)等效于max(1,2,3,10,12)

运行效果:

★当函数的参数个数太多,使用functools.partial 可以创建一个新的函数,这个新函数可以把原函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,从而在调用时更简单

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!