《Fluent Python》笔记 | 函数对象和装饰器

在Python中函数是对象,本质是function类的实例。同样函数对象也是“一等对象”,即满足以下条件:

  • 在运行时创建
  • 能赋值给变量或数据结构中的元素
  • 能作为参数传给函数
  • 能作为函数的返回结果

函数对象的__doc__属性用于生成对象的帮助文本。

接受函数为参数, 或者把函数作为结果返回的函数是高阶函数(higherorder function),例如map、filter、reduce等函数。

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

可调用对象

可调用对象为可以应用调用运算符()的对象。

Python文档中列出以下7种可调用对象。

  • 用户定义的函数

    使用 def 语句或 lambda 表达式创建。

  • 内置函数

    使用 C 语言(CPython) 实现的函数, 如 len 或 time.strftime。

  • 内置方法

    使用 C 语言实现的方法, 如 dict.get。

  • 方法
    在类的定义体中定义的函数。


  • 调用类时会运行类的 __new__ 方法创建一个实例, 然后运行__init__ 方法, 初始化实例, 最后把实例返回给调用方。

  • 类的实例
    如果类中定义了 __call__方法, 那么它的实例可以作为函数调用。

  • 生成器函数
    使用 yield 关键字的函数或方法。 调用生成器函数返回的是生成器对象。

可以通过内置函数callable() 判断对象是否可调用。

函数属性

既然我们说函数是一个对象,那么它也会像对象一样拥有属性。下表列出了一些只有用户定义的函数对象具有而常规对象没有的属性。

名称 类型 说明
__annotations__ dict 参数和返回值的注解
__call__ method wrapper 实现 () 运算符; 即可调用对象协议
__closure__ tuple 函数闭包, 即自由变量的绑定(通常是 None)
__code__ code 编译成字节码的函数元数据和函数定义体
__defaults__ tuple 形式参数的默认值
__get__ method wrapper 实现只读描述符协议
__globals__ dict 函数所在模块中的全局变量
__kwdefaults__ dict 仅限关键字形式参数的默认值
__name__ str 函数名称
__qualname__ str 函数的限定名称, 如 Random.choice

仅限关键字参数

仅限关键字参数(keyword-only argument)。

def tag(name, *content, cls=None, **attrs): pass
def f(a, *, b): pass

tag函数的输入参数中,第一个参数后面的任意个参数会被 *content 捕获, 存入一个元组(即content为元组)。在tag函数定义中没有明确指定名称的关键字参数会被 **attrs 捕获, 存入一个字典(即attrs为字典)。 在上面代码中,name参数和a参数为定位参数,cls参数和b参数为仅限关键字参数。如果仅限关键字参数没有默认值,那么强制传入对应实参。

获取函数的信息

  • 获取函数参数信息

    函数对象有个 __defaults__ 属性, 它的值是一个元组, 里面保存着定位参数和关键字参数的默认值。 仅限关键字参数的默认值在__kwdefaults__ 属性中。 参数的名称在 __code__ 属性中, 它的值是一个 code 对象引用, 自身也有很多属性。

  • 获取函数注解

    带注解的函数声明示例:

    def clip(text:str, max_len:'int > 0'=80) -> str: pass
    

    各个参数在 : 之后添加注解表达式,若参数有默认值,则在参数名和=之间加入注解表达式。如果想注解返回值, 在 ) 和函数声明末尾的 : 之间添加 -> 和注解表达式。

    注解不会做任何处理, 只是存储在函数的 __annotations__ 属性(一个字典) 中。

    函数装饰器

    函数装饰器用于在源码中“标记”函数, 以某种方式增强函数的行为。 装饰器是可调用的对象, 其参数是另一个函数(被装饰的函数) 。 装饰器可能会处理被装饰的函数, 然后把它返回, 或者将其替换成另一个函数或可调用对象。

    装饰器替换被装饰函数示例:

    def deco(func):		# deco是一个装饰器,其输入参数时另一个函数
    	def inner():
    		print('running inner()')
    	return inner
    
    @deco
    def target():	# 使用 deco 装饰 target
    	print('running target()')
        
    >>> target()
    running inner()
    >>> target
    <function deco.<locals>.inner at 0x10063b598>
    

装饰器的第一大特性是, 能把被装饰的函数替换成其他函数。 第二个特性是, 装饰器在加载模块时立即执行

装饰器通常在一个模块中定义, 然后应用到其他模块中的函数上。

大多数装饰器会在内部定义一个函数, 然后将其返回,替换被装饰的函数。

闭包

在这里插入图片描述

>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0

在上面的示例中,seriesmake_averager 函数的局部变量。 可是,调用 avg(10)时, make_averager 函数已经返回了,而它的本地作用域也一去不复返了。但代码测试结果告诉我们, series变量以某种形式保存了下来,这就是闭包的特性。series自由变量,即未在本地作用域中绑定的变量。

闭包是一种函数, 它会保留定义函数时存在的自由变量的绑定,这样调用函数时, 虽然定义作用域不可用了, 但是仍能使用那些绑定。

只有嵌套在其他函数中的函数才可能需要处理不在全局作用域中的外部变量,也就是说只有嵌套在其他函数中的函数才会涉及到闭包的知识。

nonlocal关键字能把变量标记为自由变量。示例如下:

def make_averager():
	count = 0
	total = 0
	def averager(new_value):
		nonlocal count, total	# count, total都为自由变量,都会在闭包中进行绑定和更新
		count += 1
		total += new_value
		return total / count
	return averager

实现一个简单的装饰器,用于计算被调用函数的运行时间。

import time
def clock(func):
	def clocked(*args):
		t0 = time.perf_counter()
        result = func(*args)
        elapsed = time.perf_counter() - t0
        name = func.__name__
        arg_str = ', '.join(repr(arg) for arg in args)
        print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
        return result
	return clocked

上述代码反映了装饰器的典型行为: 把被装饰的函数替换成新函数, 二者接受相同的参数, 而且通常返回被装饰的函数本该返回的值, 同时还会做些额外操作。

标准库中的装饰器

  • functools.lru_cache

    functools.lru_cache是非常实用的装饰器, 实现了备忘功能。 这是一项优化技术, 它把耗时的函数的结果保存起来, 避免传入相同的参数时重复计算。 LRU 三个字母是“Least Recently Used”的缩写, 表明缓存不会无限制增长, 一段时间不用的缓存条目会被清除。

    该装饰器可以使用两个可选参数进行配置。

    functools.lru_cache(maxsize=128, typed=False)  
    
    • maxsize:指定存储多少个调用的结果。
    • typed:若为 True, 把不同参数类型得到的结果分开保存,即把通常认为相等的浮点数和整数参数(如 1 和 1.0) 区分开。
  • functools.singledispatch

    functools.singledispatch 装饰器可以把整体方案拆分成多个模块, 甚至可以为无法修改的类提供专门的函数(使用@<base_function>.register(<type>) 装饰)。 使用@singledispatch 装饰的普通函数会变成泛函数(generic function) :根据第一个参数的类型, 以不同方式执行相同操作的一组函数。

叠放装饰器

因为装饰器是一个函数(可调用对象),所以可以组合使用。

@d1
@d2
def f():
	print('f')

等同于:

def f():
	print('f')
f = d1(d2(f))  

参数化装饰器

为了实现参数化装饰器,我们引入装饰器工厂函数,装饰器函数嵌套在该函数中。调用装饰器工厂函数会返回真正的装饰器, 这才是应用到目标函数上的装饰器。参数化装饰器示例如下:

import time

DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'

def clock(fmt=DEFAULT_FMT): 	# clock是参数化装饰器工厂函数
	def decorate(func): 		# decorate 是真正的装饰器
		def clocked(*_args): 	# clocked 包装被装饰的函数
            t0 = time.time()	
            _result = func(*_args)	# _result 是被装饰的函数返回的真正结果 
            elapsed = time.time() - t0
            name = func.__name__
            args = ', '.join(repr(arg) for arg in _args) 
            result = repr(_result) 
            print(fmt.format(**locals())) # **locals()是为了在fmt中引用clocked的局部变量
            return _result 
		return clocked 
	return decorate 

if __name__ == '__main__':
	@clock(DEFAULT_FMT)
	def snooze(seconds):
		time.sleep(seconds)
	for i in range(3):
		snooze(.123)

猜你喜欢

转载自blog.csdn.net/qq_39784672/article/details/128289169