1. 函数是一等对象
python从来不是一门函数式编程语言,但函数确实是一等对象,准确的说函数和其他对象一样,都是平等的.
编程语言理论家把'一等对象'定义为满足下述条件的程序实体:
- 在运行时创建
- 能赋值给变量或数据结构中的元素
- 能作为参数传给函数
- 能作为函数的返回结果
Python函数是对象.这里我们创建了一个函数,然后调用它,读取它的__doc__属性,并且确定函数对象本身是function类的实例
def factorial(n): """return n!""" return 1 if n<2 else n*factorial(n-1) factorial(42) 1405006117752879898543142606244511569936384000000000 factorial.__doc__ 'return n!' type(factorial) function 可以看到它和其他对象形式上是保持一致的.
有了一等函数,就可以使用函数式风格编程.函数式编程的特点之一是使用高阶函数
1.1. 高阶函数
接受函数为参数,或者把函数作为结果返回的函数是高阶函数(higher-order function).内置的map,reduce,filter,sorted等都是高阶函数的代表,这边不再复述.
最为人熟知的高阶函数有map、filter、reduce 和 apply.
apply函数在Python 3中已经移除了,因为不再需要它了.如果想使用不定量的参数调用函数,可以编写fn(*args, **keywords),不用再编写apply(fn, args, kwargs)
map,filter 和reduce这三个高阶函数还能见到,不过多数使用场景下都有更好的替代品.
1.1.1. map、filter和reduce的现代替代品
函数式语言通常会提供map、filter和reduce三个高阶函数(有时使用不同的名称).在Python 3中,map和 filter还是内置函数,但是由于引入了列表推导和生成器表达式,它们变得没那么重要了.列表,字典,集合推导或生成器表达式具有map 和filter两个函数的功能,而且更易于阅读,具体的可以看数据结构预算法中相关的内容.
内置的高阶函数与可迭代对象有着很强的关联.这是python3的一项优化,通过延迟计算可以节省内存.
1.2. 匿名函数
lambda关键字在Python表达式内创建匿名函数.
然而,Python 简单的句法限制了lambda函数的定义体只能使用纯表达式.换句话说,lambda函数的定义体中不能赋值,也不能使用while和try等 Python 语句.
在参数列表中最适合使用匿名函数.比如sorted中就可以用lambda表达式
fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana'] sorted(fruits, key=lambda word: word[::-1]) ['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry'] 除了作为参数传给高阶函数之外,Python 很少使用匿名函数.由于句法上的限制,非平凡的lambda表达式要么难以阅读,要么无法写出
如果使用lambda表达式导致一段代码难以理解,PIL的作者"Fredrik Lundh"建议像下面这样重构
- 编写注释,说明
lambda表达式的作用。 - 研究一会儿注释,并找出一个名称来概括注释.
- 把
lambda表达式转换成def语句,使用那个名称来定义函数. - 删除注释.
lambda句法只是语法糖--与def语句一样,lambda表达式会创建函数对象.这是Python中几种可调用对象的一种.下一节会说明所有可调用对象
1.3. 可调用对象
除了用户定义的函数,调用运算符(即 ())还可以应用到其他对象上.如果想判断对象能否调用,可以使用内置的callable()函数.Python数据模型文档列出了8种可调用对象.
用户定义的函数
使用
def语句或lambda表达式创建内置函数
使用C语言(CPython)实现的函数,如
len或time.strftime内置方法使用 C 语言实现的方法,如dict.get方法
在类的定义体中定义的函数
类
调用类既是创建实例
类的实例
如果类定义了
__call__方法,那么它的实例可以作为函数调用生成器函数
使用
yield关键字的函数或方法.调用生成器函数返回的是生成器对象协程
使用
async def创建
callable(...)函数可以用于判定对象是否是可调用的对象
1.3.1. 用户定义的可调用类型
不仅Python函数是真正的对象,任何Python对象都可以表现得像函数.为此只需实现实例方法__call__.
例:BingoCage类.这个类的实例使用任何可迭代对象构建,而且会在内部存储一个随机顺序排列的列表.调用实例会取出一个元素
import random 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('pick from empty BingoCage') def __call__(self): return self.pick() factorial(42) 0 factorial(42) 1factorial(42) 2 factorial(42) 3factorial(42) 4 factorial(42) 51.4. 函数内省
函数对象还有很多属性.使用dir函数可以探知函数具有的属性.这其中大多数属性是Python对象共有的.与函数对象相关的几个属性有:
__annotations__参数和返回值的注解__call__实现()运算符;即可调用对象协议__closure__函数闭包,即自由变量的绑定(通常是 None)__code__编译成字节码的函数元数据和函数定义体__defaults__形式参数的默认值__get__实现只读描述符协议__globals__函数所在模块中的全局变量__kwdefaults__仅限关键字形式参数的默认值__name__函数名称__qualname__函数的限定名称,如 Random.choice
1.5. 从定位参数到仅限关键字参数
Python最好的特性之一是提供了极为灵活的参数处理机制,而且Python 3进一步提供了仅 限关键字参数(keyword-only argument).与之密切相关的是,调用函数时使用*和**"展开"可迭代对象,映射到单个参数.
仅限关键字参数是Python 3新增的特性.用于指定参数只能通过关键字参数指定,而一定不会捕获未命名的定位参数. 定义函数时若想指定仅限关键字参数,要把它们放到前面有*的参数后面.如果不想支持数量不定的定位参数,但是想支持仅限关键字参数,在签名中放一个*.
factorial(42) 6 factorial(42) 7 factorial(42) 8factorial(42) 9 1405006117752879898543142606244511569936384000000000 0仅限关键字参数不一定要有默认值,可以像上例中 b 那样,强制必须传入实参.
要获取一个函数的参数签名,可以使用inspect模块
inspect.signature函数返回一个inspect.Signature对象,它有一个parameters属性,这是一个有序映射,把参数名和inspect.Parameter对象对应起来.各个Parameter属性也有自己的属性,例如name、default 和 kind.特殊的inspect._empty值表示没有默认值.考虑到None是有效的默认值(也经常这么做),而且这么做是合理的.kind属性的值是_ParameterKind类中的5个值之一,列举如下:
POSITIONAL_OR_KEYWORD
可以通过定位参数和关键字参数传入的形参(多数 Python 函数的参数属于此类)
VAR_POSITIONAL
定位参数元组
VAR_KEYWORD
关键字参数字典
KEYWORD_ONLY
仅限关键字参数
POSITIONAL_ONLY
14050061177528798985431426062445115699363840000000001
除了name、default 和 kind,inspect.Parameter 对象还有一个annotation属性,它的值通常是inspect._empty,这部分与类型注释和检验有关
1405006117752879898543142606244511569936384000000000 2 1405006117752879898543142606244511569936384000000000 3 1405006117752879898543142606244511569936384000000000 4 1405006117752879898543142606244511569936384000000000 5inspect.Signature对象有个bind方法,它可以把任意个参数绑定到签名中的形参上,所用的规则与实参到形参的匹配方式一样.框架可以使用这个方法在真正调用函数前验证参数
1405006117752879898543142606244511569936384000000000 6 1405006117752879898543142606244511569936384000000000 7 1405006117752879898543142606244511569936384000000000 8 1405006117752879898543142606244511569936384000000000 9factorial.__doc__ 0 factorial.__doc__ 11.6. 支持函数式编程的包
虽然python之父 "Guido" 明确表明,Python的目标不是变成函数式编程语言,但是得益于operator和functools等包的支持,函数式编程风格也可以信手拈来.
1.6.1. operator模块
在函数式编程中,经常需要把算术运算符当作函数使用.例如,不使用递归计算阶乘.求和可以使用sum函数,但是求积则没有这样的函数.我们可以使用reduce函数,但是需要一个函数计算序列中两个元素之积.
operator模块为多个算术运算符提供了对应的函数,从而避免编写lambda a, b: a*b这种平凡的匿名函数.
factorial.__doc__ 2 operator模块中还有一类函数,能替代从序列中取出元素或读取对象属性的lambda表达式,因此itemgetter 和 attrgetter 其实会自行构建函数
factorial.__doc__ 3 factorial.__doc__ 4如果把多个参数传给 itemgetter,它构建的函数会返回提取的值构成的元组:
factorial.__doc__ 5 factorial.__doc__ 6itemgetter 使用[]运算符,因此它不仅支持序列,还支持映射和任何实现__getitem__ 方 法的类attrgetter与 itemgetter 作用类似,它创建的函数根据名称提取对象的属性。如果把多个属性名传给attrgetter,它也会返回提取的值构成的元组。此外,如果参数名中包含.(点号),attrgetter 会深入嵌套对象,获取指定的属性。
我们构建一个嵌套结构,这样才能展示attrgetter如何处理包含点号的属性名.
定义一个namedtuple,名为metro_data,演示使用attrgetter处理它
factorial.__doc__ 7 factorial.__doc__ 8factorial.__doc__ 9 'return n!' 0'return n!' 1 'return n!' 2 'return n!' 3 'return n!' 4具体符号如下:
| Operation | Syntax | Function |
|---|---|---|
| Addition | a + b | add(a, b) |
| Concatenation | seq1 + seq2 | concat(seq1, seq2) |
| Containment Test | obj in seq | contains(seq, obj) |
| Division | a / b | truediv(a, b) |
| Division | a // b | floordiv(a, b) |
| Bitwise And | a & b | and_(a, b) |
| Bitwise Exclusive Or | a ^ b | xor(a, b) |
| Bitwise Inversion | ~ a | invert(a) |
| Bitwise Or | a l b | or_(a, b) |
| Exponentiation | a ** b | pow(a, b) |
| Identity | a is b | is_(a, b) |
| Identity | a is not b | is_not(a, b) |
| Indexed Assignment | obj[k] = v | setitem(obj, k, v) |
| Indexed Deletion | del obj[k] | delitem(obj, k) |
| Indexing | obj[k] | getitem(obj, k) |
| Left Shift | a << b | lshift(a, b) |
| Modulo | a % b | mod(a, b) |
| Multiplication | a * b | mul(a, b) |
| Matrix Multiplication | a @ b | matmul(a, b) |
| Right Shift | a >> b | rshift(a, b) |
| Slice Assignment | seq[i:j] = values | setitem(seq, slice(i, j), values) |
| Slice Deletion | del seq[i:j] | delitem(seq, slice(i, j)) |
| Slicing | seq[i:j] | getitem(seq, slice(i, j)) |
| String Formatting | s % obj | mod(s, obj) |
| Subtraction | a - b | sub(a, b) |
| Truth Test | obj | truth(obj) |
| Ordering | a < b | lt(a, b) |
| Ordering | a <= b | le(a, b) |
| Equality | a == b | eq(a, b) |
| Difference | a != b | ne(a, b) |
| Ordering | a >= b | ge(a, b) |
| Ordering | a > b | gt(a, b) |
| Matrix Multiplication | a @ b | matmul(a, b) |
| Negation (Arithmetic) | a | neg(a) |
| Negation (Logical) | not a | not_(a) |
| Positive | a | pos(a) |
1.7. 使用functools.partial冻结参数
functools.partial这个高阶函数用于部分应用一个函数.部分应用是指,基于一个函数创建一个新的可调用对象,把原函数的某些参数固定.使用这个函数可以把接受一个或多个参数的函数改编成需要回调的API,这样参数更少. functools.partialmethod函数的作用与partial一样,不过是用于处理方法的.我们以partial来作为例子
'return n!' 5 'return n!' 6'return n!' 7 'return n!' 8使用unicode.normalize函数再举个例子,这个示例更有实际意义.如果处理多国语言编写的文本,在比较或排序之前可能会想使用unicode.normalize('NFC', s)处理所有字符串s如果经常这么做,可以定义一个nfc函数.
'return n!' 9 type(factorial) 0type(factorial) 1 factorial(42) 5type(factorial) 3 factorial(42) 5 



还没有评论,来说两句吧...