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 可以创建一个新的函数,这个新函数可以把原函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,从而在调用时更简单