CHAR.V 一等函数

在Python中,函数是一等对象。编程语言理论家把“一等对象”定义为满足下述条件的程序实体:

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

在Python中,所有函数都是一等对象。

5.1 把函数视作对象

示例 5-1 创建并测试一个函数

def factorial(n):'''return n!'''return 1 if n <2 else n * factorial(n-1)print(factorial(42))
print(factorial.__doc__)
print(type(factorial))
1405006117752879898543142606244511569936384000000000
return n!
<class 'function'>

示例5-2 展示了函数对象的“一等”本性。我们可以把factorial函数赋值给变量fact,然后通过变量名调用。我们还能把它作为参数传给map函数。map函数返回了一个可迭代对象,里面的元素是把第一个参数(一个函数)应用到第二个参数中各个元素得到的结果,(第二个参数也是可迭代对象)。

示例 5-2 别名使用函数,函数作为参数传递。

fact = factorial
print(fact)print(fact(5))
print(map(factorial,range(11)))
print(list(map(factorial,range(11))))
<function factorial at 0x0000029BE1336280>
120
<map object at 0x0000029BE1458940>
[1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]

有了一等函数,就可以使用函数式风格编程。函数式编程的特点之一就是使用高阶函数——这是下一节的话题。

5.2 高阶函数

接受函数作为参数,或者把函数作为结果返回的函数是 高阶函数(higher-order function)map函数就是一例,内置函数sorted也是:可选的key参数用于提供一个函数,应用到每个元素上进行排序。

map、filter、reduce这三个高阶函数还能见到,不过多数使用场景下都有更好的替代品。

map、filter和reduce的现成替代品

列表推导或生成器表达式具有mapfilter两个函数的功能,而且更易于阅读。

示例5-5 计算阶乘列表:map和filter与列表推导比较

def factorial(n):'''return n!'''return 1 if n < 2 else n * factorial(n - 1)fact = factorial
#   构建0!到5!的一个阶乘列表
print(list(map(fact, range(6))))
#   使用列表推导执行相同的操作
print([fact(n) for n in range(6)])
#   使用map和filter计算直到5!的奇数阶乘列表
print(list(map(fact, filter(lambda n: n % 2, range(6)))))
#   使用列表推导做相同的工作,换掉map和filter,并避免了使用lambda表达式
print([factorial(n) for n in range(6) if n % 2])
[1, 1, 2, 6, 24, 120]
[1, 1, 2, 6, 24, 120]
[1, 6, 120]
[1, 6, 120]

Python3中,mapfilter返回生成器,因此它们的直接替代品是生成器表达式。Python3reduce被放到了functools模块里了。这个函数常用于求和,但是Python2.3之后最好使用内置的sum函数,sumreduce的通用思想是把某个操作连续应用到序列的元素上,累计之前的结果,把一系列值归约成一个值。

from functools import reduce
from operator import addprint(reduce(add, range(100)))
print(sum(range(100)))

allany 也是内置的归约函数。

all(iterable)

​ 如果iterable的每个元素都是真值,返回True;all([]) 返回True。

any(iterable)

​ 只要iterable中有元素是真值,就返回True;all([]) 返回True。

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

5.3 匿名函数

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

然而,Python 简单的句法限制了 lambda 函数的定义体只能使用传表达式。换句话说,lambda 函数的定义体中不能赋值,也不能使用whiletryPython语句。在参数列表中最适合使用匿名函数。例如,示例5-7 使用lambda表达式重写了示例5-4 中排序押韵单词的示例,这样省掉了reverse函数。

示例5-7 使用lambda表达式反转拼写,然后依此给单词列表排序。

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

除了作为参数传给高阶函数之外,Python很少使用匿名函数。非平凡的lambda表达式要么难以阅读,要么无法写出。

Lundh 提出 的lambda表达式重构秘笈

如果使用lambda表达式导致一段代码很难理解,Fredrik Lundh建议像下面这样重构。

  1. 编写注释,说明lambda表达式的作用。
  2. 研究一会儿注释,并找出一个名称来概括注释。
  3. 把lambda表达式转换成def语句,使用那个名称来定义函数。
  4. 删除注释。

这几步摘自“Functional Programming HOWTO”,这是一篇必读文章。

lambda 句法只是语法糖:与def 语句一样,lambda表达式会创建函数对象。这是Python中几种可调用对象的一种。

5.4 可调用对象

除了用户定义的函数,调用运算符(即())还可以应用到其他对象上。如果想判断对象是否调用,可以使用内置的callable()函数。Python数据模型文档列出了7种可调用对象。

用户定义的函数

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

内置函数

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

内置方法

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

方法

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

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

类的实例

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

生成器函数

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

判断对象能否调用最安全的方法就是使用内置的callable()函数。

l = [callable(obj) for obj in [abs,str,13]]
print(l)
[True, True, False]

5.5 用户定义的可调用类型

任何Python对象都可以表现得像函数,为此,只需实现实例方法_call_。

示例5-8实现了BingoCage类。这个类的实例使用任何可迭代对象的构建,而且会在内部存储一个随机顺序排序的列表。调用实例会去除一个元素。

示例5-8 bingocage.py:调用BingoCage实例,从打乱的列表中取出一个元素

import randomclass BingoCage:def __init__(self, items):self._items = list(items)random.shuffle(self._items)def pick(self):try:return self._items.pop()except IndexError:raise LookupError('pick from empty BingoCage')def __call__(self):return self.pick()bingo = BingoCage(range(3))print(bingo.pick())
print(bingo())
print(callable(bingo))
1
2
True

使用_call_ 方法的类是创建函数类对象的简便方法,此时必须在内部维护一个状态,让它在多次调用之间可用,例如BingoCage中的剩余元素。装饰器就是这样。装饰器必须是函数,而且有时要在多次调用之间“记住”某些事【例如备忘(memoization)】,及缓存大的计算结果,供后面使用。

创建保有内部状态的函数,还有一种截然不同的方式——使用闭包。闭包和装饰器在第7章讨论。

下面讨论把函数视作对象处理的另一方面:运行时内省。

5.6 函数内省

使用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__']

本节讨论与函数视作对象相关的几个属性,先从_dict_ 开始。

与用户定义的常规类一样,函数使用_dict_属性存储赋予它的用户属性。这姓党羽一种基本形式的注解。一般来说,为函数随意赋予属性不是很常见的做法,但是Django框架这么做了。比如一个简短的描述:

def upper_case_name(obj):return ("%s    %s" % (obj.first_name,obj.last_name)).upper()
upper_case_name.short_description = 'Customer name'

下面重点说明函数专有而用户定义的一般对象没有的属性。计算两个属性集合的差集便能得到函数专有属性列表。

示例5-9 列出常规对象没有而函数有的属性

class C:passdef func():passobj = C()
print(sorted(set(dir(func)) - set(dir(obj))))
['__annotations__', '__call__', '__closure__', '__code__', '__defaults__', '__get__', '__globals__', '__kwdefaults__', '__name__', '__qualname__']
名称 类型 说明
annotations dict 参数和返回值的注解
call method-wrapper 实现()运算符;即可调用对象协议
closure tuple 函数闭包,即自由变量的绑定(通常是None)
code code 编译成字节码的函数元数据和函数定义体
defaults tuple 形式参数的默认值
get method-wrapper 实现只读描述符协议
globals dict 函数所在模块中的常见变量
kwdefaults dict 仅限关键字形式参数的默认值
name str 函数名称
qualname str 函数的限定名称,如Random.choice

后面几节会讨论_defaults_、_code_和_annotations_属性,IDE和框架使用它们提取关于函数签名的信息。但是,为了深入了解这些属性,要先讨论Python为声明函数形参和传入实参所提供的强大句法。

5.7 从定位参数到仅限关键字参数

Python调用函数时,使用* 和 ** “展开”可迭代对象,映射到单个参数。

示例5-10 tag函数用于生成HTML标签;使用名叫cls的关键字参数传入“class”属性,这是一种变通方法,因为“class”是Python的关键字

def tag(name, *content, cls=None, **attrs):if cls is not None:attrs['class'] = clsif attrs:attr_str = ''.join(' %s="%s"' % (attr, value)for attr, valuein 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'))
#   第一个参数后面的任意个参数会被*content捕获,存入一个元组
print(tag('p', 'hello'))
print(tag('p', 'hello', 'world'))
#   tag函数签名中没有明确当前名称的关键字参数会被**attrs捕获,存入一个元组。
print(tag('p', 'hello', id=33))
#   cls参数只能作为关键字参数传入
print(tag('p', 'hello', 'world', id=33, cls='sidebar'))
#   调用tag函数时,即便第一个定位参数也能作为关键字参数传入
print(tag(content='testing', name="img"))
#   在my_tag前面加上**,字典中所有元素都能作为单个参数传入,同名键会被绑定到对应的具名参数上,余下的则被**attrs捕获。
my_tag = {'name': 'img', 'title': 'Sunset Boulevard','src': 'sunset.jpg', 'cls': 'framed'}
print(tag(**my_tag))
<br />
<p>hello</p>
<p>hello</p>
<p>world</p>
<p id="33">hello</p>
<p class="sidebar" id="33">hello</p>
<p class="sidebar" id="33">world</p>
<img content="testing" />
<img class="framed" src="sunset.jpg" title="Sunset Boulevard" />

仅限关键字参数是Python3新增的特性。cls参数只能通过关键字参数指定,它一定不会捕获未命名的定位参数。定义函数时若想指定仅限关键字参数,要把它们放到*参数后面,如果不想支持数量不定的定位参数,但是想支持仅限关键字参数,在签名中放一个 * ,如下所示:

def f(a,*,b):return a,b
print(f(1,b=2))
#output (1,2)

5.8 获取关于参数的信息

HTTP微框架Bobo中有个使用函数内省的好例子。下面示例5-12是对Bobo教程中“Hello World”应用的改编,说明了内省怎么使用。

示例5-12 Bobo知道hello需要person参数,并且从HTTP请求中获取它

#    hello.pyimport bobo@bobo.query('/')
def hello(person):return 'Hello %s!' % person

bobo.query装饰器把一个普通的函数(如hello)与框架的请求处理机制集成起来了。装饰器会在第七章讨论,这不是这个示例的关键。这里的关键是,Bobo内省 hello函数,发现它需要一个名为perpon的参数,然后从请求中获取那个名称对应的参数,将其传给hello函数,因此程序员根本不用触碰请求对象。

安装Bobo,然后启动开发服务器,执行脚本bobo -f hello.py

然后访问http://localhost:8080/ 看到的消息是“Missing form variable person”,HTTP状态码是403.这是因为,Bobo知道调用hello函数必须传入person参数,但是在请求中找不到同名参数。下面示例5-13 在shell会话中使用curl展示了这个行为。

示例5-13 如果请求中缺少函数的参数,Bobo返回403 forbidden响应;curl -i 的作用是把首部转储到标准输出

(Getting Started) E:\project\Getting Started>curl -i http://localhost:8080/
HTTP/1.0 403 Forbidden
Date: Sat, 17 Dec 2022 08:11:25 GMT
Server: WSGIServer/0.2 CPython/3.8.5
Content-Type: text/html; charset=UTF-8
Content-Length: 103<html>
<head><title>Missing parameter</title></head>
<body>Missing form variable person</body>
</html>

示例5-14 传入所需的person参数才能得到OK响应。

(Getting Started) E:\project\Getting Started>curl -i http://localhost:8080/?person=jim
HTTP/1.0 200 OK
Date: Sat, 17 Dec 2022 08:13:09 GMT
Server: WSGIServer/0.2 CPython/3.8.5
Content-Type: text/html; charset=UTF-8
Content-Length: 10Hello jim!

Bobo是怎么知道函数需要哪个参数的呢?它又是怎么知道参数有没有默认值呢?

函数对象有个_defaults_属性,它的值是一个元组,里面保存着定位参数和关键字参数的默认值。仅限关键字参数的默认值在_kwdefaults_属性中。然而,参数的名称在_code_属性中,它的值是一个code对象引用,本身也有很多属性。

为了说明这些属性的用途,下面在clip.py 模块中定义clip 函数,如示例5-15,然后再审查它。

示例5-15 在指定长度附近截断字符串的函数

def clip(text, max_len=80):"""在max_len前面或后面的第一个空格处截断字符串的函数"""end = Noneif len(text) > max_len:space_before = text.rfind(' ', 0, max_len)if space_before >= 0:end = space_beforeelse:space_after = text.rfind(' ', max_len)if space_after >= 0:end = space_afterif end is None:  # 没找到空格end = len(text)return text[:end].rstrip()

示例5-16 审查示例5-15中定义的clip函数,查看_defaults_、code.co_varnames 和 _code.co_argcount_的值。

print(clip.__defaults__)
print(clip.__code__)
print(clip.__code__.co_varnames)
print(clip.__code__.co_argcount)
'''
(80,)
<code object clip at 0x0000021C9D4D6030, file "E:/project/Getting Started/clip.py", line 1>
('text', 'max_len', 'end', 'space_before', 'space_after')
2
'''

可以看出,这种组织信息的方式并不是最便利的。参数名称在code.co_varnames中,不过里面还有函数定义体中创建的局部变量。因此,参数名称是前N个字符串,N的值由code.co_argcount确定。顺便说一下,这里不包含前缀为*或**的变长参数。参数的默认值只能通过_defaults_元组中的位置确定,因此要从后往前扫描才能把参数和默认值对应起来。在这个示例中clip函数有两个参数,text和max_len,其中一个有默认值,即80,因此它必然属于最后一个参数,即max_len。这有违常理。

幸好我们有更好的方式——使用inspect模块。

下面来看一下示例5-17

示例5-17 提取函数的签名

sig = signature(clip)
print(sig)
print(str(sig))for name, param in sig.parameters.items():print(param.kind, ':', name, '=', param.default)'''
output:
(text, max_len=80)
(text, max_len=80)
POSITIONAL_OR_KEYWORD : text = <class 'inspect._empty'>
POSITIONAL_OR_KEYWORD : max_len = 80'''

这样就好多了。inspect.signature函数返回一个inspect.Signature对象,它有一个parameters属性,这是一个有序映射,把参数名和inspect.Parameter对象对应起来。各个parameter属性也有自己的属性,例如name、defaultkind。特殊的inspect._empty值表示没有默认值,考虑到None是有效的默认值,而且这么做是合理的。

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

POSITIONAL_OR_KEYWORD

​ 可以通过定位参数和关键字参数传入的形参(多数Python函数的参数属于此类)。

VAR_POSITIONAL

​ 定位参数元组(一个*的)

VAR_KEYWORD

​ 关键字参数字典(两个*的)

KEYWORD_ONLY

​ 仅限关键字参数

POSITIONAL_ONLY

​ 仅限定位参数;目前,Python声明函数的句法不支持,但是有些使用C语言实现且不接受关键字参数的函数(如divmod)支持。

除了name、defaultkindinspect.Parameter对象还有一个annotation(注解)属性,它的值通常是inspect._empty,但是可能包含Python3新的注解句法提供的函数签名元素数据。

inspect.Signature对象有个bind方法,它可以把任意个参数绑定到签名的形参上,所用的规则与实参到形参的匹配方式一样。框架可以使用这个方法在真正调用函数前验证参数,如示例5-18所示。

示例5-18 把tag函数的签名绑定到一个参数字典上

import inspect#  获取tag函数的签名
sig = inspect.signature(tag)
my_tag = {'name': 'img', 'title': 'Sunset Boulevard','src': 'sunset.jpg', 'cls': 'framed'}
#   把一个字典参数传给.bind()方法
bound_args = sig.bind(**my_tag)
#   得到一个inspect.BoundArguments对象
print(bound_args)
#   迭代bound_args.arguments(一个OrderedDict对象)中的元素,显示参数的名称和值。
for name,value in bound_args.arguments.items():print(name, '=', value)
#   把必须指定的参数name从my_tag参数中删除
del my_tag['name']
#   调用sig.bind(**my_tag),抛出TypeError,抱怨缺少name参数。
bound_args = sig.bind(**my_tag)
<BoundArguments (name='img', cls='framed', attrs={'title': 'Sunset Boulevard', 'src': 'sunset.jpg'})>
name = img
cls = framed
attrs = {'title': 'Sunset Boulevard', 'src': 'sunset.jpg'}
Traceback (most recent call last):File "E:/project/Getting Started/sentence.py", line 28, in <module>bound_args = sig.bind(**my_tag)File "c:\users\26591\appdata\local\programs\python\python38\lib\inspect.py", line 3025, in bindreturn self._bind(args, kwargs)File "c:\users\26591\appdata\local\programs\python\python38\lib\inspect.py", line 2940, in _bindraise TypeError(msg) from None
TypeError: missing a required argument: 'name'Process finished with exit code 1

框架和IDE等工具可以使用这些信息验证代码。Python3的另一个特性——函数注解——增进了这些信息的用途,参见下一节。

5.9 函数注解

Python3提供了一种句法,用于为函数声明中的参数和返回值附加元数据。

def clip(text: str, max_len: 'int > 0' = 80) -> str:#   1"""在max_len前面或后面的第一个空格处截断字符串的函数"""end = Noneif len(text) > max_len:space_before = text.rfind(' ', 0, max_len)if space_before >= 0:end = space_beforeelse:space_after = text.rfind(' ', max_len)if space_after >= 0:end = space_afterif end is None:  # 没找到空格end = len(text)return text[:end].rstrip()

1.有注解的函数声明。

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

print(clip.__annotations__)
'''
{'text': <class 'str'>, 'max_len': 'int > 0', 'return': <class 'str'>}
'''

示例5-20 从函数签名中提取注解

sig = signature(clip)
#   sig.return_annotation 返回值类型。
print(sig.return_annotation)
#
for param in sig.parameters.values():note = repr(param.annotation).ljust(13)print(note, ':', param.name, '=', param.default)
<class 'str'>
<class 'str'> : text = <class 'inspect._empty'>
'int > 0'     : max_len = 80

现在bobo框架好像可以支持注解进一步自动处理请求了。例如,使用price:float注解的参数可以自动把查询字符串转换成函数期待的float类型;quantity:'int > 0’这样的字符串注解可以转换成对参数的验证。

函数注解的最大影响或许不是让Bobo等框架自动设置,而是为IDE he lint程序等工具中的静态类型检查功能提供额外的类型信息。

5.10 支持函数式编程的包

得益于operatorfunctools 等包的支持,函数式编程风格也可以信手拈来。

5.10.1 operator模块

operator模块为多个运算符提供了对应的函数,从而避免编写lambda a,b:a*b这种平凡的匿名函数。使用算术运算符函数可以这样计算阶乘。

from functools import reduce
from operator import muldef fact(n):return reduce(mul, range(1, n+1))

operator 模块中还有一类函数,能替代从序列中取出元素或读取对象属性的lambda表达式:因此,itemgetterattrgetter其实会自行构建函数。

示例5-23 展示itemgetter的常见用途:根据元组的某个字段给元组列表排序。在这个示例中,按照国家代码(第二个字段)的顺序打印各个城市的信息。其实,itemgetter(1)的作用和lambda fields:fields[1] 一样:创建一个接受集合的函数,返回索引位1的元素。

示例5-23 演示使用itemgetter排序一个元组列表

from operator import itemgettermetro_data = [('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),('Delhi NCR', 'IN', 21.935, (28.613889, 77.208809)),('Mexico City', 'MX', 20.142, (35.689432, 1639.691667)),('New York-Newark', 'US', 20.104, (38.6895322, 109.691667)),('Sao Paulo', 'BR', 19.649, (323.6539722, 169.691667)),
]for city in sorted(metro_data, key=itemgetter(1)):print(city)
('Sao Paulo', 'BR', 19.649, (323.6539722, 169.691667))
('Delhi NCR', 'IN', 21.935, (28.613889, 77.208809))
('Tokyo', 'JP', 36.933, (35.689722, 139.691667))
('Mexico City', 'MX', 20.142, (35.689432, 1639.691667))
('New York-Newark', 'US', 20.104, (38.6895322, 109.691667))

如果把多个参数传给itemgetter,它构建的函数会返回提取的值构成的元组:

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')

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

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

示例5-24 定义一个namedtuple,名为metro_data,演示使用attrgetter处理它

from collections import namedtuple
from operator import attrgettermetro_data = [('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),('Delhi NCR', 'IN', 21.935, (28.613889, 77.208809)),('Mexico City', 'MX', 20.142, (35.689432, 1639.691667)),('New York-Newark', 'US', 20.104, (38.6895322, 109.691667)),('Sao Paulo', 'BR', 19.649, (323.6539722, 169.691667)),
]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]
name_lat = attrgetter('name', 'coord.lat')
for city in sorted(metro_areas, key=attrgetter('coord.lat')):print(name_lat(city))

下面是operator 模块定义的部分函数(省略了以_开头的名称,有哪位它们基本上是实现细节):

print([name for name in dir(operator) if not name.startswith('_')])
['abs', 'add', 'and_', 'attrgetter', 'concat', 'contains', 'countOf', 'delitem', 'eq', 'floordiv', 'ge', 'getitem', 'gt', 'iadd', 'iand', 'iconcat', 'ifloordiv', 'ilshift', 'imatmul', 'imod', 'imul', 'index', 'indexOf', 'inv', 'invert', 'ior', 'ipow', 'irshift', 'is_', 'is_not', 'isub', 'itemgetter', 'itruediv', 'ixor', 'le', 'length_hint', 'lshift', 'lt', 'matmul', 'methodcaller', 'mod', 'mul', 'ne', 'neg', 'not_', 'or_', 'pos', 'pow', 'rshift', 'setitem', 'sub', 'truediv', 'truth', 'xor']

这个名称中大部分的作用不言而喻。以i开头、后面是另一个运算符的那些名称(如iadd、iand等),对应的是增量赋值运算符(如+=、&=等)。如果第一个参数是可变的,那么这些运算符函数会就地修改它;否则,作用与不带i的函数一样,直接返回运算结果。

operator 模块余下的函数中,我们最后介绍一下methodcaller 。它的作用与attrgetteritemgetter类似,它会自行创建函数。methodcaller创建的函数会在对象上调用参数指定的方法。

示例5-25 methodcaller使用示例:第二个测试展示绑定额外参数的方式

from operator import methodcallers = 'The time has come'
upcase = methodcaller('upper')
print(upcase(s))
hiphenate = methodcaller('replace', ' ', '-')
print(hiphenate(s))
THE TIME HAS COME
The-time-has-come

5.10.2 使用functools.partial冻结参数

functools 模块提供了一系列的高阶函数,其中除了reduce 最有用的就是partial 及其变体,partialmethod

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

示例5-26 使用partial把一个两参数函数改编成需要单参数的可调用对象。

from operator import mul
from functools import partialtriple = partial(mul,3)
print(triple(7))
#   output 21

示例5-28 把partial 应用到示例5-10中定义的tag函数上。

from functools import partial
from sentence import tagprint(tag)
#   将第一个参数固定为'img',把cls关键字参数固定为'pic-frame'
picture = partial(tag, 'img', cls='pic-frame')
print(picture(src='wumpus.jpg'))
print(picture)
#   下面查看原函数和固定参数的属性。
print(picture.func)
print(picture.args)
print(picture.keywords)
<function tag at 0x0000018E6300F040>
<img class="pic-frame" src="wumpus.jpg" />
functools.partial(<function tag at 0x0000018E6300F040>, 'img', cls='pic-frame')
<function tag at 0x0000018E6300F040>
('img',)
{'cls': 'pic-frame'}

functools.partialmethod函数的作用与partial一样,不过是用于处理方法的。

functools模块中的lru_cache函数令人印象深刻,它会做备忘(memoization),这是一种自动优化措施,它会存储耗时的函数调用结果,避免重新计算。

5.11 本章小结

5.12 延伸阅读

18章会讲述异步编程的故事。

CHAR.V 一等函数相关推荐

  1. python【进阶】5.一等函数(注销)

    在 Python 中,函数是一等对象.编程语言理论家把"一等对象"定义为满足下述条件的程 序实体: 在运行时创建 能赋值给变量或数据结构中的元素 能作为参数传给函数 能作为函数的返 ...

  2. 读书笔记:《流畅的Python》第五章 一等函数

    # 一等对象/一等函数 ''' 1.在运行时创建 2.能赋值给变量或数据结构中的元素 3.能作为函数的参数传给函数 4.能作为函数的返回值返回结果 '''# 函数对象本身时function对象的实例d ...

  3. 函数式编程:一等函数(First-class Function)

    函数式编程:一等函数(First-class Function) 说起函数式编程,不得不提的是First-class Function的概念,有些文章把它翻译成"第一类函数",有些 ...

  4. 通过pyhton认识一等函数

    文章目录 前言 一.什么是一等函数? 二.函数看为对象 1.检查类型 2.赋值及参数传递 总结 前言 作为一名python初学者,第一次接触到了一等对象.一等函数的概念,这也是与java的不同点之一. ...

  5. C 语言中 char[] 的操作函数

    C语言中char[]的操作函数 1.赋值操作 在C语言中,char型数组是不可以直接赋值的.例如在如下的代码中,会得到错误: char c1[20] = "women"; char ...

  6. python3for metro_Fluent Python 译本 读书笔记 第5章 一等函数

    这是学习<流畅的Python>的第二天,今天希望把这一章看完,之所以跳过第二部分,是因为我对第三部分兴趣更多一些,之后再看第二部分吧.笔记不是对书的重复,只是把我接触到的任何不是特别熟悉的 ...

  7. int main(int argc,char *argv[]),主函数的参数问题

    主函数的参数 在VC++中,我们有时候看到一些程序的主函数是带有参数的,虽然我们经常用到VC++但是对主函数的参数并不是很理解: int main(int argc,char *argv[]) 对于里 ...

  8. c++ 关于char *的类库函数

    2019独角兽企业重金招聘Python工程师标准>>> 一,拷贝: char stpcpy(char *dest,const char *src) 将字符串src复制到dest ch ...

  9. python[进阶] 6.使用一等函数实现设计模式

    文章目录 6.1.1 经典的"策略"模式 6.1.2 使用函数实现"策略"模式 6.1.3 选择最佳策略:简单的 6.1.4 找出模块中的全部 6.2 &quo ...

  10. C语言*char转换int函数

    事先声明:刚学C语言,在做课程设计时这个问题折磨了我很长时间,而且网上找不到资源,于是自己写了这个函数,也恳请大佬们指正. #include<stdio.h> #include<st ...

最新文章

  1. 机器学习——深度学习之卷积神经网络(CNN)——LeNet卷积神经网络结构
  2. 同步的概念(python 版)
  3. HashMap,,ConcurrentHashMap------------------浅谈!!
  4. html 收藏网站 功能实现,网站常用的收藏网站实现代码
  5. gif一键抠图 在线_8个免费在线抠图网站 不会PS的小白也能一键抠出专业效果图...
  6. 注册一个域名需要多少钱_购买一个域名要多少钱?
  7. 手机创新何时不再“纸上谈兵”?
  8. 基于OpenCV实现的灰度图幻影坦克
  9. dell 重装linux系统_预装Linux(Ubuntu)的DELL笔记本重装Windows
  10. pythonl list 的修改元素
  11. APP开发技术方案模板
  12. activemq官方文档分析
  13. access文档合并
  14. 区块链之java(六) 合约监听
  15. Jackson 序列化 自定义注解处理Null 值
  16. 利用metaphlan2结果计算alpha多样性
  17. java整人_记录几个有趣的整蛊代码
  18. 1. 获取数据-requests.get()
  19. 1048: Gardon的幸运数字
  20. Android Studio 搜索快捷键不起作用

热门文章

  1. 【计算机毕设】基于SpringBoot 的二手图书交易系统设计与实现
  2. cellchat:细胞通讯与其可视化利器part1:单个数据集细胞通讯
  3. 国产第一颗应用在• 扩展坞• 视频集线器• 加密狗“Type-C(DP)转音频,Audio,VGA加2个HDMI”国产芯片。LT8712EXI
  4. MySQL基础知识复习(一)
  5. “大佬,求你做我的需求”|产品经理心里过不去的梗,你有吗?
  6. 毕业三年之际写给可能迷茫的你我
  7. Hadoop3.2.0 YARN ResourceManager restart
  8. 【金九银十】和网易大牛的技术面谈
  9. 计算机专业比较好的加州州立,2020美国计算机专业排名TOP30
  10. iOS Simulator运行慢悠悠,但是APP的功能又很正常