第三部分 把函数视作对象
第5章 一等函数
在Python中,函数是一等对象
一等对象特点:
在运行时创建
能赋值给变量或数据结构中的元素
能作为参数传给函数
能作为函数的返回结果
5.1 把函数视作对象
例5.1
def factorial(n):
"""n!"""
return 1 if n<2 else n*factorial(n-1)
print(42)
print(factorial.__doc__)
#__doc__是函数对象众多属性中的一个
print(type(factorial))
#factorial是function类的实例
print(help(factorial))
展示函数对象的一等本性:
展示函数对象的一等本性:
例5.2
#函数对象的一等本性
'''
通过别名使用函数,将函数当参数传递
'''
fact=factorial
#函数传给一个变量
fact(5)
map(fact,range(11))
#函数作为参数传递给令一个函数
下面介绍函数式编程:
函数式编程的特点之一是使用高阶函数
5.2 高阶函数
受函数为参数,或者把函数作为结果返回的函数是高阶函数
eg:
map函数
sorted()函数的key参数用于接收一个函数参数
5.3
fruits=["strawberry","fig","apple","cherry","raspberry","banana"]
new_fruits=sorted(fruits,key=len)
#任何单参数函数都可以作为key的参数
print(new_fruits)
5.4创建押韵排序
#逆序单词
def reverse(word):
return word[::-1]
#按照逆序的单词排序
new_reverse_fruits=sorted(fruits,key=reverse)
print(new_reverse_fruits)
在函数式编程范式中,最为人熟知的高阶函数有map、filter、reduce
map、filter和reduce的现代替代品
Python 3中,map和filter返回生成器(一种迭代器),因此现在它们的
直接替代品是生成器表达式
(在python2最中的替代是列表推导式)
5.5计算阶乘列表:map和filter与列表推导比较
list(map(fact,range(6)))
print([fact(n) for n in range(6)])
list(map(fact,filter(lambda n:n%2,range(6))))
[fact(n) for n in range(6) if n%2]
reduce()
把某个操作连续应用到序列的元素上,
累计之前的结果,把一系列值归约成一个值
5.6
from functools import reduce
from operator import add
reduce(add,range(100))
sum(range(100))
all和any也是内置的归约函数。
all(iterable)
如果iterable的每个元素都是真值,返回True;all([])返回True。
any(iterable)
如果iterable的任何一个元素是真值返回True
a=[1,0,1,1,1,0]
print(all(a))
print(any(a))
为了使用高阶函数,有时创建一次性的小型函数更便利。这便是匿名函数存在的原因
5.3 匿名函数
#lambda
lambda函数的定义体只能使用纯表达式
即lambda函数中不能赋值,也不能使用while、try等python语句
在参数列表中最适合使用匿名函数
5.3
fruits=["strawberry","fig","apple","cherry","raspberry","banana"]
new_reverse_fruits=sorted(fruits,key=lambda word:word[::-1])
print(new_reverse_fruits)
5.4 可调用对象
可以使用()调用运算符的对象称为可调用对象:
用户定义的函数
内置函数
内置方法
方法
类
类的实例
如果类定义了__call__方法,那么它的实例可以作为函数调用。参见5.5节。
生成器函数
判断对象能否调用,可以使用内置的callable( )函数:
[callable(obj) for obj in (abs,str,13)]
5.5 用户定义的可调用类型
任何Python对象都可以表现得像函数
只需实现实例方法__call__
import random
class BingoCage:
def __init__(self,items):
self._items=list(items)
random.shuffle(self._items)
def pick(self):
try:
return self._items.pop()
except IdexError:
raise LookupError("pick from empty BingoCage")
def __call__(self):
return self.pick()
#通过重写__call__方法,类对象成为可调用对象,和函数具有相同功能
bingo=BingoCage(range(3))
a=bingo()
print(a)
print(callable(bingo))
注意,使用__call__是穿件类函数对象的快速方法,
此时必须在内部维护一个状态,在多次调用时记住一些数据。
比如这里的self._items
穿件保有内部状态的函数还有一种方式:闭包
5.6 函数内省
用dir函数可以探知factorial具有下述属性:
dir(fact)
['__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__']
示例5-9 列出常规对象没有而函数有的属性
class C:pass
obj=C()
def fuc():pass
print(sorted(set(dir(fuc))-set(dir(obj))))
['__annotations__', '__call__', '__closure__',
#参数和返回值的注解,实现()运算符,函数闭包,自由变量的绑定
'__code__', '__defaults__', '__get__',
#形式参数的默认值 #实现只读描述协议
'__globals__', '__kwdefaults__',
#函数所在模块中的全局变量 , 关键字形式的形参默认值
'__name__', '__qualname__']
#函数名字,函数的限定名字
5.7 从定位参数到仅限关键字参数
tag函数:
def tag(name,*content,cls=None,**attrs):
"""生成一个或多个HTML标签"""
#name定位参数,传入第一个位置的非关键字形参
#*content,截取其余非关键字形参
#cls,获取特定关键字传参。
#**attrs获取其余关键字传参
if cls is not None:
attrs["class"]=cls
#添加类属性标签
if attrs:
attr_str="".join('%s="%s"'%(attr,value) for attr,value in sorted(attrs.items()))
#添加其他属性标签
else:
attr_str=''
#属性标签为空
if content:
return '\n'.join('<%s%s>%s</%s>'%(name,attr_str,c,name) for c in content)
else:
return '<%s%s />'%(name,attr_str)
print(tag("br"))
print("p","hello")
仅限关键字参数:
def f(a,*c,b):
return a,c,b
#*c会获得所有除了第一个位置的位置参数,
#所以b只能通过关键字传参
print(f(1,2,3,4,5,b=6))
如果不想定义数量不定的定位传参
就写如下:
def f(a,*,b):
return a,b
print(f(1,b=6))
5.8 获取关于参数的信息
函数对象有个__defaults__属性,它的值是一个元组,里面保存着定位参数和关键字参数的默认值。仅限关键字参数的默认值在__kwdefaults__属性中。然而,参数的名称在__code__属性中,它的值是一个code对象引用,自身也有很多属性。
幸好,我们有更好的方式——使用inspect模块。
示例5-17 提取函数的签名[插图]
就好多了。inspect.signature函数返回一个inspect.Signature对象,它有一个parameters属性,这是一个有序映射,把参数名和inspect.Parameter对象对应起来。各个Parameter属性也有自己的属性,例如name、default和kind。特殊的inspect._empty值表示没有默认值,考虑到None是有效的默认值(也经常这么做),而且这么做是合理的。
inspect.Signature对象有个bind方法,它可以把任意个参数绑定到签名中的形参上,所用的规则与实参到形参的匹配方式一样。框架可以使用这个方法在真正调用函数前验证参数,如示例5-18所示。
这个示例在inspect模块的帮助下,展示了Python数据模型把实参绑定给函数调用中的形参的机制,这与解释器使用的机制相同。框架和IDE等工具可以使用这些信息验证代码。Python 3的另一个特性——函数注解——增进了这些信息的用途,
5.9 函数注解
示例5-19 有注解的clip函数
函数声明中的各个参数可以在:之后增加注解表达式。如果参数有默认值,注解放在参数名和=号之间。如果想注解返回值,在)和函数声明末尾的:之间添加->和一个表达式。
注解不会做任何处理,只是存储在函数的__annotations__属性(一个字典)中
说,注解对Python解释器没有任何意义。注解只是元数据,可以供IDE、框架和装饰器等工具使用。
5.10 支持函数式编程的包
operator和functools
5.10.1 operator模块
示例5-21 使用reduce函数和一个匿名函数计算阶乘
itemgetter和attrgetter其实会自行构建函数。
根据元组的某个字段给元组列表排序
itemgetter使用[]运算符,因此它不仅支持序列,还支持映射和任何实现__getitem__方法的类。
5.10.2 使用functools.partial冻结参数