zoukankan      html  css  js  c++  java
  • 《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 '
    '.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位排序用的)。

  • 相关阅读:
    ant
    maven 构建web项目
    什么叫openapi
    dubbo学习
    Java 获取环境变量
    配置文件书写对象的几种方式
    怎么改svn的登陆账号
    Android 它们的定义View (一)
    eclipse建立cocos2d-x开发环境
    Android——采用SQLiteDatabase操作SQLite数据库
  • 原文地址:https://www.cnblogs.com/Moriarty-cx/p/12692273.html
Copyright © 2011-2022 走看看