本文多参考《流畅的python》,在此基础上增加了一些实例便于理解
姊妹篇
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的说法,有这么多可调用对象
可调用对象 | 说明 |
---|---|
用户定义的函数 | 使用 def 语句或 lambda 表达式创建 |
内置函数 | 使用 C 语言(CPython)实现的函数,如 len 或 time.strftime |
内置方法 | 使用 C 语言实现的方法,如 dict.get |
方法 | 在类的定义体中定义的函数 |
类 | 调用类时会运行类的 new 方法创建一个实例,然后运行 init 方法,初始化实 例,最后把实例返回给调用方。因为 Python 没有 new 运算符,所以调用类相当于调用函数。 |
类的实例 | 如果类定义了 call 方法,那么它的实例可以作为函数调用。 |
生成器函数 | 使用 yield 关键字的函数或方法。调用生成器函数返回的是生成器对象。 |
对普通的初学者而言其实就是函数和类,类的调用分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()