在Python中,函数通常通过def关键字或lambda表达式定义:
def func(fn):
return fn(5)
y = func(lambda x: x**2)
print(y)
# 25既然在Python中,一切皆对象,那么函数自然也是一种对象,这类对象称作**可调用对象。**可以通过内建函数callable判断一个对象是否是可调用对象:
print(callable(func))
# True
print(callable(lambda x: x**2))
# True函数作为一个对象(一等公民→点我回忆),它也是一些属性方法的集合,同时我们也可以动态地为函数增减属性和方法,然后将函数作为普通的对象来使用:
def func():
print('func')
func.a = 5
def f():
print('a')
func.f = f
print(func.a)
func.f()函数同普通的类实例一样,也有一些默认的属性来表征它的一些特性。例如,__dict__存储了用户为函数添加的一些属性:
print(func.__dict__)
# {'a': 5,
# 'f': <function f at 0x0000017325D4BF28>}除此之外,函数有一些独有的属性,这些属性在用户自定义类中不存在。如何获得这些属性?可以通过dir函数:
print(dir(func))
# ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'a', 'f']这么多属性。。。哪些才是函数所独有的而普通类不存在的呢?我们利用set集合类型来找到他们,首先定义一个函数和一个类的实例:
def f(): pass
class A: pass
a = A()通过dir函数能够获得两个对象f和a的属性的集合,再做一个差集运算,即可获得f有而a没有的那些属性:
from pprint import pprint
diff = set(dir(f)) - set(dir(a))
pprint(diff)
# {'__annotations__',
# '__call__',
# '__closure__',
# '__code__',
# '__defaults__',
# '__get__',
# '__globals__',
# '__kwdefaults__',
# '__name__',
# '__qualname__'}我们依次介绍一下它们的作用:
__annotations__: 类型注解(→点我回忆)。__call__: 可调用对象协议(我们把这个属性放到后面介绍)。__closure__:顾名思义,闭包所绑定的变量(→点我回忆闭包)。__code__:字节码对象。__defaults__:默认参数。__get__:描述符协议(这个放到类系列的后面介绍)。__globals__:绑定的全局变量。__kwdefaults__:关键字默认参数。__name__:函数名称。__qualname__:函数限定名称。
我们通过例子来依次看一下除了2和6以外的其他属性都是什么东西:
# 1. __annotations__
def func(a:int, b:str) -> float:
return 1.1
pprint(func.__annotations__)
# {'a': <class 'int'>,
# 'b': <class 'str'>,
# 'return': <class 'float'>}可以看到,函数的类型注解仅仅存储于__annotations__属性中,仅此而已。
# 3. __closure__
def func():
i = 1
def funcin():
return i
return funcin
print(func().__closure__)
# (<cell at 0x0000029F54750F78:
# int object at 0x0000000073D9CEF0>,)
print(func().__closure__[0].cell_contents)
# 1既然涉及到闭包,那么一定是由内部函数引用了外部函数的某些变量所致。这些变量之所以在外部函数调用结束之后还存在,其核心原因便在于它们以cell对象的形式存在于内部函数的__closure__属性中。注意,只有当外部函数调用结束后,变量才能绑定到这个cell中;之后,因为外部函数调用结束,这个变量在外部函数的引用被清理掉了,它只能由__closure__属性访问到,即上例的最后一条打印。
# 4. __code__
def func(): pass
print(func.__code__)
# <code object func at 0x0000027E44966C90,
# file "C:\...\oo7.py", line 53>Python虽然是一门解释型语言,但实际上在运行时,解释器会将代码编译成字节码,而函数所编译而成的字节码存储于__code__属性中。这些字节码无法直接查看,需要一些标准库的帮助。但是,我们可以直接执行这些字节码,利用exec函数:
def func():
print('hi')
exec(func.__code__)
# 'hi'# 5. __defaults__
def func(a=1, b=2):
print(a, b)
print(func.__defaults__)
# (1, 2)__defaults__以元组的形式将函数定义的默认位置参数(→点我回忆)存储在内。
# 7. __globals__
a = 1
def func(): pass
pprint(func.__globals__)
# {'__builtins__': <module 'builtins' (built-in)>,
# '__cached__': None,
# '__doc__': None,
# '__file__': 'C:\\...\\oo7.py',
# '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000022169F369B0>,
# '__name__': '__main__',
# '__package__': None,
# '__spec__': None,
# 'a': 1,
# 'pprint': <function pprint at 0x0000022169FA3620>}可以看到,__globals__将函数所在的全局作用域的所有变量都打出来了。
# 8. __kwdefaults__
def func(a=1, b=2):
print(a, b)
print(func.__kwdefaults__)
# None
def func(a, *, b=2):
print(a, b)
print(func.__kwdefaults__)
# {'b': 2}和__defaults__不同的是,__kwdefaults__以字典的方式存储了仅限关键字参数(→点我回忆)的默认值。
# 9. __name__
def func(): pass
print(func.__name__)
# func__name__存储的是函数的名称。
# 10. __qualname__
def func(): pass
print(func.__qualname__)
# func
class A:
def func(self): pass
print(A.func.__qualname__)
# A.func
print(A.func.__name__)
# func
def func():
def nested(): pass
print(nested.__qualname__)
func()
# func.<locals>.nested__qualname__存储的是函数的限定性名称。所谓限定性,包含了函数定义所处的上下文。如果函数定义于全局作用域中,则__qualname__和__name__一样;如果在类内部或函数内部,__qualname__则包含了点路径。
那么,__call__是做何用的呢?
在这篇文章中,我们介绍了__call__是可调用对象协议,也就是说,它赋予了一个对象像函数一样可被调用的能力。拥有了__call__方法的对象可以直接通过小括号来调用,请看:
class FuncClass:
def __call__(self):
print('hi')
func = FuncClass()
func() # 这里像函数一样调用对象func
# hi可以看到,func是FuncClass类的对象。当将它直接调用时,执行的就是可调用协议中的代码。而__call__方法,也是区分一个对象是否是可调用对象的核心所在:
class A: pass
a = A()
print(callable(func))
# True
print(callable(a))
# False前面说了,所有的自定义函数都是对象,这些函数对象的类是Python的内建类型function(就像int一样,唯一的区别是没有直接的标识符来标明function类型)。而这些函数之所以能调用,正是应为它们具有__call__方法:
def func():
print('hi')
print(type(func))
# <class 'function'>
func.__class__.__call__(func)
# hi
func()
# hi