《Fluent Python》 CH.05_数据结构-一等函数 (函数对象的各种内置函数、operator模块、itemgetter 和 attrgetter迭代/取值函数)

取自第三部分 把函数视作对象

其他

  • 转换命令:
  • jupyter nbconvert --to markdown E:PycharmProjectsTianChiProject0_山枫叶纷飞competitions13_fluent_pythonCH.05_数据结构-一等函数.ipynb

5.1 把函数视为对象

示例生成一个function函数,打印__doc__对象

def factorial(n):
    '''returns n!'''
    return 1 if n < 2 else n * factorial(n-1)

factorial(5)
120
type(factorial)

function
print('打印\_\_doc\_\_对象,返回注释信息!!')
help(factorial)

Help on function factorial in module __main__:

factorial(n)
    returns n!
factorial.__doc__

'returns n!'
list(map(factorial, range(11)))

[1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]

示例 5-2 通过别的名称使用函数,再把函数作为参数传递

fact = factorial
fact(5)
120

5.2 高阶函数

接受函数为参数,或者把函数作为结果返回的函数是高阶函数(higher- order function)。
map 函数就是一例,如上面的示例 5-2 所示。

此外,内置函 数 sorted 也是:可选的 key 参数用于提供一个函数,它会应用到各个 元素上进行排序。

示例 5-3 根据单词长度给一个列表排序

fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
sorted(fruits, key=len)

['fig', 'apple', 'cherry', 'banana', 'raspberry', 'strawberry']

示例 5-4 根据反向拼写给一个单词列表排序

def reverse(word): return word[::-1]
sorted(fruits, key=reverse)
['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']

map、filter和reduce的现代替代品

使用列表推导式

5.3 匿名函数

lambda 关键字在 Python 表达式内创建匿名函数

fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
sorted(fruits, key=lambda x: x[::-1])
['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']

5.4 可调用对象

如果想判断对象能否调用,可以使用内置的 callable() 函数。

Python 数据模型文档列出了 7 种可调用对象。

  • 用户定义的函数
  • 内置函数 (使用 C 语言(CPython)实现的函数,如 len 或 time.strftime。)
  • 内置方法 (使用 C 语言实现的方法,如 dict.get。)
  • 方法 (在类的定义体中定义的函数。)
  • 类 (调用类时会运行类的 __new__ 方法创建一个实例,然后运行 __init__ 方法,初始化实例,最后把实例返回给调用方。)
  • 类的实例,如果类定义了 __call__ 方法,那么它的实例可以作为函数调用。
  • 生成器函数:使用 yield 关键字的函数或方法,调用生成器函数返回的是生成 器对象。

callable() 函数

Python 中有各种各样可调用的类型,因此判断对象能否调用,最安 全的方法是使用内置的 callable() 函数:

abs, str, 13
(<function abs(x, /)>, str, 13)
[callable(obj) for obj in (abs, str, 13)]

[True, True, False]

5.5 用户定义的可调用类型

不仅 Python 函数是真正的对象,任何 Python 对象都可以表现得像函数。为此,只需实现实例方法 __call__。

示例 5-8 实现了 BingoCage 类。

这个类的实例使用任何可迭代对象构 建,而且会在内部存储一个随机顺序排列的列表。调用实例会取出一个 元素。

  • __init__ 接受任何可迭代对象;在本地构建一个副本,防止列表参 数的意外副作用。
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()

print('对象的方法调用')
bingo = BingoCage(range(3))
bingo.pick()
对象的方法调用





2
print('进行第一次类的示例的call调用')
bingo()

进行第一次类的示例的call调用





0
print('进行第二次类的示例的call调用')
bingo()
进行第二次类的示例的call调用





1

下面讨论把函数视作对象处理的另一方面:运行时内省。

5.6 函数内省(函数的反射)

除了 __doc__,函数对象还有很多属性。使用 dir 函数可以探知 factorial 具有下述属性:

print('探知任意函数的更多属性~~')
dir(factorial)[:5]
探知任意函数的更多属性~~





['__annotations__', '__call__', '__class__', '__closure__', '__code__']

详细的内省函数

['__annotations__', # dict 参数和返回值的注解

'__call__', # method- wrapper 实现 () 运算符;即可调用对象协议

'__class__',

'__closure__',# tuple 函数闭包,即自由变量的绑定(通常是 None)

'__code__', # code 编译成字节码的函数元数据和函数定义体
'__defaults__',# tuple 形式参数的默认值
'__delattr__',
'__dict__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__get__',
'__getattribute__',

'__globals__',# dict 函数所在模块中的全局变量

'__gt__',
'__hash__',
'__init__',
'__init_subclass__',

'__kwdefaults__',# dict 仅限关键字形式参数的默认值

'__le__',
'__lt__',
'__module__',
'__name__',
'__ne__',
'__new__',

'__qualname__', #str 函数的限定名称,如 Random.choice( 参阅PEP 3155,https://www.python.org/dev/peps/pep-3155/)

'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__sizeof__',
'__str__',
'__subclasshook__']

示例 5-9 列出常规对象没有而函数有的属性

class C:
    pass
obj = C()
def func():
    pass
sorted(set(dir(func)) - set(dir(obj)))
['__annotations__',
 '__call__',
 '__closure__',
 '__code__',
 '__defaults__',
 '__get__',
 '__globals__',
 '__kwdefaults__',
 '__name__',
 '__qualname__']

5.7 从定位参数到仅限关键字参数

Python 3 进一步提供了仅限关键字参数(keyword-only argument)。

与之密切相 关的是,调用函数时使用 * 和 **“展开”可迭代对象,映射到单个参 数。

示例 5-10 tag 函数用于生成 HTML标签;

使用名为 cls 的关键 字参数传入“class”属性,这是一种变通方法,因为“ class”是 Python 的关键字

def tag(name, *content, cls=None, **attrs):
    """生成一个或多个HTML标签"""
    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)

示例 5-11 tag 函数(见示例 5-10)众多调用方式中的几种

tag('br')

'<br />'
tag('p', 'hello')

'<p>hello</p>'
print(tag('p', 'hello', 'world'))

<p>hello</p>
<p>world</p>

5.8 获取关于参数的信息

import bobo
@bobo.query('/')
def hello(person):
    return 'Hello %s!' % person

hello('person')
'Hello person!'

类似于反射:

bobo.query 装饰器把一个普通的函数(如 hello)与框架的请求处理 机制集成起来了。

,Bobo 会内省 hello 函数,发现它需要一个名为 person 的参数,然后从请求中获取那个名称对应的参数,将其传给 hello 函 数,因此程序员根本不用触碰请求对象。

函数对象的 defaults 属性

,它的值是一个元组,里面保存着定 位参数和关键字参数的默认值。

仅限关键字参数的默认值在 kwdefaults 属性中。然而,参数的名称在 code 属性中,它 的值是一个 code 对象引用,自身也有很多属性。

示例 5-15 在指定长度附近截断字符串的函数

def my_clip(text, max_len=80):
    """在max_len前面或后面的第一个空格处截断文本"""
    print(text)
    pass
提取关于函数参数的信息__defaults__>>>>> 





(80,)

defaults

%

print('提取关于函数参数的信息__defaults__>>>>> ')
my_clip.defaults

code

print('提取关于函数参数的信息__code__>>>')
my_clip.__code__

signature

print('示例 5-17 提取函数的签名')
from inspect import signature
sig = signature(my_clip)
sig
示例 5-17 提取函数的签名





<Signature (text, max_len=80)>
str(sig)


'(text, max_len=80)'

5.9 函数的注解 参数解释

Python 3 提供了一种句法,用于为函数声明中的参数和返回值附加元数 据。

函数声明中的各个参数可以在 : 之后增加注解表达式。
如果参数有默认 值,注解放在参数名和 = 号之间。

如果想注解返回值,在 ) 和函数声明 末尾的 : 之间添加 -> 和一个表达式。那个表达式可以是任何类型。注 解中最常用的类型是类(如 str 或 int)和字符串(如 'int > 0')。

但是,注解对 Python 解释器没有任何 意义。注解只是元数据,可以供 IDE、框架和装饰器等工具使用。

在示例 5-19 中,max_len 参数的注解用的是字符串。 注解不会做任何处理,只是存储在函数的 annotations 属性(一 个字典)中:

示例 5-19 有参数解释的 clip 函数

def clip(text:str, max_len:'int > 0'=80) -> str:
    """在max_len前面或后面的第一个空格处截断文本 """
    pass

clip.__annotations__
{'text': str, 'max_len': 'int > 0', 'return': str}

示例 5-20 从函数签名中提取注解

signature 函数返回一个 Signature 对象,它有一个 return_annotation 属性和一个 parameters 属性,后者是一个字 典,把参数名映射到 Parameter 对象上。每个 Parameter 对象自己也 有 annotation 属性。

signature(clip).return_annotation
str
for param in signature(clip).parameters.values():
    note = repr(param.annotation).ljust(13)
    print(note, ':', param.name, '=', param.default)

<class 'str'> : text = <class 'inspect._empty'>
'int > 0'     : max_len = 80

5.10 支持函数式编程的包

虽然 Guido 明确表明,Python 的目标不是变成函数式编程语言,但是得 益于 operator 和 functools 等包的支持,函数式编程风格也可以信 手拈来。

5.10.1 operator模块

示例 5-21 使用 reduce 函数和一个匿名函数计算阶乘

from functools import reduce
def fact(n):
    return reduce(lambda a,b:a*b, range(1, n+1))
fact(4)
24

operator包 mul累乘函数

operator 模块为多个算术运算符提供了对应的函数,从而避免编写 lambda a, b: a*b 这种平凡的匿名函数。

使用算术运算符函数,可以 把示例 5-21 改写成示例 5-22 那样。

from functools import reduce
from operator import mul
def fact(n):
    return reduce(mul, range(1, n+1))

fact(2)
2

operator包 itemgetter 和 attrgetter 迭代函数

operator 模块中还有一类函数,能替代从序列中取出元素或读取对象 属性的 lambda 表达式:

  • itemgetter(1) 的作用与 lambda fields: fields[1] 一样:创建一个接受集合的函数,返回索引位 1 上的元素。
  • attrgetter 与 itemgetter 作用类似,它创建的函数根据名称提取对 象的属性。如果把多个属性名传给 attrgetter,它也会返回提取的值 构成的元组。此外,如果参数名中包含 .(点号),attrgetter 会深 入嵌套对象,获取指定的属性。

5.10.2 使用functools.partial冻结参数

functools.partial 这个高阶函数用于部分应用一个函数。部分应用 是指,基于一个函数创建一个新的可调用对象,把原函数的某些参数固 定。

使用这个functools.partial函数可以把接受一个或多个参数的函数改编成需要回调的 API,这样参数更少。示例 5-26 做了简单的演示。

from operator import mul
from functools import partial
triple = partial(mul, 3)
triple(7)
21
list(map(triple, range(1, 10)))

[3, 6, 9, 12, 15, 18, 21, 24, 27]


你不逼自己一把,你永远都不知道自己有多优秀!只有经历了一些事,你才会懂得好好珍惜眼前的时光!
原文地址:https://www.cnblogs.com/zhazhaacmer/p/14391574.html