FluentPythonCh05

 5th July 2017 at 10:07am

这章主要讲一些基础的函数式编程。

在一门语言中,如果函数可以满足下面几个条件,那么它就是这个语言中的一等公民(first-class object)。"first-class function" 和 "functions as first-class objects" 表达的是同样的意思。

  • Created at runtime
  • Assigned to a variable or element in a data structure
  • Passed as an argument to a function
  • Returned as the result of a function

这章的重点是如何获取函数签名信息,并给了 bobo 库作为一个漂亮的例子讲解。

Treating a Function Like an Object

展示了函数被赋值给对象,函数的 __doc__ 属性等,以表达函数是个对象。

Higher-Order Functions

即使用函数作为参数的函数。比如内置的 sort

>>> fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
>>> sorted(fruits, key=len)
['fig', 'apple', 'cherry', 'banana', 'raspberry', 'strawberry']

同类函数有 map, filter ,但这两个函数在灵活性和扩展性上不如 list comprehension,Python 社区已经不推荐。

Anonymous Functions

匿名函数在语法上很受限制,它除了跟高阶函数(Higher-Order Functions)一起搭配使用外,很少有合适的场景:

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

The Seven Flavors of Callable Objects

__builtin__.callable() Checks.

7 Callable types:

  • User-defined functions (def, lambda)
  • Built-in functions (C functions: len, time.strftime)
  • Built-in methods (dict.get)
  • Methods (functions defined in a class)
  • Classes (point = Point3D())
  • Class instances (a class defines a __call__ method)
  • Generator functions (we'll talk about this later)

User-Defined Callable Types

在类中定义一个 __call__ 类型,可以使类的实例可以当作函数来调用。书中给了一个很实用的使用场景,可以用作做装饰器(decorator),以保存多次调用间的中间状态。比如可以用来缓存一些复杂运算的结果。

Function Introspection

函数有一些特殊的内部属性,可以被用来观察函数内部。

其中 __defaults__, __code__, __annotations__ 经常被 IDE 或者一些框架用来提取关于函数签名的信息。

From Positional to Keyword-Only Parameters

Python 3 提供了一个新特性,可以定义一个函数参数,只能作为命名参数被调用。比如:

>>> def f(a, *args, b):
...    return a, b
...
>>> f(1, b=2)
(1, 2)

这个例子中,调用时需要给上 b 这个命名参数,不然会报错。如果你不想要使用 a 之后的位置参数,你也可以把 args 省掉:def f(a, *, b):

Retrieving Information About Parameters

这是本章的重点。

函数的签名信息存在它的 __defaults__, __kwdefaults__, __code__ 属性中,但是你不需要自己去解析他们,标准库 inspect 做了这个事情。boto Web 框架是一个绝佳例子,它利用了参数信息做 HTTP 请求的参数校验:

import bobo

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

如果你访问 http://localhost:8080/ 时不带 person 参数,bobo 会返回 HTTP 403 表示缺少参数。

Function Annotations

Python 3 提供了一套机制,用来给函数参数和返回值增加元信息:

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

这些信息会存放在函数对象的 __annotations__ 中。但是 Python 不会使用这些数据做任何事情,比如参数校验等。这些信息主要被 IDE、静态代码检查器使用。同时 Python 3.5 引入了 Type Hints,给出一个数据规范让 IDE / 其他工具更好地进行代码分析。

inspect 模块可以处理他们。像 bobo 这些 Web 框架,也可以利用这些信息做参数校验。

Packages for Functional Programming

The operator Module

内置的 operator 模块提供了几类函数:

  • 表示操作符的函数,比如加法 add_(a, b) 函数
  • itemgetter, attrgetter
  • 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'

Freezing Arguments with functools.partial

functools.partial 的作用跟 methodcaller 类似,区别在于一个对函数动手,一个对方法(类里面的函数)动手。它非常实用

>>> import unicodedata, functools
>>> nfc = functools.partial(unicodedata.normalize, 'NFC')
>>> s1 = 'café'
>>> s2 = 'cafe\u0301'
>>> s1, s2
('café', 'café')
>>> s1 == s2
False
>>> nfc(s1) == nfc(s2)
True

functools.partialmethod 的作用类似,但是用在类内部方法中。

Furture Reading

Python 之父在设计之初并没有想把 Python 做成一门函数式编程语言,只是他从其他语言借鉴了相应的部分,让他有了一些函数式的特性而已。同时 Python 没有做尾递归优化,可能限制了它在函数式编程上的一些使用。这块内容我还看不懂,后面更深入了解了再回来看看。