第5章 一等函数

131 阅读6分钟

一等函数

**”一等对象“**的定义:

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

把函数视作对象

In [40]: def factorial(n):
    ...:     return 1 if n < 2 else n * factorial(n-1)
    ...: 

In [41]: dir(factorial)
Out[41]: 
['__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__']

高阶函数

​ 接受函数为参数,或者把函数作为结果返回的函数就是高阶函数。例如sorted()key参数接受一个函数。

​ 常见的高阶函数有map filter reduce apply。其中apply函数在Python2.3中标记为过时,在Phthon3中移除了。前三个虽然还能见到但是大多数场景都有更好的替代品(像作者这样近乎直接使用Python3的人基本都是直接学习了其替代品,对于这三位反而是后续才了解到)。

匿名函数

lambda关键字在Python表达式内创建匿名函数。然而Python简单的句法限制了其定义体只能使用纯表达式。在参数列表中最适合使用匿名函数,除此之外Python很少使用匿名函数,因此也不要过分追求使用匿名函数。

>>> fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
>>> sorted(fruits, key=lambda word: word[::-1])
['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']

可调用对象

  1. 用户定义的函数:

    使用def语句或lambda表达式创建。

  2. 内置函数

    使用 C 语言(CPython)实现的函数,如lentime.strftime

  3. 内置方法

    使用 C 语言实现的方法,如dict.get

  4. 方法:

    在类的定义体中定义的函数

  5. 类:

    调用类时会运行类的 __new__方法创建一个实例,然后运行__init__ 方法,初始化实例,最后把实例返回给调用方。因为 Python没有 new 运算符,所以调用类相当于调用函数。(通常,调用类会创建那个类的实例,不过覆盖 __new__ 方法的话,也可能出现其他行为。

  6. 类的实例

    如果类定义了__call__方法,那么它的实例可以作为函数调用。

  7. 生成器函数

    使用yield关键字的函数或方法。调用生成器函数返回的是生成器对象。

用户定义的可调用类型

​ 任何Python对象只要实现了__call__方法便可以像函数一样被调用。

参数分类

In [6]: def func(a, b, *var, c, d, e=3, **kw):
   ...:     pass

In [7]: sig = signature(func)

In [8]: for name, param in sig.parameters.items():
   ...:     print('参数类型:%s,参数名:%s,参数默认值:%s' %(param.kind, name,
   ...:  param.default))
参数类型:POSITIONAL_OR_KEYWORD,参数名:a,参数默认值:<class 'inspect._empty'>		#定位参数或关键字参数
参数类型:POSITIONAL_OR_KEYWORD,参数名:b,参数默认值:<class 'inspect._empty'>		#定位参数或关键字参数
参数类型:VAR_POSITIONAL,参数名:var,参数默认值:<class 'inspect._empty'>			#定位参数元组
参数类型:KEYWORD_ONLY,参数名:c,参数默认值:<class 'inspect._empty'>				#仅限关键字参数
参数类型:KEYWORD_ONLY,参数名:d,参数默认值:<class 'inspect._empty'>				#仅限关键字参数
参数类型:KEYWORD_ONLY,参数名:e,参数默认值:3										#仅限关键字参数
参数类型:VAR_KEYWORD,参数名:kw,参数默认值:<class 'inspect._empty'>				#关键字参数字典

In [9]:

位置参数

位置参数是按位置顺序传参的。

默认参数

函数定义时,如果有参数,则所有的参数名都是一个关键字,如果想通过关键字的方式传参,可以使用关键字=值的方式传参,默认参数是在函数定义时就给参数传入了一个默认的参数值,如果函数调用时没有给这个参数传值,就使用默认值,如果显示的传参了,就使用新传入的值代替默认值。

可变参数

可变参数是一个形参可以接受多个实参,用 *arg 标志这是一个可变参数。可变参数的传入数是不确定的,通常由函数调用方式决定。可变参数会将所有接受的值以元组的形式传入函数体内,如果可变参数没有接受到任何值,则传入一个空元组。

可变关键字参数

可变关键字参数用双星号+参数名表示,如:kwargs,可变关键字参数接受零个或多个关键字参数,并以字典的形式传入函数体,关键字为此字典的key,关键字绑定的值为value。如果可变关键字没有接收到任何参数,则传入函数体一个空字典。

In [15]: def func(a, b, *arg, **kwargs):
    ...:     print('a=', a)
    ...:     print('b=', b)
    ...:     print('arg=', arg)
    ...:     print('kwargs=', kwargs)   

In [16]: func(1, 2, 3, 4, x=11, y=22, z=33)
a= 1
b= 2
arg= (3, 4)
kwargs= {'x': 11, 'y': 22, 'z': 33}

仅限关键字参数

仅限关键字参数就是只能传入关键字参数,不能通过其他方式传参。仅限关键字参数不可缺省(除非有默认值),且只能强制性通过关键字传参。

在定义参数时,若想指定仅限关键字参数,要把他们放在有的可变参数后面。即可变参数后面的关键字参数都是仅限关键字参数,若不想要可变参数,只要仅限关键字参数,可以省略可变参数名,只留一个单星号*表示不接受任何可变参数,它可以作为普通参数的结束标志。

函数注解

def clip(text:str, max_len:'int > 0'=80) -> str:
    pass

​ 函数声明中的各个参数可以在 : 之后增加注解表达式。如果参数有默认值,注解放在参数名和 = 号之间。如果想注解返回值,在 ) 和函数声明末尾的 : 之间添加 -> 和一个表达式。那个表达式可以是任何类型。注解中最常用的类型是类(如 str 或 int)和字符串(如 'int >0')

​ 注解不会做任何处理,只是存储在函数的__annotations__属性中。除此之外Python不做检查、不做强制、不做验证,什么操作都不做。

operator模块

itemgetter

itemgetter(1)的作用与lambda fields: fields[1]一样。

itemgetter 使用 [] 运算符,因此它不仅支持序列,还支持映射和任何实现 __getitem__ 方法的类。

>>> metro_data = [
... ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
... ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
... ('Mexico City', 'MX', 20.142, (19.433333, -99.133333)),
... ('New York-Newark', 'US', 20.104, (40.808611, -74.020386)),
... ('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833)),
... ]
>>>
>>> from operator import itemgetter
>>> for city in sorted(metro_data, key=itemgetter(1)):
... print(city)
...
('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635833))
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889))
('Tokyo', 'JP', 36.933, (35.689722, 139.691667))
('Mexico City', 'MX', 20.142, (19.433333, -99.133333))
('New York-Newark', 'US', 20.104, (40.808611, -74.020386))

>>> cc_name = itemgetter(1, 0)
>>> for city in metro_data:
... print(cc_name(city))
...
('JP', 'Tokyo')
('IN', 'Delhi NCR')
('MX', 'Mexico City')
('US', 'New York-Newark')
('BR', 'Sao Paulo')

attrgettr

attrgetteritemgetter 作用类似,它创建的函数根据名称提取对象的属性。如果把多个属性名传给 attrgetter,它也会返回提取的值构成的元组。此外,如果参数名中包含 .(点号),attrgetter 会深入嵌套对象,获取指定的属性。

# 数据接续上一块代码块
>>> from collections import namedtuple
>>> LatLong = namedtuple('LatLong', 'lat long') 
>>> Metropolis = namedtuple('Metropolis', 'name cc pop coord')
>>> metro_areas = [Metropolis(name, cc, pop, LatLong(lat, long))
... for name, cc, pop, (lat, long) in metro_data]
>>> metro_areas[0]
Metropolis(name='Tokyo', cc='JP', pop=36.933, coord=LatLong(lat=35.689722,
long=139.691667))
>>> metro_areas[0].coord.lat
35.689722

>>> from operator import attrgetter
>>> name_lat = attrgetter('name', 'coord.lat') # 定义一个 attrgetter,获取 name 属性和嵌套的 coord.lat 属性。
>>>
>>> for city in sorted(metro_areas, key=attrgetter('coord.lat')):	# 使用 attrgetter,按照纬度排序城市列表。
... print(name_lat(city))	# 使用定义的 attrgetter,只显示城市名和纬度。
...
('Sao Paulo', -23.547778)
('Mexico City', 19.433333)
('Delhi NCR', 28.613889)
('Tokyo', 35.689722)
('New York-Newark', 40.808611)

methodcaller

methodcaller会自行创建函数。methodcaller 创建的函数会在对象上调用参数指定的方法

>>> from operator import methodcaller
>>> s = 'The time has come'
>>> upcase = methodcaller('upper')
>>> upcase(s)
'THE TIME HAS COME'

>>> hiphenate = methodcaller('replace', ' ', '-')  #预先指定参数,可以简化后续的调用
>>> hiphenate(s)
'The-time-has-come'

functools.partial

functools.partial 这个高阶函数用于部分应用一个函数。部分应用是指,基于一个函数创建一个新的可调用对象,把原函数的某些参数固定。使用这个函数可以把接受一个或多个参数的函数改编成需要回调的API,这样参数更少。

>>> from operator import mul
>>> from functools import partial
>>> triple = partial(mul, 3)
>>> triple(7)