流畅的python笔记(五)一等函数

目录

前言

一、把函数视作一等对象

二、高阶函数

高阶函数定义

map、filter、reduce的现代替代品

三、匿名函数

四、可调用对象

五、用户定义的可调用类型

六、函数内省

七、从定位参数到仅限关键字参数

八、获取关于参数的信息

使用函数对象内置属性进行函数内省

使用inspect模块进行函数内省 

九、函数注解

十、支持函数式编程的包

operator模块

functools.partial


前言

“一等对象”是指满足下述条件的程序实体:

  • 在运行时创建
  • 能赋值给变量或数据结构中的元素
  • 能作为参数传给函数
  • 能作为函数的返回结果

在python中,整数、字符串、字典等都是一等对象,这跟其它编程语言一致,但是python中的所有函数也是一等对象,或者可以称之为一等函数(所有函数都是一等函数,不存在二等三等函数的说法)。

一、把函数视作一等对象

def factorial(n):
    '''returns n! good!'''
    return 1 if n < 2 else n * factorial(n - 1)

print(factorial(21))
print(factorial.__doc__)
print(type(factorial))

fact = factorial
print(fact)
print(fact(5))

print(list(map(fact, range(11))))

如上例子可看出,函数本身也是一个对象,factorial对象是function类的实例。注意在函数体开头用三个单引号括起来的是自定义的函数的帮助文档。__doc__是函数对象的一个属性,用于返回对象的帮助文档。

        可以把factorial函数赋值给变量fact,然后通过变量名调用,还能把fact作为参数传递给map函数,map函数返回一个可迭代对象,里面的元素是把第二个参数中的各个元素传递给第一个参数得到的结果。这体现了函数的一等性。

二、高阶函数

高阶函数定义

函数式编程的特点之一是使用高阶函数。高阶函数是以函数为参数,或者把函数作为结果返回的函数。上小节中的map函数就是例子。还有内置的sorted函数也是,其可选的key参数可以提供一个函数,应用到各个元素上进行排序。

        如下例子,若想根据单词的长度排序,只需把len函数传递给key参数。任何单参数函数都能作为key的实参。

函数式编程中最常用的高阶函数有map、filter、reduce等。不过现在都有很好的替代品。

map、filter、reduce的现代替代品

函数式语言通常会提供map、filter和reduce三个高阶函数。python3中,map和filter还是内置函数,但是由于引入列表推导式和生成器表达式,他们变得没那个重要了。列表推导和生成器表达式具有map和filter两个函数的功能。如下例子:

python3中,map和filter返回生成器,它们的直接替代品是生成器表达式。

python2中,reduce是内置函数,python3中放到了functools模块里了。这个函数最常用于求和。

sum和reduce的通用思想是把某个单参数函数连续应用到一个序列中的元素上,累计之前的结果,把一系列值归约成一个值。

        all和any也是内置的归约函数,all(iterable),如果iterable中每个元素都是真值,返回True,all([ ])返回True。any(iterable),只要iterable中有元素是真值,就返回True,any([ ])返回False。

        为了使用高阶函数,有时创建一次性的小型函数更遍历,这便是匿名函数存在的原因

三、匿名函数

lambda关键字在python表达式内创建匿名函数。

python中,lambda函数的定义体中只能使用纯表达式,即不能赋值,也不能使用while和try等python语句。如下例所示,lambda表达式中冒号之前部分即为形参,冒号后是一个表达式用于处理形参并返回。在python中,除了传给高阶函数,一般情况下lambda表达式只是语法糖。

四、可调用对象

除了函数,调用运算符()还可以应用到其它对象上,要判断对象能否调用,可以使用内置的callable()函数。

        python数据模型文档给出了七种可调用对象。

  • 用户定义的函数,使用def语句或者lambda表达式创建
  • 内置函数,使用C语言(CPython)实现的函数,如len。
  • 内置方法,使用C语言实现的方法,如dict.get
  • 方法,在类的定义体中定义的函数
  • ,用()调用类时会运行类的__new__方法创建一个实例,然后运行__init__方法,初始化实例,最后把实例返回给调用方。由于python没有new运算符,所以调用类相当于调用函数,但是如果覆盖__new__方法的话,也可能出现其他行为。
  • 类的实例。如果类定义了__call__方法,那么它的实例可以作为函数调用。
  • 生成器函数。使用yield关键字返回结果的函数或方法,使用生成器函数返回的是生成器对象。

类肯定是可调用的,因为为了定义对象嘛,但是类的对象就不一定是可调用的了。python中判断对象能否调用可使用内置的callable()函数。

五、用户定义的可调用类型

任何python对象都可以表现得像函数,即可调用,只需要实现实例方法__call__。

        如下例子,BingoCage类使用一个可迭代对象来实例化,并在内存存储一个随机顺序排列的列表,因为实现了__call__方法,因此每次调用实例会取出列表中的一个元素。

六、函数内省

函数内省,其实就相当于用一些方法来获取函数自身的某些性质,比如属性和方法。

除了__doc__,函数对象还有很多属性,使用dir函数可以看到函数的属性。

大多数属性是python对象共有的,但也有一些常规对象没有而函数有的属性。下面代码计算常规对象没有而函数对象有的属性。

 

        

七、从定位参数到仅限关键字参数

可以参考python 位置参数,默认参数, 可变参数,仅限关键字参数,可变关键字参数的详解及区别_littleRpl的博客-CSDN博客

python最好的特性之一是提供了极为灵活的参数处理机制,python3进一步提供了仅限关键字参数。

        python函数传参有如下几种形式:
        定位参数:一般函数传参的时候,是按照实参的位置依次传给对应的位置上的形参,而跟形参和实参的名字无关,如果某个参数有缺省值,那么有缺省值的参数要在无缺省值的参数的后边。这就是定位参数。

        关键字参数:比如函数add,add(1, 2)就是定位参数传参的方式,add(a = 1, b = 2)就是关键字参数传参的方式。

        可变参数:* 和 ** 都是传入任意个数的参数,* 是以元组的形式导入(即把多余的定位实参都以元组的形式传给*指定的形参),** 是以字典的形式导入(把多余的关键字实参以字典的形式传入**指定的形参)。

        仅限关键字参数:keywords only arguments。只能通过关键字传参,要定义仅限关键字参数,则此参数应该在参数列表中的可变参数后边,如果我们不想要可变参数,则可以省略可变参数名,而只用一个 * 占一个位置即可。可见,仅限关键字参数是没办法被多余的参数自动填充的,因为前边有可变参数。

八、获取关于参数的信息

其实就是函数内省。

使用函数对象内置属性进行函数内省

        python函数对象有个__defaults__属性,其值是元组,保存定位参数和关键字参数的默认值。仅限关键字参数在__kwdefaults__属性中。参数的名称在__code__属性中,__code__属性的值是一个code对象的引用,因此不能直接用print打印出来。可以用__code__的属性co_argument和co_varnames分别看参数的数目和参数名称。

def ppp(c = 1, e = 1, *, a, b = 3):
    print(a, b)

ppp(1,e = 1,  a = 1)
print(ppp.__defaults__)
print(ppp.__kwdefaults__)
# print(ppp.__code__) # 不能直接打印

print(ppp.__code__.co_argcount, ppp.__code__.co_varnames)

使用inspect模块进行函数内省 

from inspect import signature

def ppp(c = 1, e = 1, *, a, b = 3):
    print(a, b)

sig = signature(ppp) # 获取ppp函数的签名,其实就是ppp函数的一些元信息
print(sig)
print(str(sig))

print(type(sig.parameters))
for name, param in sig.parameters.items():
    print(param.kind, name, '=', param.name, '=', param.default)

 

signature函数的参数是我们要进行内省的函数,比如这里的ppp,signature函数返回一个inspect.Signature对象,它有一个patameters属性,这是一个有序映射类型对象,即mappingproxy类的对象,它把每个参数的名称和inspect.Parameter对象对应起来。这个Patameter对象也有自己的属性,比如name、default,annotation和kind(kind参数表示参数的类型,定位还是关键字参数,或者仅限关键字参数)。特殊的inspect._empty值表示没有默认值,之所以考虑用一个_empty是因为None很多时候是有效的默认值,因此没有默认值不能用None。

        kind属性的值是_ParameterKind类中的5个值之一,列举如下:

 

inspect.Parameter对象还有一个annotation属性,它的值通常是inspect._empty,但是可能包含python3新的注解句法提供的函数签名元数据。

inspect.Signature对象有一个bind方法,用于将任意个参数绑定到签名中的形参上(这块不懂,函数签名这里即指Signature对象sig,用bind方法可以将任意个参数绑定到sig上?,然后返回了一个BoundArguments对象,但是这样做有意义吗?又没有调用函数,传实参干嘛?),规则与形参实参匹配规则相同。

1:即获取tag函数的签名对象sig

2:将字典my_tag用sig.bind方法绑定到sig对象上

4:打印绑定得到的bound_args.arguments(是一个OrderedDict对象)中的元素。

5:删除一个必须要指定的参数name

6:因为字典my_tag中缺少了一个必须要指定的参数name,因此再次将其绑定到签名对象sig时失败了。

九、函数注解

如第一行,说明这是一个有注解的函数声明。

 注解可以用类名也可以用一个自定义的字符串。

注意函数的__annotations__属性是一个字典,其中键return的值是返回值的注解。

python对注解做的唯一的事情就是将其存储在函数的__annotations__属性中,即注解对python解释器没有任何意义。可以使用inspect.signature函数提取注解。

十、支持函数式编程的包

operator模块

上例中reduce函数有两个参数,第一个参数是一个函数对象(该函数有两个参数),这里用的是匿名函数,第二个对象是一个可迭代对象, reduce首先从可迭代对象中抽取前两个元素送入函数计算,然后将得到的结果与第三个元素再次送入函数计算。

operator模块提供了一些函数对应算数运算符,比如mul,就是乘法,因此就不用写上边那个匿名函数了。

functools.partial

functools模块提供了一系列高阶函数,最常用的是reduce。此外还有partial及其变体partialmethod。

        所谓高阶函数,也就是其参数也是函数。partial这个高阶函数用于部分应用一个函数,所谓部门应用,就是基于一个函数构建一个参数更好的函数,其实相当于固定一个参数值,然后只传入另外一些参数就行了。比如下边对乘法函数的改造,固定一个乘数,因此使用时只需要传入另一个乘数即可。

partial的用法是,第一个参数传入一个可调用对象,后边跟着任意个要固定的定位参数和关键字参数。

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值