《Fluent Python》- 05 一等函数

先跳过一下第四章,并非第四章不重要,先总结核心语法部分的内容,后续再补吧。

在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 '\n'.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位排序用的)。

猜你喜欢

转载自www.cnblogs.com/Moriarty-cx/p/12692273.html