文 | 菊子皮(转载请注明出处)
关注公众号:
AIAS编程有道
环境:Python 3.7,VS Code
读书总结 :流畅的Python
可以看成对象的函数
Python中的函数可以看成一个对象。以下例为例有,先定义一个简单的函数,并调用和查看相关信息。
def factorial(n): # <1>
'''return n!'''
return 1 if n < 2 else n * factorial(n - 1)
print(factorial(10)) # 3628800
print(factorial.__doc__) # return n! <2>
print(type(factorial)) # <3>
说明:
<1> 创建一个计算递归的函数,并在下一行使用固定格式进行函数注释
<2>
__doc__
是函数对象众多属性中的一个
<3> factorial是function类的
实例
,这时就有对象的感觉了
补充:
__doc__
属性用于生成对象的帮助文本。可通过help(factorial)命令输出的内容。
再做进一步分析,把factorial函数赋值给变量fact,然后通过变量名调用。还能把它作为参数传给map函数。map函数返回一个可迭代对象,里面的元素是把第一个参数(一个函数)应用到第二个参数(一个可迭代对象,这里是range(11))中各个元素上得到的结果,之前也有过内建函数的介绍。
fact = factorial
print(fact) #
print(fact(8)) # 40320
print(map(factorial, range(11))) #
print(list(map(fact, range(11)))) # [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]
高阶函数
接受函数为参数,或者把函数作为结果返回的函数是高阶函数(higher-orderfunction),这也是函数式编程的特点之一。例如map函数,sorted函数(key参数用于提供一个函数)。
在函数式编程范式中,最为人熟知的高阶函数有map、filter、reduce。如果想使用不定量的参数调用函数,可以编写fn(*args, **keywords)。
可调用对象
除了用户定义的函数,调用运算符(即( ))还可以应用到其他对象上。如果想判断对象能否调用,可以使用内置的callable( )函数。Python数据模型文档列出了7种可调用对象。
用户定义的函数: 使用def语句或lambda表达式创建。
内置函数:使用C语言(CPython)实现的函数,如len或time.strftime。
内置方法:使用C语言实现的方法,如dict.get。
方法:在类的定义体中定义的函数。
类:调用类时会运行类的
__new__
方法创建一个实例,然后运行
__init__
方法,初始化实例,最后把实例返回给调用方。因为Python没有new运算符,所以调用类相当于调用函数。
类的实例:如果类定义了
__call__
方法,那么它的实例可以作为函数调用。
生成器函数:使用yield关键字的函数或方法。调用生成器函数返回的是生成器对象。
Python中有各种各样可调用的类型,因此判断对象能否调用,最安全的方法是使用内置的callable( )函数,如下:
print(abs, str, 13, sep="\t")
# 查看对象是否可以被调用
print([callable(obj) for obj in (abs, str, 13)])
# 13
# [True, True, False]
用户定义的可调用类型
不仅Python函数是真正的对象,任何Python对象都可以表现得像函数,只需实现实例方法
__call__
。如下示例:
描述:实现BingoCage类,这个类的实例使用任何可迭代对象构建,而且会在内部存储一个随机顺序排列的列表。调用实例会取出一个元素。
import random
class BingoCage:
def __init__(self, items):
self._items = list(items) # <1>
random.shuffle(self._items) # <2>
def pick(self): # <3>
try:
return self._items.pop()
except IndexError:
raise LookupError('pick from empty BingoCage') # <4>
def __call__(self): # <5>
return self.pick()
说明:
<1>
__init__
接受任何可迭代对象;在本地构建一个副本,防止列表参数的意外副作用。
<2> shuffle定能完成工作,因为self._items是列表,打乱self._items。
<3> 主要作用的方法。
<4> 如果self._items为空,抛出异常,并设定错误消息。
<5>
bingo.pick( )的快捷方式是bingo( )
。
该类的使用方法如下,注意,bingo实例可以作为函数调用,而且内置的callable(…)函数判定它是可调用的对象。
bingo = BingoCage(range(3))
print(bingo.pick()) # 1
print(bingo()) # 2
print(callable(bingo)) # True
实现
__call__
方法的类是创建函数类对象的简便方式,此时必须在内部维护一个状态,让它在调用之间可用,例如BingoCage中的剩余元素。装饰器就是这样。装饰器必须是函数,而且有时要在多次调用之间“记住”某些事[例如备忘(memoization),即缓存消耗大的计算结果,供后面使用]。
函数内省
除了
__doc__
,函数对象还有很多属性。使用dir函数可以探知factorial(
dir(factorial)
)具有下述属性:
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
其中大多数属性是Python对象共有的。
先了解一下
__dic__
。与用户定义的常规类一样,函数使用
__dict__
属性存储赋予它的用户属性。这相当于一种基本形式的
注解
(一种编程方式)。一般来说,为函数随意赋予属性不是很常见的做法。
下面重点说明函数专有而用户定义的一般对象没有的属性。计算两个属性集合的差集便能得到函数专有属性列表。如下:
class C:
pass # <1>
obj = C() # <2>
def func():
pass # <3>
print(set(dir(func)) - set(dir(obj))) # <4>
# {'__kwdefaults__', '__get__', '__closure__', '__qualname__', '__code__', '__annotations__', '__call__', '__name__', '__defaults__', '__globals__'}
说明:
<1> 创建一个空的用户定义的类
<2> 创建一个实例。
<3> 创建一个空函数。
<4> 计算差集,然后排序,得到类的实例没有而函数有的属性列表。
这些函数说明可如下表。