先跳过一下第四章,并非第四章不重要,先总结核心语法部分的内容,后续再补吧。
在Python中函数是一等对象。编程语言理论家把“一等对象”定义为满足以下条件的程序实体:
- 在运行时创建
- 能赋值给变量或者数据结构中的元素
- 能作为参数传给函数
- 能作为函数的返回结果
在Python中,整数,字符串和字典都是一等对象--没什么特别的。
把函数视为对象
def factorial(n): '''returns n!''' return 1 if n < 2 else n * factorial(n - 1) print(factorial(10)) print(factorial.__doc__) # returns n! print(type(factorial)) # <class 'function'> factorial 是function的实例
我们创建一个函数,读取doc,并确定它是function类的实例。
下例就可以体现函数的一等性:
fc = factorial print(fc(10))
高阶函数
接受函数作为参数,或者把函数作为结果返回的函数是高阶函数。比方说sorted函数:
fruits = ['fig', 'apple', 'cherry', 'ba'] print(sorted(fruits, key=len))
任何单参数函数都能作为key参数的值
函数式语言中通常会提供map,filter和reduce三个高阶函数。在Python3中,map和filter还是内置函数,但是由于引入列表推导和生成器表达式,它们变得没那么重要了:
print(list(map(fc, range(6)))) # 使用map print(list(fc(n) for n in range(6))) # 使用列表推导 print(list(map(fc, filter(lambda n: n%2, range(6))))) print(list(fc(n) for n in range(6) if n%2)) # 使用列表推导能替换掉map和filter
在Python2中。reduce是内置函数,但是在Python3中放到functools模块里了。这个函数最常用于求和,自2.3起,最好使用sum函数
reduce(add, range(100)) sum(range(100)) # 与上面等价
匿名函数
lambda 关键字在Python表达式内创建匿名函数。
fruits = ['fig', 'apple', 'cherry', 'ba'] print(sorted(fruits, key=lambda word: word[::-1]))
Python简答的句法限制了lambda函数的定义体只能使用纯表达式。换句话说,lambda函数的定义体中不能赋值,也不能使用while和try等Python语句。除了作为参数传给高阶函数之外,Python很少使用匿名函数。lambda句法就是简单的语法糖。
可调用对象
除了用户定义的函数,调用运算符(即 ())还可以应用到其他对象上。如果想判断对象能否调用,可以使用内置的callable()函数。文档列举了7种可调用对象:
用户定义的函数:使用def语句或者lambda表达式创建。
内置函数:使用C实现的函数,例如len或者time.strftime。
内置方法:使用C实现的方法,如dict.get。
方法:在类的定义体中定义的函数。
类:调用类时会运行__new__方法创建一个实例,然后运行__init__方法,初始化实例,最后把实例返回给调用方。
类的实例:如果定义了__call__方法,那么它的实例可以作为函数调用。
生成器函数:使用yield关键字的函数或方法。
用户定义的可调用类型
不仅Python函数是真正的对象,任何Python对象都可以表现的像函数。为此只需要实现__call__方法。
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() bingo = BingoCage(range(3)) item = bingo() # 会访问 call方法
实现__call__方法的类是创建函数类对象的简便方式,此时必须在内部维护一个状态,让它在调用之间可用。装饰器就是这样,装饰器必须是函数,而且有时要在多次调用之间“记住”某些事[例如备忘,即缓存消耗大的计算结果,供后面使用]。
函数内省
除了doc,函数对象还有很多属性,使用dir可以探知factorial具体属性
print(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__']
其中大部分属性是Python对象共有的。下面说明一下函数专有而用户定义的一般对象没有的属性:
名称 | 类型 | 说明 |
__annotations__ | dict | 参数和返回值的注解 |
__call__ | method-wrapper | 实现()运算符;即可调用对象协议 |
__closure__ | tuple | 函数闭包,即自由变量的绑定 |
__code__ | code | 编译成字节码的函数元数据和函数定义体 |
__defaults__ | tuple | 形式参数的默认值 |
__get__ | method-wrapper | 实现只读描述符协议 |
__globals__ | dict | 函数所在模块中的全局变量 |
__kwdefaults__ | dict | 仅限关键字形式参数的默认值 |
__name__ | str | 函数名称 |
__qualname__ | str | 函数的限定名称,如Random.chice |
从定位参数到仅限关键字参数
Python最好的特性之一是提供了极为灵活的参数处理机制,而Python3进一步提供了仅限关键字参数。
def tag(name, *content, cls=None, **attrs): if cls is not None: attrs['class'] = cls if attrs: attr_str = ''.join(' %s="%s"'%(attr, value) for attr, value in sorted(attrs.items())) else: attr_str = '' if content: return ' '.join('<%s%s>%s</%s>' % (name, attr_str, c, name) for c in content) else: return '<%s%s />' % (name, attr_str) my_tags = {'name':'img', 'title':'Sunset Boulevard', 'src':'sunset.jpg','cls':'framed'} print(tag(**my_tags)) # 其他使用方式不说明,之前大概都有提过,简单说一下最后这个参数。 # 在my_tags前面加**,所有参数会被看作单参数传入,对应到其命名部分 # 其余多余出来的,会传入最后那个**attrs 里面
cls参数只能通过关键词传入,它不会捕获未命名定位参数(会被content全截了)。
最后说一下,命名定位的话就和传参顺序没有关系了
def test_in(a, b, c): return a*a - b - c print(test_in(c = 3, a = 2, b = 1)) # 这样之后就可以无视顺序函数会自动对应到其位置
获取关于参数的信息
HTTP微框架Bobo中有个使用函数内省的好例子。示例5-12是对Bobo教程中“Hello world”应用的改编,说明了内省怎么使用。
@bobo.query('/') def hello(person): return 'Hello %s!' % person
Bobo会内省hello函数,发现它需要一个名为person的参数,然后从请求中获取那个名称对应的参数,将其传给hello函数,因此程序员不用触碰请求对象。(这部分后续补吧,我还没太弄明白要怎么使用函数内省)
函数注解
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': <class 'str'>, 'max_len': 'int > 0', 'return': <class 'str'>}
函数声明中的各个参数可以在:之后增加注解表达式。如果参数有默认值,注解放在参数名和=号之间。如果想注解返回值,在 ) 和函数声明末尾的 : 之间添加 -> 和一个表达式。表达式可以为任何类型。注解中最常用的类型是类(如str或int)和字符串(如'int > 0')。函数注解最大的影响或许不是让Bobo等框架自动设置,而是为IDE和lint程序等工具中的静态类型检查功能提供额外的类型信息。
支持函数式编程的包
operator模块
求和可以用sum但是求积没有这样的函数,可以用reduce和lambda解决:
def fact(n): return reduce(lambda a, b: a*b, range(1, n+1))
但是operator模块为多个算术运算符提供了对应的函数。
def fact(n): return reduce(mul, range(1, n+1)) triple = partial(mul, 3) print(triple(7)) print(mul(3, 7))
其余的内容不赘述了,triple是一个绑定,这里就是绑定了3和mul,也就是说 triple(7) 是 3*7 的意思。还有itemgetter是一个用来给第i位排序的(元祖列表,按第i位排序用的)。