Python基础系列之装饰器【12】

目标

  • 温故函数很多概念
  • 掌握 Python 装饰器

如果能准确的表述下侧代码的结果

就不用往下看啦,想必你已经掌握了 Python 装饰器

def outer(func):
def inner():
print("1")
print("foo---%s" % func)
return "2"
return inner

def foo():
print("3")
return "4"

print("===foo====")
print(foo)
print("===foo()====")
print(foo())
print("===outer(foo)====")
print(outer(foo))
print("===outer(foo())====")
print(outer(foo()))
print("===outer(foo())()====")
print(outer(foo())())

先认真回忆下之前函数的相关章节内容
不难但可能有点绕

===foo====
<function foo at 0x7fcae64dbea0>
===foo()====
3
4
===outer(foo)====
<function outer.<locals>.inner at 0x7fcae64dbe18>
===outer(foo())====
3
<function outer.<locals>.inner at 0x7fcae645d598>
===outer(foo())()====
3
1
foo---4
2

没答出来,或对装饰器有疑惑,那就即可开始吧

第一部分 前奏

针对于开头的那块代码逐步分析下,究竟再内存中是如何运行的❓ 执行顺序是什么❓

强烈推荐网址:代码可视化执行过程

这几行代码主要为了加深理解代码的执行过程函数名函数调用等等

结合代码与下侧图示,重温那些我们习以为常的东西。


def outer(func):
def inner():
print("1")
print("foo---%s" % func)
return "2"
return inner

def foo():
print("3")
return "4"

# 将函数 outer、foo 加载到内存中
# 以上代码到此行,未执行

print("===foo====")
# foo【函数名】;
# 执行:对应的【函数信息】;返回:🈚️
print(foo)
print("===foo()====")
# 真正执行 foo函数
# 执行:打印 3; 返回 4
print(foo())
print("===outer(foo)====")
# 将【foo函数名】传给【outer函数】,参数为 【函数名】
# 执行:将inner加载到内存,返回:🈚️
print(outer(foo))
print("===outer(foo())====")
# 调用【foo函数】,将4作为参数传给传给【outer函数】
# 执行:打印3 返回4, 打印outer函数的返回值 inner内存信息
print(outer(foo()))
print("===outer(foo())()====")
# 调用【foo函数】,将4作为参数传给传给【outer函数】,调用inner函数
# 执行:打印3 返回4,进而打印 1 、 4【func()]、2以及inner的返回值2
# over!!!
print(outer(foo())())

我觉得,先把这些看似简单的东西理清楚,对接下来,装饰器的理解更容易些

第二部分 装饰器【基础】

1 装饰器是什么

装饰器(Decorator):可以在不修改原有代码的情况下,为被装饰的对象增加新的功能或者附加限制条件或者帮助输出。原则:开放封闭

分类

  1. 函数装饰器;
  2. 类的装饰器。

最近公司也在用装饰器,不过是再 Nodejs 中用的;
举个别的例子。因为某种原因,需要对每个接口进行认证记录日志,如何快速高效的实现这样的需求 ❓

可能有几种思路

  • 在基类中写相应的方法,如果有的话;
  • 写两个函数【认证的、日志的】,在每个接口中添加;
  • 等等思路

2 简单的装饰器

但今天只能选装饰器,因为不能偏离主题啦,描述了需求就开始干吧!

def outer(func):
print("装饰器启动,自动执行")
def inner():
print("统一认证")
result = func()
print("记录日志信息")
return result
return inner

@outer
def f1():
print("接口1")
f1()

返回值:

装饰器启动,自动执行
统一认证
接口1
记录日志信息

大致和第一部分的例子有点类似,不同的是

  1. inner 函数返回了个函数
  2. 多个@outer

@outer 中的@是注解语法糖;把 f1 作为参数传给 outer,今儿满足我们的开放封闭的原则;

其解释器会解释成下面这样的语句:
*@outer <=> outer(f1)

翻译成函数调用, 我们用新的例子来对比一下,其实就是下侧这样的

def f2():
print("接口2")
outer(f2)()

'''
装饰器启动,自动执行
统一认证
接口1
记录日志信息
'''

了解了上述的调用含义, @outer <=> outer(f1)

注: 传的只是函数名

自动执行

整体的流程

  1. 自上而下执行,将函数outer在内存注册;
  2. 执行到@outer 时,将函数名f1作为参数传给@outer;
  3. 调用 outer 函数,执行打印【装饰器启动,自动执行】;
  4. 函数 inner 在内存注册,内存地址:[xxxx],返回【inner 函数名】,
  5. 将函数f1在内存注册;其实是直接指向 [xxxx]
  6. 调用 f1(), 直接调用第五步的 [xxxx], 也就是第四步的;
  7. 打印【统一认证】;
  8. result = func()执行回调【f1】函数, 打印:接口 1,没有返回值,result = None
  9. 回到inner(),打印记录日志信息;
  10. 返回 None,OVER!

:完整的有 17 步,动图还不会制作,可以前往–>代码可视化网址**,查看完整的步骤。

实在是想不出来,如何用图来表示装饰器,有相应的图示,请介绍。

比较喜欢这段代码, 挺有意思的。O(∩_∩)O 哈哈~
来源

会自动打印结果,因为采用了装饰器

def fuck(fn):
print "fuck %s!" % fn.__name__[::-1].upper()

@fuck
def wfg():
pass

第三部分 装饰器【进阶】

如果没有看明白第二部分,还是这回去,沉下心,专注的、认真的读下去,真的不难,好好分析下流程,一步一个脚印,踩实了!

只介绍最基础的代码,就是耍流氓

1 多个装饰器

下侧代码来源

#当有多个装饰器装饰一个函数时,他们的执行顺序
#观察下方的代码就会发现
def decorator_a(func):
print('Get in decorator_a')
def inner_a(*args, **kwargs):
print('Get in inner_a')
return func(*args, **kwargs)
return inner_a

def decorator_b(func):
print('Get in decorator_b')
def inner_b(*args, **kwargs):
print('Get in inner_b')
return func(*args, **kwargs)
return inner_b

@decorator_b
@decorator_a
def f(x):
print('Get in f')
return x * 2

f(10)

'''
Get in decorator_a
Get in decorator_b
Get in inner_b
Get in inner_a
Get in f
'''

调用时,函数的顺序

  1. 顺序等同于==> decorator_b(decorator_a(f)),返回的是inner_b,所以执行的时候是先执行inner_b
  2. 然后在执行【decorator_a(f)】返回的inner_a .最终在调用f(1)的时候,函数inner_b输出’Get in inner_b’
  3. 执行inner_a输出Get in decorator_a,最后执行func(),即f

2 多参多装饰器

为了更加深入的了解,装饰器,摘录酷 壳 博客上的一段代码,好像官网实例代码也不少;

def makeHtmlTag(tag, *args, **kwds):
def real_decorator(fn):
css_class = " class='{0}'".format(kwds["css_class"]) \
if "css_class" in kwds else ""
def wrapped(*args, **kwds):
return "<"+tag+css_class+">" + fn(*args, **kwds) + "</"+tag+">"
return wrapped
return real_decorator

@makeHtmlTag(tag="b", css_class="bold_css")
@makeHtmlTag(tag="i", css_class="italic_css")
def hello():
return "hello world"

print(hello())

'''
<b class='bold_css'><i class='italic_css'>hello world</i></b>
'''

3 再深入一些

表示还没有研究这段代码,先分享出来
摘录酷 壳 博客上的代码

from functools import wraps
def memo(fn):
cache = {}
miss = object()

@wraps(fn)
def wrapper(*args):
result = cache.get(args, miss)
if result is miss:
result = fn(*args)
cache[args] = result
return result

return wrapper

@memo
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)

Python函数告一段落了