- 理解装饰器必须理解函数、闭包等概念
- 闭包后面单独讲,函数在本文是重点,从函数讲起
|
- 在Python中,函数是一等对象,需要满足以下条件:
- 在运行时创建
- 能赋值给变量或数据结构中的元素
- 能作为参数传给函数
- 能作为函数的返回结果
- 在Python中,整数、字符串和字典都是一等对象
示例
def func(): print('hello') my_func = func # 此处不要写成func() my_func() # hello func() # hello 这样的使用比比皆是,比如在pytest中的一个应用
import pytest xfail = pytest.mark.xfail # 就是这里 @xfail # 这样看就比较简洁了 def test_hello(): assert 1 if __name__ == '__main__': pytest.main(['-sv',__file__]) 较为为典型的应用就是lambda,它是匿名的,但它同样可以赋值给一个变量
my_add = lambda x,y:x+y result = my_add(1,2) print(result) # 3
示例
def double(x): return x*2 def triple(x): return x*3 def calc(funcion_name,x): return funcion_name(x) print(double(2)) # 4 print(triple(2)) # 6 print(calc(double,2)) # 4 print(calc(triple,2)) # 6 在上面的例子中你可以看到calc这个函数接收的个参数是函数名字
调用的时候你传入的是double、triple这样的名字
仔细观察代码,calc的实现其实的本意就是把个参数当做函数名,第二个参数是个参数的参数。所以本质上你可以做任何事情,只要这个函数仅接收一个参数即可
print(calc(bin,10)) # 返回的是bin(10)的结果 0b1010 print(calc(max,(2,5,3))) # 执行的是max((2,5,3)) 高阶函数如map/filter/reduce/sort等,如果你接触过,他们的参数不都是函数名吗?
我也写过一篇文章,Python函数式编程之map/filter/reduce/sorted
示例
def add(x,y): return x+y def func(): print('calling func') return add print(func()(1,2)) # 输出如下 # calling func # 3 # func() 就是 add , 跟你执行add(1,2)的效果是一样的 你也可以这样
new_add = func() print(new_add(1,2)) # calling func # 3 如果你看过前面的两篇文章,到这里就应该很熟悉了
|
除了函数是可调用的,还有很多(其实也没多少)都是可调用对象
按照流畅的python的说法,有这么多可调用对象
对普通的初学者而言其实就是函数和类,类的调用分2级,Obj()这是实例化,同时调用new和init。
new和init魔术方法,后面会单独开篇讲解,单例跟这个是息息相关的。
生成器后面也考虑单独开文章说一下。
示例代码(说明new和init)
class Person: def __new__(cls, *args, **kwargs): print('calling new') cls.instance = super().__new__(cls) return cls.instance def __init__(self): print('calling init') wuxianfeng = Person() 示例输出
calling new calling init 但此时wuxianfeng这个Person类的实例并不是可调用的对象
如果你写wuxianfeng(),会给你提示
TypeError: 'Person' object is not callable 你需要在Person类中定义一个__call__方法
class Person: ... def __call__(self, *args, **kwargs): print('callable') 此时再次执行wuxianfeng()就可以得到callable了
当然如果你执行Person()()结果也是这样的
calling new calling init callable
python提供了一个内置的callable()函数来检测对象是否可调用
print([callable(obj) for obj in (abs, str, 13)]) # [True, True, False]
|
虽然你可能已经学到装饰器三了,但请你清空下你了解的装饰器,倒也不是从0开始,带点复习
示例代码
def decorate(function_name): def inner(): print('calling inner') function_name() return inner @decorate def target(): print('calling target') target() 输出结果
calling inner calling target
根据公式,分析下执行过程
当你在执行target()的时候,由于target上有个装饰器,实际上发生的事情是target = decorate(target)
前面的target 是新的(一个变量),后面的decorate(target)中的target是你之前定义的函数
decorate(target)就会去调用decorate函数传入target参数,返回inner
卡....返回了inner,是你加了装饰器的效果,至此都没有执行函数
正是由于终的target(),就是去调用了inner(),对应的语句是
print('calling inner') function_name() # 你传入的是target就是此处的function_name
- 说一些理论
- 装饰器只是语法糖
- 装饰器可以像常规的可调用对象那样调用,其参数是另一个函数(被装饰的函数)。
- 装饰器可能会处理被装 饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。
- 装饰器的一大特性是,能把被装饰的函数替换成其他函数
- 第二个特性是,装饰器在加载模块时立即执行
关于被替换
def decorate(function_name): def inner(): print('calling inner') function_name() print('这是inner的id:',id(inner)) return inner @decorate def target(): print('calling target') print('这是target的id:',id(target)) 示例输出(你输出的id跟我肯定不一样,但2者应该是一致的,从这个角度也能看出来你执行的target不再是原来的target了)
这是inner的id: 1804087435904 这是target的id: 1804087435904
|
日常代码中还是有一些场景能看到一个函数被多个装饰器装饰的情况,比如pytest的allure
这个执行顺序就是如你所想的那般,先装饰的先执行
示例代码
def decorate1(function_name): def inner1(): print('calling inner1') function_name() return inner1 def decorate2(function_name): def inner2(): print('calling inner2') function_name() return inner2 @decorate1 @decorate2 def target(): print('calling target') target() # 输出 # calling inner1 # calling inner2 # calling target 但这种情况下的公式是怎样的呢???你知道不~
公式1
@decorate1 def target(): print('calling target') # 等价于做了一件事 target = decorate1(target) 公式2
@decorate1 @decorate2 def target(): print('calling target') # 等价于做了2件事 # 件事,注意,就近原则 target = decorate2(target) # 前面的target是新的变量,后面的target是def的初的、原始的函数 # 第二件事 target = decorate1(target) # 前面的target又是一个新的变量,后面的target是line8的前面的target # 你也可以理解为做了一件事(合并上面2行) target = decorate1(decorate2(target) ) # 近的@的先调用 不信请看
def decorate1(function_name): def inner1(): print('calling inner1') function_name() return inner1 def decorate2(function_name): def inner2(): print('calling inner2') function_name() return inner2 def target(): print('calling target') target = decorate2(decorate1(target) ) target()
- 装饰器就讲到这里了
- 会用是步,理解简单的过程是第二步,会写一个装饰器才算是基本懂了