一切皆对象——Python面向对象(七)

函数与可调用对象

在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

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180625G1V6D400?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券