1. __ call __方法
python中一切皆对象,一个方法可以调用,称可调用对象。
一个类可以变成一个可调用对象,只要实现了__ call __方法。
class Person(object):
def __init__(self, name, gender):
self.name = name
self.gender = gender
def __call__(self, friend):
print('My name is %s...' % self.name)
print('My friend is %s...' % friend)
class Fib(object):
def __init__(self):
pass
def __call__(self,num):
a, b = 0, 1
self.l = []
for i in range(num):
self.l.append(a)
a, b = b, a+b
return self.l
def __str__(self):
return str(self.l)
p = Person('Bob', 'male')
p('Tim')
f = Fib()
print(f(10))
>>> My name is Bob...
>>> My friend is Tim...
>>> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
函数内部再定义一个函数,并且内部函数用到了外部函数作用域里的变量(enclosing),那么将这个内部函数以及用到的外部函数内的变量一起称为闭包(Closure)。装饰器(decorator)接受一个callable对象(可以是函数或者实现了call方法的类)作为参数,并返回一个callable对象,它经常用于有切面需求的场景,比如:插入日志、性能测试(函数执行时间统计)、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用
2. 函数
-
函数定义
在讨论装饰器之前先谈谈函数,因为装饰器的本质终究还是函数。
def fun(n):
print(n)
f = fun
print(f)
f(1)
>>> <function fun at 0x002DF7C8>
>>> 1
上面例子中声明了一个函数,名字叫fun,fun指向了一个函数function对象,地址在0x002DF7C8,调用函数只需给函数名加上括号(有参数就传参)
-
函数作为返回值
一直有提到的,python中一切皆对象,函数也可以作为其他函数的返回值
def fun():
return 'this is fun'
def bar():
return fun
print(bar()())
print(fun())
b = bar()
print(b)
print(fun)
>>> this is fun
>>> this is fun
>>> <function fun at 0x0074F7C8>
>>> <function fun at 0x0074F7C8>
bar()的返回值是函数对象,可以对返回值继续调用,调用bar()等价于fun,两者指向同一内存地址的对象
- 函数作为参数
def fun():
return 'this is fun'
def bar(a):
return a()
print(bar(fun))
>>> this is fun
bar接收一个参数,参数是可调用的对象,返回该对象的调用执行结果,例子中a和fun两个变量名都指向了同一个函数对象,执行a()相当于执行了fun()
3. 闭包
def outer(x):
def inner():
print(x)
return inner
closure = outer(5)
closure()
函数内部再定义一个函数,并且内部函数用到了外部函数作用域里的变量(enclosing),那么将这个内部函数以及用到的外部函数内的变量一起称为闭包(Closure)
4. 装饰器
经过上面对函数和闭包概念的了解,现在开始切入正题----装饰器
装饰器(decorator)接受一个callable对象(可以是函数或者实现了call方法的类)作为参数,并返回一个callable对象
它经常用于有切面需求的场景,比如:插入日志、性能测试(函数执行时间统计)、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
4.1 修饰无参的函数
举个简单的例子,假如执行下面的业务:
def work():
print('工作中....')
为了方便维护和调试,需要加上日志:
import datetime
def work():
print(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), '开始记录日志;')
print('工作中....')
print(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), '结束记录日志。')
work()
>>> 2019-07-12 18:29:39 开始记录日志;
>>> 工作中....
>>> 2019-07-12 18:29:39 结束记录日志。
要实现日志记录的功能就要侵入代码去修改,如果有很多个这样的函数需要加日志,name就会很麻烦,这时候装饰器就能派上用场了
import datetime
def outer(fun):
def inner():
print(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), '开始记录日志;')
fun()
print(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), '结束记录日志。')
return inner
def work():
print('工作中....')
w = outer(work)
w()
这样做没有修改原来work()里面的任何代码逻辑,其中outer函数就是一个装饰器,它接收的参数是一个函数,返回的是一个新函数的闭包,装饰器本质是一个函数,而闭包至少有里函数和外函数,那么一般写一个装饰器至少嵌套3个函数。
乍一看装饰器很麻烦,但装饰器一旦定义好,很多相同需求的函数都能使用,python中还提供了语法糖@,用在函数定义处:
@outer
def work():
print('工作中....')
work()
>>> 2019-07-12 18:39:25 开始记录日志;
>>> 工作中....
>>> 2019-07-12 18:39:25 结束记录日志。
这样一看简洁、优雅了许多,
4.2 修饰的函数带参数
def login_required(func):
def inner(*args, **kwargs):
if func.__name__ == 'f1':
print(func.__name__, ' 权限验证成功')
func(*args, **kwargs)
else:
print(func.__name__, ' 权限验证失败')
return inner
@login_required
def f1(a, b):
print('function f1, args: ', a, ',', b)
f1(10, 20)
>>> f1 权限验证成功
>>> function f1, args: 10 , 20
Python中的* args和** kwargs可以匹配任意长度的位置参数或关键字参数,可以适应不同函数参数不相同的情况。
4.3 修饰的函数有返回值
def login_required(func):
def inner(*args, **kwargs):
if func.__name__ == 'f1':
print(func.__name__, ' 权限验证成功')
func(*args, **kwargs)
else:
print(func.__name__, ' 权限验证失败')
return inner
@login_required
def f1(a, b, c):
print('function f1, args: a={}, b={}, c={}'.format(a, b, c))
return 'hello, world'
res = f1(10, 20, 30)
print(res)
>>> f1 权限验证成功
>>> function f1, args: a=10, b=20, c=30
>>> None
输出结果中多了一个None,原因是在调用f1(10, 20, 30)时,实际是调用inner(10, 20, 30),然后执行inner闭包的函数体,在执行到func(* args, ** kwargs)后,没有接收原f1函数体的返回值。当inner闭包执行完毕,Python解释器也没有发现有return语句,就默认返回None
在inner中接收func函数的返回值,然后return返回它,本示例中装饰器的inner执行完func(* args, ** kwargs)后没有其它代码了,所以可以直接修改为return func(* args, ** kwargs),如果还有其它逻辑,则用变量保存func的返回值res = func(* args, ** kwargs),inner最后一行返回return res
def login_required(func):
def inner(*args, **kwargs):
if func.__name__ == 'f1':
print(func.__name__, ' 权限验证成功')
return func(*args, **kwargs)
else:
print(func.__name__, ' 权限验证失败')
return inner
@login_required
def f1(a, b, c):
print('function f1, args: a={}, b={}, c={}'.format(a, b, c))
return 'hello, world'
res = f1(10, 20, 30)
print(res)
>>> f1 权限验证成功
>>> function f1, args: a=10, b=20, c=30
>>> hello, world
4.4 装饰器带参数
假如装饰器带参数,那么把带参数的装饰器看做一个函数,他返回的是一个无参装饰器,即在一个装饰器外面再加上一层函数
def logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
print('[日志级别 {}]: 被装饰的函数名是 {}'.format(level, func.__name__))
return func(*args, **kwargs)
return wrapper
return decorator
@logging('DEBUG') # 等价于 f1 = logging('DEBUG')(f1) ,即先执行loggin('DEBUG'),返回decorator引用(真正的装饰器),再用decorator装饰f1,返回wrapper
def f1(a, b, c):
print('function f1, args: a={}, b={}, c={}'.format(a, b, c))
return 'hello, world'
@logging('INFO')
def f2():
print('function f2...')
res = f1(10, 20, 30)
print(res)
f2()
>>> [日志级别 DEBUG]: 被装饰的函数名是 f1
>>> function f1, args: a=10, b=20, c=30
>>> hello, world
>>> [日志级别 INFO]: 被装饰的函数名是 f2
>>> function f2...
4.5 @wraps
Python装饰器(decorator)在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),为了不影响,Python的functools包中提供了一个叫wraps的decorator来消除这样的副作用。写一个decorator的时候,最好在实现之前加上functools的wrap,它能保留原有函数的名称和docstring。
- 不加@wraps
def my_decorator(func):
def wrapper(*args, **kwargs):
"""这是wrapper的文档"""
print('calling start')
func(*args, **kwargs)
print('calling end')
return wrapper
@my_decorator
def exam():
"""这是exam的文档"""
print('example....')
print(exam.__name__)
print(exam.__doc__)
>>> wrapper
>>> 这是wrapper的文档
- 加@wraps
from functools import wraps
def my_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""这是wrapper的文档"""
print('calling start')
func(*args, **kwargs)
print('calling end')
return wrapper
@my_decorator
def exam():
"""这是exam的文档"""
print('example....')
print(exam.__name__)
print(exam.__doc__)
>>> exam
>>> 这是exam的文档
4.6 基于类实现装饰器
实现了call方法,那么类实例化后的对象就是callable,即拥有了被直接调用的能力。装饰器接受一个callable对象作为参数,并返回一个callable对象,那么我们可以让类的构造函数init ()接受一个函数,然后重载call ()并返回一个函数,也可以达到装饰器函数的效果:
class logging(object):
def __init__(self, func):
self._func = func
def __call__(self, *args, **kwargs):
print('[DEBUG]: 被装饰的函数名是 {}'.format(self._func.__name__))
return self._func(*args, **kwargs)
@logging
def f1(a, b, c):
"""This is f1 function"""
print('function f1, args: a={}, b={}, c={}'.format(a, b, c))
return 'hello, world'
res = f1(10, 20, 30)
print(res)
>>> [DEBUG]: 被装饰的函数名是 f1
>>> function f1, args: a=10, b=20, c=30
>>> hello, world
如果类装饰器带参数,那么就需要在__ call __方法中返回闭包
class logging(object):
def __init__(self, level='INFO'):
self._level = level
def __call__(self, func): # 接受函数
def wrapper(*args, **kwargs):
print('[日志级别 {}]: 被装饰的函数名是 {}'.format(self._level, func.__name__))
return func(*args, **kwargs)
return wrapper # 返回闭包
@logging('DEBUG')
def f1(a, b, c):
"""This is f1 function"""
print('function f1, args: a={}, b={}, c={}'.format(a, b, c))
return 'hello, world'
res = f1(10, 20, 30)
print(res)
很多笔记直接抄的课件,但也相当于复习了一边,加深了印象,改天再找些实战练习一下