函数与可调用对象
在Python中,函数通常通过关键字或表达式定义:
deffunc(fn):
returnfn(5)
y=func(lambdax:x**2)
print(y)
# 25
既然在Python中,一切皆对象,那么函数自然也是一种对象,这类对象称作可调用对象。可以通过内建函数判断一个对象是否是可调用对象:
print(callable(func))
# True
print(callable(lambdax:x**2))
# True
函数作为一个对象(一等公民→
点我回忆
),它也是一些属性方法的集合,同时我们也可以动态地为函数增减属性和方法,然后将函数作为普通的对象来使用:
deffunc():
print('func')
func.a=5
deff():
print('a')
func.f=f
print(func.a)
func.f()
函数同普通的类实例一样,也有一些默认的属性来表征它的一些特性。例如,存储了用户为函数添加的一些属性:除此之外,函数有一些独有的属性,这些属性在用户自定义类中不存在。如何获得这些属性?可以通过函数:
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']
这么多属性。。。哪些才是函数所独有的而普通类不存在的呢?我们利用集合类型来找到他们,首先定义一个函数和一个类的实例:
deff():pass
classA:pass
a=A()
通过函数能够获得两个对象和的属性的集合,再做一个差集运算,即可获得有而没有的那些属性:
frompprintimportpprint
diff=set(dir(f))-set(dir(a))
pprint(diff)
# {'__annotations__',
# '__call__',
# '__closure__',
# '__code__',
# '__defaults__',
# '__get__',
# '__globals__',
# '__kwdefaults__',
# '__name__',
# '__qualname__'}
我们依次介绍一下它们的作用:
: 类型注解(→点我回忆)。
: 可调用对象协议(我们把这个属性放到后面介绍)。
:顾名思义,闭包所绑定的变量(→点我回忆闭包)。
:字节码对象。
:默认参数。
:描述符协议(这个放到类系列的后面介绍)。
:绑定的全局变量。
:关键字默认参数。
:函数名称。
:函数限定名称。
我们通过例子来依次看一下除了2和6以外的其他属性都是什么东西:
# 1. __annotations__
deffunc(a:int,b:str)->float:
return1.1
pprint(func.__annotations__)
# {'a': ,
# 'b': ,
# 'return': }
可以看到,函数的类型注解仅仅存储于属性中,仅此而已。既然涉及到闭包,那么一定是由内部函数引用了外部函数的某些变量所致。这些变量之所以在外部函数调用结束之后还存在,其核心原因便在于它们以对象的形式存在于内部函数的属性中。注意,只有当外部函数调用结束后,变量才能绑定到这个中;之后,因为外部函数调用结束,这个变量在外部函数的引用被清理掉了,它只能由属性访问到,即上例的最后一条打印。
# 4. __code__
deffunc():pass
print(func.__code__)
#
# file "C:\...\oo7.py", line 53>
Python虽然是一门解释型语言,但实际上在运行时,解释器会将代码编译成字节码,而函数所编译而成的字节码存储于属性中。这些字节码无法直接查看,需要一些标准库的帮助。但是,我们可以直接执行这些字节码,利用函数:
deffunc():
print('hi')
exec(func.__code__)
# 'hi'
# 5. __defaults__
deffunc(a=1,b=2):
print(a,b)
print(func.__defaults__)
# (1, 2)
以元组的形式将函数定义的默认位置参数(→点我回忆)存储在内。
# 7. __globals__
a=1
deffunc():pass
pprint(func.__globals__)
# {'__builtins__': ,
# '__cached__': None,
# '__doc__': None,
# '__file__': 'C:\\...\\oo7.py',
# '__loader__': ,
# '__name__': '__main__',
# '__package__': None,
# '__spec__': None,
# 'a': 1,
# 'pprint': }
可以看到,将函数所在的全局作用域的所有变量都打出来了。
# 8. __kwdefaults__
deffunc(a=1,b=2):
print(a,b)
print(func.__kwdefaults__)
# None
deffunc(a,*,b=2):
print(a,b)
print(func.__kwdefaults__)
# {'b': 2}
和不同的是,以字典的方式存储了仅限关键字参数(→点我回忆)的默认值。
# 9. __name__
deffunc():pass
print(func.__name__)
# func
存储的是函数的名称。
# 10. __qualname__
deffunc():pass
print(func.__qualname__)
# func
classA:
deffunc(self):pass
print(A.func.__qualname__)
# A.func
print(A.func.__name__)
# func
deffunc():
defnested():pass
print(nested.__qualname__)
func()
# func..nested
存储的是函数的限定性名称。所谓限定性,包含了函数定义所处的上下文。如果函数定义于全局作用域中,则和一样;如果在类内部或函数内部,则包含了点路径。
那么__call__是做何用的?
在这篇文章(→点我跳转)中,我们介绍了是可调用对象协议,也就是说,它赋予了一个对象像函数一样可被调用的能力。拥有了方法的对象可以直接通过小括号来调用,请看:
classFuncClass:
def__call__(self):
print('hi')
func=FuncClass()
func()# 这里像函数一样调用对象func
# hi
可以看到,是类的对象。当将它直接调用时,执行的就是可调用协议中的代码。而方法,也是区分一个对象是否是可调用对象的核心所在:
classA:pass
a=A()
print(callable(func))
# True
print(callable(a))
# False
前面说了,所有的自定义函数都是对象,这些函数对象的类是Python的内建类型(就像一样,唯一的区别是没有直接的标识符来标明类型)。而这些函数之所以能调用,正是应为它们具有方法:
deffunc():
print('hi')
print(type(func))
#
func.__class__.__call__(func)
# hi
func()
# hi
友情链接: https://vonalex.github.io/
欢迎关注 它不只是Python
领取专属 10元无门槛券
私享最新 技术干货