其实这个是个大家都已比较熟悉的话题,Callable,也就是可调用的。但当接触到装饰器(decorator)的时候,表现形式复杂些时,我发现还是有必要理清一下的。
什么是Callable
任何可以使用括号操作符的目标都是可调用的。
如果我们在Python使用某个目标具有以下特点,我们就说这个目标是可调用的。
- 目标名
在Python中目标名往往都是绑定某个目标的内存地址。我们就是通过目标名最终引用到目标本身。
- 括号
当标签名后面跟着括号时,那就是说有代码被执行了
- 输入和输出
输入:也就实参,在调用是会把实参拷贝给形参,请注意,在Python中,都是拷贝的内存地址
输出:也就是返回值,在Python都是有返回值的,没有明确返回什么的情况下,返回的默认是None
注意:输入和输出也有可能是callable,但级联使用时可能一时之间有点迷糊!
怎么验证Callable
在Python中有个内置函数(built-in funtion)callable。
Help on built-in function callable in module builtins: callable(obj, /) Return whether the object is callable (i.e., some kind of function). Note that classes are callable, as are instances of classes with a __call__() method.
接下来我们callable函数运行的一些结果。
注意:对于(1).bit_length,为什么我们不能写成1.bit_length? 那是因为如果我们写成1.bit_length,Python会当成浮点型字面量去解析,碰到小数点后面的字母时,当然解析出错。此处把1放进括号里,就是告诉Python解析器,1就是一个token了。
注意:此处callable去调用callable本身也是返回True。
Callable的种类
- 内置函数
print, len, callable
- 内置方法
'abc'.upper, [1].append, (1).bit_length
- 自定义函数
我们通过def和lambda自定义的函数
- 方法
绑定到对象的方法
- 类
类也是可以调用的,通过调用类,我们生成了类的实例。
- 实例
实例也可以调用?哈哈,Python提供了一种机制,让类的实例也可以直接调用。请看以下例子。
-
其他(生成器, 协程,异步生成器等等)
Callalbe的级联调用
之前所说的内容,大家都是完全理解和掌握的。但接触装饰器(decorator)时,callable的级联调用被@语法糖隐藏了,就有可能有点犯迷糊。我们直接看一些例子吧。
-
例: @语法糖 - 不带参数
Q:print可以用作装饰器吗?
A:当然可以。只要用作装饰器的函数能解释一个函数作为输入,就可以用作装饰器,就可以用@语法糖。
Q:打印出“<function func at 0x......>”到底在什么时候运行?
A:在用@语法糖的时候,打印出“<function func at 0x......>”相当于被装饰函数的定义阶段就执行了。因为有print(func)调用,有调用就有代码被执行了,此处就是打印函数本身。
Q:为什么func变成了None? 原来func不是函数吗?
A:我们也展示了不用@语法糖时的同等代码。func原来确实是绑定到一个函数,但后来func却又绑定到由print(func)函数执行过后的返回值,print函数的返回值就是None。执行过func=print(func)代码过后,func绑定的内容变了。
Q:为什么没有“func running”输出?
A:当func绑定到一个函数时,我们从来没有调用过这个函数,也就是说这个函数体内的代码从来没被执行过,也就是说“func running”从来没被输出过。当func绑定到None后,None是不可调用的,如果这时执行func()就会产生异常了。
例:执行顺序
from functools import wraps
def dec1(fn):
print('dec1.outer: start')
@wraps(fn)
def inner(*args, **kwargs):
print('dec1.inner: start')
result = fn(*args, **kwargs)
print('dec1.inner: end')
return result
print('dec1.outer: end')
return inner
def dec2(fn):
print('dec2.outer: start')
@wraps(fn)
def inner(*args, **kwargs):
print('dec2.inner: start')
result = fn(*args, **kwargs)
print('dec2.inner: end')
return result
print('dec2.outer: end')
return inner
print('*' * 50)
@dec1
@dec2
def add(a, b): # 相当于 add = dec1(dec2(add))
print('add: start')
result = a + b
print(f'add reult: {result}')
print('add: end')
return result
print('*' * 50)
add(1, 2)
print('*' * 50)
add(3, 4)
输出如下:
请仔细参考这张内存中的关系图。
Q:dec函数中除了返回inner函数的其他代码在实际中有没有用?
A:只有当装饰其他函数的时候,这部分代码才会被运行一次。除了日志等目的之外,例如记录哪些函数被当前装饰器装饰之外,还真没见过其他特别的用途。
Q:如果不用@语法糖,直接运行dec1(dec2(add))(5, 6),代码到底是怎么运行的?
A:这个表达式(expression)是不是有点复杂?我们只要根据之前了解的关于callable的基础之上,一点一点来分析。
add
add就是一个绑定函数的变量名!当然可以作为dec2(fn)的输入
dec2(add)
此处有调用,有调用就有代码被执行。输入是add函数,输出是一个闭包函数(closure)。
dec1(dec2(add))
dec2(add)的返回值就是一个函数,dec1()函数的输入就要一个函数。
另外,此处有调用,有调用就有代码被执行。
dec1(dec2(add))输出也是一个闭包函数(closure)。
dec1(dec2(add))(5, 6)
因为dec1(dec2(add))的输出就是一个函数,我们当然可以调用啦!此处的返回是11.
我们可以把dec1(dec2(add))(5, 6)执行分成2部分,这样便于理解
- 首先是求得inner函数的部分
dec1(dec2(add))根据表达式的求值顺序,当然是先执行dec2.outer,然后在执行dec1.outer
- 然后是inner函数的执行部分
dec1(dec2(add))指向的是dec.inner函数,执行起来当然先执行dec1.inner代码,dec1.inner函数又闭包了dec2.inner函数,然后又可以执行dec2.inner函数,后来dec2.inner函数有闭包了add函数,然后有执行了add函数,最后在一步一步回退,把add的返回值回退给dec1.inner当做返回值!
在实际应用中,可能应用多个装饰器的机会也不多,即使有多个装饰器,需要特别注意顺序的机会也不多,但如果理解了这些执行顺序的细节,那我们就更加全面的了解了装饰器。