首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >《流畅的Python》第七章学习笔记

《流畅的Python》第七章学习笔记

作者头像
zx钟
发布2020-12-24 11:36:54
发布2020-12-24 11:36:54
5100
举报
文章被收录于专栏:测试游记测试游记

函数装饰器用于在源码中「标记」函数,以某种方式增强函数的行为。

闭包除了在装饰器中有用处外,还是回调式异步编程和函数式编程风格的基础。

装饰器只是语法糖。

装饰器可以像常规的可调用对象那样调用,其参数是另一个函数。

装饰器的执行

装饰器在函数定义之后立即运行

函数装饰器在导入模块时立即执行,被装饰的函数只在明确调用时运行。

代码语言:javascript
复制
registry = []


def register(func):
    print(f"注册函数:{func}")
    registry.append(func)
    return func


@register
def f1():
    print("运行f1")


@register
def f2():
    print("运行f2")


def f3():
    print("运行f3")


if __name__ == '__main__':
    print(registry)
    f1()
    f2()
    f3()
    print(registry)
    """
    注册函数:<function f1 at 0x7f854f30cd40>
    注册函数:<function f2 at 0x7f854f312ef0>
    [<function f1 at 0x7f854f30cd40>, <function f2 at 0x7f854f312ef0>]
    运行f1
    运行f2
    运行f3
    [<function f1 at 0x7f854f30cd40>, <function f2 at 0x7f854f312ef0>]
    """

注册装饰器

通过装饰器将内容进行预添加

代码语言:javascript
复制
# 注册装饰器.py
import pytest

all_func = []


def get_func(func):
    all_func.append(func.__name__)
    return func


@get_func
def test_f1():
    print("f1")


@get_func
def test_f3():
    print("f3")


@get_func
def test_f2():
    print("f2")


if __name__ == '__main__':
    print("执行顺序\n" + "\n".join(all_func))
    pytest.main([
        '-v', '注册装饰器.py'
    ])

记录pytest的执行顺序

代码语言:javascript
复制
执行顺序
test_f1
test_f3
test_f2
============================= test session starts ==============================
platform darwin -- Python 3.7.6, pytest-5.3.5, py-1.8.1, pluggy-0.13.1 -- /opt/anaconda3/bin/python
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/Users/zhongxin/PycharmProjects/流畅的Python/第七章:函数装饰器和闭包/.hypothesis/examples')
rootdir: /Users/zhongxin/PycharmProjects/流畅的Python/第七章:函数装饰器和闭包
plugins: hypothesis-5.5.4, arraydiff-0.3, remotedata-0.3.2, openfiles-0.4.0, doctestplus-0.5.0, astropy-header-0.1.2
collecting ... collected 3 items

注册装饰器.py::test_f1 PASSED                                            [ 33%]
注册装饰器.py::test_f3 PASSED                                            [ 66%]
注册装饰器.py::test_f2 PASSED                                            [100%]

============================== 3 passed in 0.04s ===============================

闭包

闭包指延伸了作用域的函数,其中包含了「函数定义体中引用」、「不在定义体中定义的非全局变量」

关键是它能访问定义体之外定义的非全局变量

自由变量:未在本地作用域中绑定的变量

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

nonlocal

未使用nonlocal

由于数字是不可变类型,所以count = count + 1会隐式创建局部变量count

正确的写法

代码语言:javascript
复制
def make_averager():
    count = 0
    total = 0

    def averager(new_value):
        nonlocal count, total
        count += 1
        total += new_value
        return total / count

    return averager

简单的装饰器

计算运行时间的装饰器

代码语言:javascript
复制
import time


def clock(func):
    def clocked(*args):
        start = time.perf_counter()
        result = func(*args)
        end = time.perf_counter()
        arg_str = ','.join(repr(arg) for arg in args)
        print(f"[{end - start:0.8f} s] {func.__name__}({arg_str})")
        return result

    return clocked


@clock
def func(a):
    return a + 1


if __name__ == '__main__':
    func(5)  # [0.00000112 s] func(5)

该装饰器的缺点:不支持关键字参数,遮盖了被装饰函数的__name____doc__属性

使用functools.wraps装饰器可以把相关属性复制过来

代码语言:javascript
复制
import functools
import time

def clock1(func):
    @functools.wraps(func)
    def clocked(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()
        arg_lst = []
        if args:
            arg_lst.append(','.join(repr(arg) for arg in args))
        if kwargs:
            pairs = [f"{k}={v}" for k, v in sorted(kwargs.items())]
            arg_lst.append(','.join(pairs))
        arg_str = ','.join(arg_lst)
        print(f"[{end - start:0.8f} s] {func.__name__}({arg_str})")
        return result

    return clocked

标准库中的装饰器

functools.lru_cache

functools.lru_cache做备忘,可以把耗时的函数的结果保存起来,避免传入相同的参数时重复计算。生成第n个斐波那契数这种慢速递归适合使用。从Web中获取信息的应用中也能发挥巨大作用。

functools.singledispatch

PEP443-Single-dispatch generic functions:https://www.python.org/dev/peps/pep-0443/

functools.singledispatch单分派泛函数。把整体方案拆分成多个模块

被装饰的普通函数变为泛函数:根据第一个参数的类型,以不同方式执行相同操作的一组函数。类似于重载

可以在系统的任何地方和任何模块中注册专门函数。

如果后来在新的模块中定义了新的类型,可以轻松地添加一个新的专门函数来处理那个类型。

还可以为不是自己编写的或者不能修改的类添加自定义函数。

代码语言:javascript
复制
from functools import singledispatch


@singledispatch
def fun(arg, verbose=False):
    if verbose:
        print("泛函数:", end=" ")
    print(arg)


@fun.register(int)
def _(arg, verbose=False):
    if verbose:
        print("整型:", end="")
    print(arg)


@fun.register(list)
def _(arg, verbose=False):
    if verbose:
        print("列表:")
    for i, elem in enumerate(arg):
        print(f'索引{i}:内容{elem}')


def nothing(arg, verbose=False):
    print("啥也没有")


fun.register(type(None), nothing)

if __name__ == '__main__':
    fun("你好")  # 你好
    fun("你好呀", True)  # 泛函数: 你好呀
    fun(1, True)  # 整型:1
    fun(['a', 'b', 'c'], True)
    """
    列表:
    索引0:内容a
    索引1:内容b
    索引2:内容c
    """
    fun(None)  # 啥也没有

叠放装饰器

代码语言:javascript
复制
def d1(f):
    print('d1')
    return f


def d2(f):
    print('d2')
    return f


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

执行顺序相当于f = d1(d2(f))

参数化装饰器

参数化装饰器需要使用()进行调用,非参数化装饰器则不需要()

下面是一个参数化输出格式的计算运行时间装饰器

代码语言:javascript
复制
import time

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


def clock(fmt=DEFAULT_FMT):
    def decorate(func):
        def clocked(*_args):
            t0 = time.time()
            _result = func(*_args)
            elapsed = time.time() - t0
            name = func.__name__
            args = ','.join(repr(arg) for arg in _args)
            result = repr(_result)
            print(fmt.format(**locals()))
            return _result

        return clocked

    return decorate


if __name__ == '__main__':
    @clock()
    def snnoze(seconds):
        time.sleep(seconds)


    @clock('{name}:{elapsed}s')
    def snnoze1(seconds):
        time.sleep(seconds)


    @clock('{name}({args}) dt={elapsed:0.3f}s')
    def snnoze2(seconds):
        time.sleep(seconds)


    snnoze(0.123)  # [0.12421203s] snnoze(0.123) -> None
    snnoze1(0.123)  # snnoze1:0.125046968460083s
    snnoze2(0.123)  # snnoze2(0.123) dt=0.127s

简化装饰器的构建

https://github.com/micheles/decorator/blob/master/docs/documentation.md

安装decorator模块

代码语言:javascript
复制
$ pip install decorator

使用decorator实现一个参数化的计时装饰器

代码语言:javascript
复制
import time
from decorator import decorator


@decorator
def warn_slow(func, style=None, *args, **kw):
    t0 = time.time()
    result = func(*args, **kw)
    dt = time.time() - t0
    if not style:
        print(f'执行{func.__name__}总耗时:{dt}s')
    else:
        print(f"参数化执行{func.__name__}总耗时:{dt}s")
    return result


@warn_slow
def f1(t):
    time.sleep(t)


@warn_slow(style=1)
def f2(t):
    time.sleep(t)


if __name__ == '__main__':
    f1(0.123)
    f2(0.123)
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 测试游记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 装饰器的执行
  • 注册装饰器
  • 闭包
  • nonlocal
  • 简单的装饰器
  • 标准库中的装饰器
    • functools.lru_cache
    • functools.singledispatch
  • 叠放装饰器
  • 参数化装饰器
  • 简化装饰器的构建
    • 安装decorator模块
    • 使用decorator实现一个参数化的计时装饰器
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档