流畅的python——5 一等函数

五、一等函数

一等对象:满足以下条件的程序实体

1 在运行时创建

2 能赋值给变量或数据结构中的元素

3 能作为参数传给函数

4 能作为函数的返回结果

In [9]: def f(n):  # 这是一个控制台会话,因此我们是在“运行时”创建一个函数。
   ...:     """returns n!"""
   ...:     return 1 if n < 2 else n*f(n-1)
   ...:

In [10]: f(2)
Out[10]: 2

In [11]: f.__doc__
Out[11]: 'returns n!'

In [12]: type(f)  # 函数对象本身是 function 类的对象
Out[12]: function
    
In [13]: help(f)
Help on function f in module __main__:

f(n)
    returns n!

高阶函数

定义:接受函数为参数,或者把函数作为结果返回的函数。higher-orderfunction

比如,map,sorted

对列表元素的反向进行排序

In [16]: fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']

In [17]: sorted(fruits,key=reversed)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-17-c343a7d37792> in <module>
----> 1 sorted(fruits,key=reversed)

TypeError: '<' not supported between instances of 'reversed' and 'reversed'

In [18]: reversed('fig')  # 是个迭代器
Out[18]: <reversed at 0x1ae6c881048>
    
In [19]: list(reversed('fig'))
Out[19]: ['g', 'i', 'f']

正确实现:

In [20]: def reverse(word):
    ...:     return word[::-1]
    ...:

In [21]: sorted(fruits,key=reverse)
Out[21]: ['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']
    
# lambda
In [24]: sorted(fruits,key = lambda x : x[::-1])
Out[24]: ['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']

在函数式编程范式中,最为人熟知的高阶函数有 map、filter、reduce 和 apply。apply 函数在 Python 2.3 中标记为过时,在 Python 3 中移除了,因为不再需要它了。如果想使用不定量的参数调用函数,可以编写 fn(*args, **keywords),不用再编写 apply(fn, args, kwargs)。

mapfilterreduce的现代替代品

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

使用列表推导做相同的工作,换掉 map 和 filter,并避免了使用 lambda 表达式。

在 Python 3 中,map 和 filter 返回生成器(一种迭代器),因此现在它们的直接替代品是生成器表达式(在 Python 2 中,这两个函数返回列表,因此最接近的替代品是列表推导)。

在 Python 2 中,reduce 是内置函数,但是在 Python 3 中放到 functools 模块里了。这个函数最常用于求和,自 2003 年发布的 Python 2.3 开始,最好使用内置的 sum 函数。在可读性和性能方面,这是一项重大改善。

归约函数

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

all 和 any 也是内置的归约函数

all(iterable)

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

any(iterable)

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

匿名函数

lambda 关键字

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

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

(1) 编写注释,说明 lambda 表达式的作用。

(2) 研究一会儿注释,并找出一个名称来概括注释。

(3) 把 lambda 表达式转换成 def 语句,使用那个名称来定义函数。

(4) 删除注释。

这几步摘自“Functional Programming HOWTO”(https://docs.python.org/3/howto/functional.html),这是一篇必读文章。

lambda 句法只是语法糖:与 def 语句一样,lambda 表达式会创建函数对象。

可调用对象

如果想判断对象能否调用,可以使用内置的 callable() 函数。Python 中有各种各样可调用的类型,因此判断对象能否调用,最安全的方法是使用内置的 callable() 函数:

In [28]: abs , str , 13
Out[28]: (<function abs(x, /)>, str, 13)

In [29]: [callable(obj) for obj in (abs, str, 13)]
Out[29]: [True, True, False]

Python 数据模型文档列出了 7 种可调用对象。

用户定义的函数

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

内置函数

  使用 C 语言(CPython)实现的函数,如 len 或 time.strftime。

内置方法

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

方法

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

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

类的实例

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

生成器函数

  使用 yield 关键字的函数或方法。调用生成器函数返回的是生成器对象。生成器函数在很多方面与其他可调用对象不同,详情参见第 14 章。生成器函数还可以作为协程。

用户定义的可调用类型

不仅 python函数 是真正的对象,任何 python对象 都可以表现的像函数。只需要实现 __call__ 方法。

In [30]: a = [1,2,3]

In [31]: b = list(a)  # 浅拷贝

In [32]: id(a)
Out[32]: 1848381703304

In [33]: id(b)
Out[33]: 1848380681032

In [34]: id(a[0])
Out[34]: 1831567472

In [35]: id(b[0])
Out[35]: 1831567472
    
In [36]: a = [[1,],2]

In [37]: b = list(a)

In [38]: b
Out[38]: [[1], 2]

In [39]: b[0].append(333)

In [40]: a
Out[40]: [[1, 333], 2]
In [51]: class 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('----------------')
    ...:     def __call__(self):
    ...:         return self.pick()
    ...:

In [52]: b = BingoCage([1,2,3])

In [53]: b._items
Out[53]: [3, 1, 2]

In [54]: b()
Out[54]: 2
    
In [55]: callable(b)
Out[55]: True

函数内省

使用 dir 函数可以探知函数属性。

函数使用 __dict__ 存储赋予它的用户属性。

In [58]: BingoCage.__dict__  # 类的属性
Out[58]:
mappingproxy({'__module__': '__main__',
              '__init__': <function __main__.BingoCage.__init__(self, items)>,
              'pick': <function __main__.BingoCage.pick(self)>,
              '__call__': <function __main__.BingoCage.__call__(self)>,
              '__dict__': <attribute '__dict__' of 'BingoCage' objects>,
              '__weakref__': <attribute '__weakref__' of 'BingoCage' objects>,
              '__doc__': None})

In [59]: b
Out[59]: <__main__.BingoCage at 0x1ae5c205588>

In [60]: b.__dict__  # 对象的属性
Out[60]: {'_items': [3, 1]}

一般来说,为函数随意赋予属性不是很常见的做法,但是 Django 框架这么做了。参见“The Django admin site”文档(https://docs.djangoproject.com/en/1.10/ref/contrib/admin/)中对short_description、boolean 和 allow_tags 属性的说明。这篇 Django 文档中举了下述示例,把 short_description 属性赋予一个方法,Django 管理后台使用这个方法时,在记录列表中会出现指定的描述文本:

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

函数专有的 用户定义对象没有 的属性。

>>> class C: pass
>>> obj = C()
>>> def func(): pass
>>> sorted(set(dir(func)) - set(dir(obj)))
['__annotations__', '__call__', '__closure__', '__code__', '__defaults__',
'__get__', '__globals__', '__kwdefaults__', '__name__', '__qualname__']

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

在 my_tag 前面加上 **,字典中的所有元素作为单个参数传入,同名键会绑定到对应的具名参数上,余下的则被 **attrs 捕获。

>>> my_tag = {'name': 'img', 'title': 'Sunset Boulevard',
... 'src': 'sunset.jpg', 'cls': 'framed'}
>>> tag(**my_tag)

定义函数时若想指定仅限关键字参数,要把它们放到前面有 * 的参数后面。如果不想支持数量不定的定位参数,但是想支持仅限关键字参数,在签名中放一个 *,如下所示:

In [69]: def f(a,*,b):
    ...:     return a,b
    ...:

In [70]: f(1,2)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-70-f7d8b0a791fb> in <module>
----> 1 f(1,2)

TypeError: f() takes 1 positional argument but 2 were given

In [72]: f(1,b=2)
Out[72]: (1, 2)

注意,仅限关键字参数不一定要有默认值,可以像上例中 b 那样,强制必须传入实参

获取关于参数的信息

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

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

import bobo

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

bobo.query 装饰器把一个普通的函数(如 hello)与框架的请求处理机制集成起来了。

Bobo 会内省 hello 函数,发现它需要一个名为 person 的参数,然后从请求中获取那个名称对应的参数,将其传给 hello 函数,因此程序员根本不用触碰请求对象。

如果不传参数:403

curl -i 的作用是把首部转储到标准输出

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

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

>>> from clip import clip
>>> clip.__defaults__
(80,)
>>> clip.__code__ # doctest: +ELLIPSIS
<code object clip at 0x...>
>>> clip.__code__.co_varnames
('text', 'max_len', 'end', 'space_before', 'space_after')
>>> clip.__code__.co_arg
2

参数名称在 __code__.co_varnames中,不过里面还有函数定义体中创建的局部变量。

因此,参数名称是前 N 个字符串,N的值由 __code__.co_argcount 确定。

参数的默认值只能通过它们在 __defaults__元组中的位置确定,因此要从后向前扫描才能把参数和默认值对应起来。

In [75]: def ff(a,*args,b=2,**kwargs):
    ...:     local_x = 1
    ...:     return local_x
    ...:

In [76]: ff.__defaults__

In [77]: ff
Out[77]: <function __main__.ff(a, *args, b=2, **kwargs)>

In [78]: ff.__defaults__

In [81]: ff.__code__
Out[81]: <code object ff at 0x000001AE6C8A6030, file "<ipython-input-75-a87ef9713c08>", line 1>

In [82]: ff.__code__.co_varnames
Out[82]: ('a', 'b', 'args', 'kwargs', 'local_x')

In [86]: ff.__code__.co_argcount  # 发现参数数量是 1,有 *args 影响了统计
Out[86]: 1
    
    
In [88]: def dd(a,b=2):
    ...:     x = 1
    ...:     return x
    ...:

In [89]: dd.__defaults__
Out[89]: (2,)

In [90]: dd.__code__.co_varnames
Out[90]: ('a', 'b', 'x')

In [91]: dd.__code__.co_argcount  # 参数等属性正常
Out[91]: 2

以上方式,不太方便。

inspect 模块

提取函数的签名

In [92]: from inspect import signature

In [93]: sig = signature(ff)

In [94]: ff
Out[94]: <function __main__.ff(a, *args, b=2, **kwargs)>

In [95]: str(sig)
Out[95]: '(a, *args, b=2, **kwargs)'

In [97]: for name, param_obj in sig.parameters.items():
    ...:     print(param_obj.kind, ':' , name, '=', param_obj.default)
    ...:
POSITIONAL_OR_KEYWORD : a = <class 'inspect._empty'>
VAR_POSITIONAL : args = <class 'inspect._empty'>
KEYWORD_ONLY : b = 2
VAR_KEYWORD : kwargs = <class 'inspect._empty'>

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

kind 属性的值:

POSITIONAL_OR_KEYWORD

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

VAR_POSITIONAL

  定位参数元组。

VAR_KEYWORD

  关键字参数字典。

KEYWORD_ONLY

  仅限关键字参数(Python 3 新增)。

POSITIONAL_ONLY

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

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

In [98]: args_dict = {'a':1,'c':2,'d':444,'b':333,'e':555}

In [99]: sig.bind(**args_dict)
Out[99]: <BoundArguments (a=1, b=333, kwargs={'c': 2, 'd': 444, 'e': 555})>
    
In [100]: del args_dict['a']

In [101]: sig.bind(**args_dict)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-101-ad8ec8d19bfd> in <module>
----> 1 sig.bind(**args_dict)

TypeError: missing a required argument: 'a'
     
    
In [106]: b_args = sig.bind(**args_dict)

In [107]: for name,value in b_args.arguments.items():  # 查看绑定参数对象的名称和值
     ...:     print(name,value)
     ...:
a 1
b 333
kwargs {'c': 2, 'd': 444, 'e': 555}

这个示例在 inspect 模块的帮助下,展示了 Python 数据模型把实参绑定给函数调用中的形参的机制,这与解释器使用的机制相同。

框架和 IDE 等工具可以使用这些信息验证代码。

函数注解

Python 3 提供了一种句法,用于为函数声明中的参数和返回值附加元数据。添加注解后的版本,区别在于函数的声明。

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

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

In [108]: def clip(text,max_len=10):
     ...:     pass
     ...:

In [109]: clip.__annotations__
Out[109]: {}

In [110]: def clip(text:str,max_len:'int > 5'=1)->int:
     ...:     return 1

In [111]: clip.__annotations__
Out[111]: {'text': str, 'max_len': 'int > 5', 'return': int}

Python 对注解所做的唯一的事情是,把它们存储在函数的 annotations 属性里。仅此而已,Python 不做检查、不做强制、不做验证,什么操作都不做。换句话说,注解对 Python 解释器没有任何意义。注解只是元数据,可以供 IDE、框架和装饰器等工具使用。

inspect 提取注解

In [111]: clip.__annotations__
Out[111]: {'text': str, 'max_len': 'int > 5', 'return': int}

In [112]: sig = signature(clip)

In [113]: sig.return_annotation
Out[113]: int

In [114]: for param in sig.parameters.values():
     ...:     print(param.annotation)
     ...:
<class 'str'>
int > 5

In [115]: for param in sig.parameters.values():
     ...:     print(repr(param.annotation))
     ...:
     ...:
<class 'str'>
'int > 5'

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

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

支持函数式编程的包

1 operator 模块

在函数式编程中,经常需要把算数运算符当做函数使用。

不使用递归,使用 reduce 和 lambda 实现阶乘

In [116]: from functools import reduce

In [117]: def fact(n):
     ...:     return reduce(lambda a,b : a*b, range(1,n+1))

operator 模块为多个算数运算符提供了对应的函数,从而避免编写 如上 简单的 lambda 函数。

In [121]: from operator import mul

In [122]: def fact2(n):
     ...:     return reduce(mul,range(1,n+1))

operator 模块还有一类函数,能替代从序列中取出元素或读取对象属性的lambda表达式

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

In [124]: 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)),
     ...: ]

In [126]: from operator import itemgetter

In [127]: 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))

如果 itemgetter 获取多个参数,返回构建数据的元组

In [128]: cc_name = itemgetter(1,0)

In [129]: 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__ 方法的类。

In [130]: d = {'1':1,'2':2}

In [131]: itemget = itemgetter('1')

In [132]: itemget(d)
Out[132]: 1

In [133]: itemget = itemgetter['1']
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-133-c38ec363ee83> in <module>
----> 1 itemget = itemgetter['1']

TypeError: 'type' object is not subscriptable

itemgetter 的作用,取一个序列或映射的某些索引的值

In [134]: itemgetter('1')(d)
Out[134]: 1

In [135]: itemgetter(1)(metro_data)
Out[135]: ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889))

attrgetter 的作用和 itemgetter 作用类似,根据名称 提取 对象的属性。多个名称 返回 属性值构成的元组。属性名称中 用 . 点号连接的属性,会深入嵌套对象,获取属性值。

In [136]: class A:
     ...:     def __init__(self):
     ...:         self.a = 1
     ...:         self.b = 2
     ...:         self.c = 3

In [137]: aa = A()

In [138]: aa
Out[138]: <__main__.A at 0x1ae6c906a20>

In [140]: aa.a
Out[140]: 1

In [141]: itemgetter('a')(aa)  # A 没有 __getitem__ 方法
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-141-c2b47ace40d1> in <module>
----> 1 itemgetter('a')(aa)

TypeError: 'A' object is not subscriptable


In [145]: from operator import attrgetter

In [146]: attrgetter('a')(aa)
Out[146]: 1

以 _ 开头的名称,因为它们基本上是实现细节

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

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

In [147]: from operator import methodcaller

In [148]: upcase = methodcaller('upper')  # 指定调用的方法 是 upper ,其实和直接调用upper没什么区别,说明 methodcaller 的作用

In [149]: s = 'ttttt'

In [150]: upcase(s)
Out[150]: 'TTTTT'

In [151]: h_re = methodcaller('replace',' ','-')  # methodcaller 还可以冻结某些参数,也就是部分应用(partial application),这与 functools.partial 函数的作用类似。

In [152]: s = 't t'

In [153]: h_re(s)
Out[153]: 't-t'

使用 functools.partial 冻结参数

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

partial 的第一个参数是一个可调用对象,后面跟着任意个要绑定的定位参数和关键字参数。

In [154]: from operator import mul

In [155]: from functools import partial

In [156]: triple = partial(mul,3)

In [157]: triple(7)
Out[157]: 21
    
In [158]: mul(2,5)
Out[158]: 10

In [159]: list(map(triple,range(1,10)))  # map一个参数,mul两个参数,可以使用partial
Out[159]: [3, 6, 9, 12, 15, 18, 21, 24, 27]

实例:unicode.normalize 标准化 多国字符串

In [162]: import unicodedata , functools

In [163]: nfc = functools.partial(unicodedata.normalize,'NFC')

In [165]:  s1 = 'café'

In [166]: s2 = 'cafe\u0301'

In [167]: s1 == s2
Out[167]: False

In [168]: nfc(s1) == nfc(s2)
Out[168]: True
    
In [170]: nfc  # functools.partial 对象
Out[170]: functools.partial(<built-in function normalize>, 'NFC')
In [175]: def aaa(a,b=1,**kwargs):
     ...:     pass

In [177]: aaa_f = functools.partial(aaa,1,b=2)

In [178]: aaa_f.args  # 位置参数
Out[178]: (1,)

In [179]: aaa_f.keywords  # 关键字参数
Out[179]: {'b': 2}
    
In [180]: aaa_f.func  # 获取原函数,可调用
Out[180]: <function __main__.aaa(a, b=1, **kwargs)>

functools.partialmethod 函数(Python 3.4 新增)的作用与 partial 一样,不过是用于处理方法的。

In [181]: class A:
     ...:     def __init__(self):
     ...:         self.x = 1
     ...:     def set_x(self,y):
     ...:         self.x = y
     ...:

In [182]: a = A()

In [185]: set_2_p = partial(a.set_x,2)

In [187]: set_2_p()

In [188]: a.x
Out[188]: 2

In [189]: class A:
     ...:     def __init__(self):
     ...:         self.x = 1
     ...:     def set_x(self,y):
     ...:         self.x = y
     ...:     set_x_pm = functools.partialmethod(self.set_x,33)
     ...:
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-189-479f39f84738> in <module>
----> 1 class A:
      2     def __init__(self):
      3         self.x = 1
      4     def set_x(self,y):
      5         self.x = y

<ipython-input-189-479f39f84738> in A()
      4     def set_x(self,y):
      5         self.x = y
----> 6     set_x_pm = functools.partialmethod(self.set_x,33)
      7

NameError: name 'self' is not defined

In [190]: class A:
     ...:     def __init__(self):
     ...:         self.x = 1
     ...:     def set_x(self,y):
     ...:         self.x = y
     ...:     set_x_pm = functools.partialmethod(set_x,33)  # 自动传 self 参数
     ...:
     ...:

In [191]: aa = A()

In [192]: aa.set_x_pm()

In [193]: aa.x
Out[193]: 33

In [194]: class A:
     ...:     def __init__(self):
     ...:         self.x = 1
     ...:     def set_x(self,y):
     ...:         self.x = y
     ...:     set_x_pm = functools.partial(set_x,33)  # 不自动传 self 参数

In [195]: aaa = A()

In [196]: aaa.set_x_pm()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-196-ef86e214eb4b> in <module>
----> 1 aaa.set_x_pm()

TypeError: set_x() missing 1 required positional argument: 'y'

        # 会把 33 当做 self 参数
In [197]: aaa.set_x_pm(333)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-197-ed5d1adfdf31> in <module>
----> 1 aaa.set_x_pm(333)

<ipython-input-194-efb4bc13b19e> in set_x(self, y)
      3         self.x = 1
      4     def set_x(self,y):
----> 5         self.x = y
      6     set_x_pm = functools.partial(set_x,33)
      7

AttributeError: 'int' object has no attribute 'x'

本章小结

本章的目标是探讨 Python 函数的一等本性。这意味着,我们可以把函数赋值给变量、传给其他函数、存储在数据结构中,以及访问函数的属性,供框架和一些工具使用。高阶函数是函数式编程的重要组成部分,即使现在不像以前那样经常使用 map、filter 和 reduce 函数了,但是还有列表推导(以及类似的结构,如生成器表达式)以及 sum、all 和 any 等内置的归约函数。Python 中常用的高阶函数有内置函数 sorted、min、max 和 functools. partial。

Python 有 7 种可调用对象,从 lambda 表达式创建的简单函数到实现 __call__ 方法的类实例。这些可调用对象都能通过内置的 callable() 函数检测。每一种可调用对象都支持使用相同的丰富句法声明形式参数,包括仅限关键字参数和注解——二者都是 Python 3 引入的新特性

Python 中一切好的特性都是从其他语言中借鉴来的。 Guido van Rossum

布朗大学的计算机科学教授 Shriram Krishnamurthi 在其论文“Teaching Programming Languages in a Post-Linnaean Age”(http://cs.brown.edu/~sk/Publications/Papers/Published/sk-teach-pl-post-linnaean/)的开头这样写道:

编程语言“范式”已近末日,它们是旧时代的遗留物,令人厌烦。既然现代语言的设计者对范式不屑一顾,那么我们的课程为什么要像奴隶一样对其言听计从?

在那篇论文中,下面这一段点名提到了 Python:

对 Python、Ruby 或 Perl 这些语言还要了解什么呢?它们的设计者没有耐心去精确实现林奈层次结构;设计者按照自己的意愿从别处借鉴特性,创建出完全无视过往概念的大杂烩。

不要试图把语言归为某一类;相反,把它们视作特性的聚合更有用。Krishnamurthi

posted @ 2021-08-17 18:17  pythoner_wl  阅读(154)  评论(0编辑  收藏  举报