在 Python 中函数(function)是一等对象(first-class objects)。编程语言理论学家把一等对象定义为满足下述条件的程序实体:
在 Python 中,整数、字符串和字典等都是一等对象。Python 中所有函数都是一等对象。
Python 函数是对象,可以通过以下示例证明(函数对象本身是 function 类的实例)。
def factorial(n):
'''return n!'''
return 1 if n < 2 else n * factorial(n-1)
print(factorial(42))
print(factorial.__doc__) # __doc__ 用于生成对象的帮助文本。
print(type(factorial)) # factorial 是 function 类的一个实例
1405006117752879898543142606244511569936384000000000
return n!
fact = factorial
print(fact)
print(fact(5))
print(map(factorial, range(11)))
"""
map(function, iterable, ...)
以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。
"""
120
通过一等函数就可以进行函数式风格编程(programming in a functional style)。
函数式风格编程
假如要实现二叉树镜像反转
命令式编程:首先判断节点是否为空;然后翻转左树;然后翻转右树;最后左右互换。命令式编程的理论模型——图灵机的特点:一条写满数据的纸带,一条根据纸带内容运动的机器,机器每动一步都需要纸带上写着如何达到。
函数式风格编程:所谓“翻转二叉树”,可以看做是要得到一颗和原来二叉树对称的新二叉树。对一颗二叉树而言,它的镜像树就是左右节点递归镜像的树。通过描述一个 旧树->新树 的映射,而不是描述「从旧树得到新树应该怎样做」来达到目的。
函数式风格编程优点:减少代码量、增加复用(函数式的代码是“对映射的描述”,除了描述二叉树这样的数据结构,任何能在计算机中体现的东西之间的对应关系都可以描述——比如函数和函数之间的映射。
高阶函数(higher-order function):接受函数为参数,或者把函数作为结果返回的函数就是高阶函数。如:map、sorted 等内置函数。
函数式编程范式中,最为人熟知的是 map、filter、reduce 。map 和 filter 由于列表推导式和生成器表达式的出现变得没有那么重要了,通常列表推导式和生成器表达式更易读,也更推荐使用。
from functools import reduce
from operator import add
print(reduce(add, range(100)))
print(sum(range(100)))
4950
4950
def any(iterable): # any函数等价于
for element in iterable:
if element:
return True
return False
Python 中匿名函数是通过关键字 lambda 创建。由于 Python 的简单句法限制了 lambda 函数的定义提只能使用纯表达式。换而言之,lambda 函数的定义体中不能赋值,也不能使用 while 和 try 等 Python 语句。
lambda 函数只是语法糖:与def语句一样,lambda 表达式会创建函数对象。
匿名函数最适合使用在参数列表中。它除了作为参数传给高阶函数之外,Python 很少使用匿名函数。匿名函数可读性差,且不好写。
fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
print(sorted(fruits, key=lambda word: word[::-1])) # 使用lambda表达式反转拼写,然后依此给单词列表排序
['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']
可调用对象是指该对象可以调用运算符(即())。
任何 Python 对象都可以表现得像函数,只要实现实例方法 __call__。
# bingocall.py:调用 BingoCage实例,从打乱的列表中取出一个元素
import random
class BingoCage(object):
def __init__(self, items):
self._items = list(items)
random.shuffle(self._items)
def pick(self):
try:
return self._items.pop()
except IndexErrror:
raise LookupError('pick from empty BingoCage')
def __call__(self):
return self.pick()
bingo = BingoCage(range(3))
print(bingo.pick())
print(bingo())
print(callable(bingo))
0
2
True
可以用 dir()函数查看函数对象的属性。
# 列出常规对象没有而函数专有的属性
class C(object):
pass
def func():
pass
obj = C()
print(sorted(set(dir(func)) - set(dir(obj))))
略
def clip(text, max_len=80):
"""在max_len前面或者后面的第一个空格处截断文本
"""
end = None
if len(text) > max_len:
space_before = text.rfind(' ', 0, max_len)
if space_before >= 0:
end = space_before
else:
space_after = text.rfind(' ', max_len)
if space_after >= 0:
end = space_after
if end is None:
end = len(text)
return text[:end].rstrip()
print(clip.__defaults__)
print(clip.__code__)
print(clip.__code__.co_varnames)
print(clip.__code__.co_argcount)
(80,)
", line 1>
('text', 'max_len', 'end', 'space_before', 'space_after')
2
使用 inspect 模块更好的查看函数的信息
from inspect import signature
sig =signature(clip)
print(sig)
print(str(sig))
for name, param in sig.parameters.items():
print(param.kind, ':', name, '=', param.default)
(text, max_len=80)
(text, max_len=80)
POSITIONAL_OR_KEYWORD : text =
POSITIONAL_OR_KEYWORD : max_len = 80
Python 3 中可以为函数声明中的参数和返回值附加元数据。函数声明中的各个参数可以在 : 之后增加注解表达式(annotation expression),如果参数有表达式,注解放在参数名和 = 之间,如果想注解返回值,在 ) 和函数声明末尾的 : 之间添加 ->和一个表达式。表达式可以是任何类型,注解中最常用的类型是类(如 str 或 int)和字符串(如 ‘int > 0’)。
注解不会做任何处理,只是存储在函数的 __annotations__ 属性(一个字典)中。Python 对注解所做的唯一事是把他们存储在函数的 __annotations__属性里。Python 不做检查、不做强制、不做验证,什么操作都不做。也就是说注解对于 Python 解释器没有任何意义。不过,注解可以供 IDE、框架和装饰器等工具使用,为 IDE 和 lint 程序等工具中的静态类型检查功能提供额外的类型信息。
# 有注解的 clip 函数
def clip(text:str, max_len:'int > 0'=80) -> str:
"""在max_len前面或者后面的第一个空格处截断文本
"""
end = None
if len(text) > max_len:
space_before = text.rfind(' ', 0, max_len)
if space_before >= 0:
end = space_before
else:
space_after = text.rfind(' ', max_len)
if space_after >= 0:
end = space_after
if end is None:
end = len(text)
return text[:end].rstrip()
print(clip.__annotations__)
{'text': , 'max_len': 'int > 0', 'return': }
operator 和 functools 等包的支持使得 Python 函数式编程风格也可以信手拈来。
operator 模块为多个算术运算提供了对应的函数,从而避免了编写 lambda a, b: a*b 这种匿名函数。其中的 itemgetter 和 attrgetter 函数会自行构建函数,从序列中取出元素或者读取对象属性。如:itemgetter(1) 的作用与 lambda fields: fields[1] 一样。
functools 模块提供了一系列高阶函数,如 reduce。此外还有一个非常有用的函数 partial 及其变体,partialmethod。
简单的说 functools.partial 是一个高阶函数,它的作用是把一个函数的一些参数固定,并返回一个新的函数对象。这样做的好处是,参数更少,调用更加方便。如下面这个例子:
# 使用 partial 把两个参数函数改编成需要单参数的函数并返回,原函数不改变。
from operator import mul
from functools import partial
triple = partial(mul, 3) # 固定一个参数为3,并返回新的改编后的函数
print(triple(7)) # 即计算 3*7
print(list(map(triple, range(1, 10))))
print(mul(4, 5)) # 原函数不受影响
21
[3, 6, 9, 12, 15, 18, 21, 24, 27]
20