当前位置: 首页 > news >正文

python 装饰器(一)

来源:《流畅的Python》

1. 装饰器基础知识

@decorate
def target():
    print('running target()')

等价于:

def target():
    print('running target()')

target = decorate(target)

最終結果是一樣的,但是target所指向的對象可能會發生變化。

提示 @只不過是一個語法糖,它的作用正如上面的代碼所展示的一樣。那麼,帶參數的裝飾器是什麼個情況呢?
@out(args)  # 首先執行out(args)函數,返回裏面的函數,接下來跟普通的裝飾器一樣。
def func():
    pass

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

2. 何时执行装饰器

在導入包時,進行初始化,而不用直接對其調用。

3. 变量作用域规则

In [1]: def f1(a):
   ...:     print(a)
   ...:     print(b)
   ...:

In [2]: f1(3)
3
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-2-db0f80b394ed> in <module>()
----> 1 f1(3)

<ipython-input-1-c1318c6d0711> in f1(a)
      1 def f1(a):
      2     print(a)
----> 3     print(b)
      4

NameError: name 'b' is not defined

In [5]: b = 6

In [6]: f1(3)
3
6
In [7]: b = 6

In [8]: def f2(a):
   ...:     print(a)
   ...:     print(b)
   ...:     b = 9
   ...:

In [9]: f2(3)
3
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-9-ddde86392cb4> in <module>()
----> 1 f2(3)

<ipython-input-8-2304a03d7bfd> in f2(a)
      1 def f2(a):
      2     print(a)
----> 3     print(b)
      4     b = 9
      5

UnboundLocalError: local variable 'b' referenced before assignment

爲什麼會出現這種情況呢?這是因爲:

Python 不要求声明变量,但是假定在函数定义体中 赋值的变量是局部变量。赋值包含: =, +=等。

4. 闭包

闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。函数是不是匿名的没有关系,关键是它能访问定义体之外定义的非全局变量。

下面兩者是等價的:

class Averager():

    def __init__(self):
        self.series = []

    def __call__(self, new_value):
        self.series.append(new_value)
        total = sum(self.series)
        return total / len(self.series)
def make_averager():
    series = []

    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total / len(series)

    return averager
  1. series 是 make_averager 函数的局部变量;
  2. 调用 avg(10) 时,make_averager 函数已经返回了,而它的本地作用域也一去不复返了。
  3. 在 averager 函数中,series 是自由变量(free variable)。
  4. 这也是一个优化的技巧。因为类的开销太大了,对于只有一个函数和一些变量的类,可以改成函数来实现。
>>> avg.__code__.co_varnames
('new_value', 'total')
>>> avg.__code__.co_freevars
('series',)

>>> avg.__code__.co_freevars
('series',)
# avg.__closure__ 中的各个元素对应于 avg.__code__.co_freevars 中的一个名称。
>>> avg.__closure__ 
(<cell at 0x107a44f78: list object at 0x107a91a48>,)
>>> avg.__closure__[0].cell_contents
[10, 11, 12]

5. nonlocal声明

def make_averager():
    count = 0
    total = 0
    def averager(new_value):
        count += 1
        total += new_value
        return total / count
    return averager

這段代碼有問題,有看出來問題在哪兒碼?

Python 不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量。
>>> avg = make_averager()
>>> avg(10)
Traceback (most recent call last):
...
UnboundLocalError: local variable 'count' referenced before assignment
>>>

当 count 是数字或任何不可变类型时,count += 1 语句的作用其实与 count= count + 1 一样。因此,我们在 averager 的定义体中为 count 赋值了,这会把 count 变成局部变量。total 变量也受这个问题影响。

但是对数字、字符串、元组等不可变类型来说,只能读取,不能更新。如果尝试重新绑定,例如 count = count + 1,其实会隐式创建局部变量 count。这样,count 就不是自由变量了,因此不会保存在闭包中。

为了解决这个问题,Python 3 引入了 nonlocal 声明。它的作用是把变量标记为自由变量,即使在函数中为变量赋予新值了,也会变成自由变量。如果为 nonlocal 声明的变量

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

赋予新值,闭包中保存的绑定会更新。

6. functools.wraps 装饰器

如果不加該裝飾器,則不支持关键字参数,而且遮盖了被装饰函数的 __name____doc__ 属性。

import time
import functools

def clock(func):
    @functools.wraps(func)
    def clocked(*args, **kwargs):
        t0 = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - t0
        name = func.__name__
        arg_lst = []
        if args:
            arg_lst.append(', '.join(repr(arg) for arg in args))
        if kwargs:
            pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
        arg_lst.append(', '.join(pairs))
        arg_str = ', '.join(arg_lst)
        print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result))
    return result
return clocked

7. 使用functools.lru_cache做备忘

这是一项优化技术,它把耗时的函数的结果保存起来,避免传入相同的参数时重复计算。LRU三个字母是“Least Recently Used”的缩写,表明缓存不会无限制增长,一段时间不用的缓存条目会被扔掉。

from clockdeco import clock


@clock
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 2) + fibonacci(n - 1)


if __name__ == '__main__':
    print(fibonacci(6))

輸出:

[0.00000143s] fibonacci(0) -> 0
[0.00000095s] fibonacci(1) -> 1
[0.00006199s] fibonacci(2) -> 1
...
[0.00026441s] fibonacci(6) -> 8
8

import functools

from clockdeco import clock


@functools.lru_cache()  # <1>
@clock  # <2>
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 2) + fibonacci(n - 1)


if __name__ == '__main__':
    print(fibonacci(6))

輸出:

[0.00000072s] fibonacci(0) -> 0
[0.00000143s] fibonacci(1) -> 1
[0.00006676s] fibonacci(2) -> 1
[0.00000215s] fibonacci(3) -> 2
[0.00009227s] fibonacci(4) -> 3
[0.00000143s] fibonacci(5) -> 5
[0.00011635s] fibonacci(6) -> 8
8

lru_cache 可以使用两个可选的参数来配置。它的签名是:

functools.lru_cache(maxsize=128, typed=False)

其中:

  • maxsize:應爲2的賠數。
  • typed:爲True時,f(1)於f(1.0)視爲不同的情況。

8. 单分派泛函数

from functools import singledispatch
from collections import abc
import numbers
import html


@singledispatch  # <1>
def htmlize(obj):
    content = html.escape(repr(obj))
    return '<pre>{}</pre>'.format(content)


@htmlize.register(str)  # <2>
def _(text):  # <3>
    content = html.escape(text).replace('\n', '<br>\n')
    return '<p>{0}</p>'.format(content)


@htmlize.register(numbers.Integral)  # <4>
def _(n):
    return '<pre>{0} (0x{0:x})</pre>'.format(n)


@htmlize.register(tuple)  # <5>
@htmlize.register(abc.MutableSequence)
def _(seq):
    inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
    return '<ul>\n<li>' + inner + '</li>\n</ul>'

@singledispatch 不是为了把 Java 的那种方法重载带入 Python。在一个类中 为同一个方法定义多个重载变体,比在一个函数中使用一长串 if/elif/elif/elif 块要更好。但是这两种方案都有缺陷,因为它们让代码单元(类或函数)承担的职责太多。@singledispath的优点是支持模块化扩展: 各个模块可以为它支持的各个类型注册一个专门函数。

这个机制最好的文档是“PEP 443 — Single-dispatch genericfunctions”

9. 参数化装饰器

创建一个装饰器工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上。
registry = set()  # <1>


def register(active=True):  # <2>
    def decorate(func):  # <3>
        print('running register(active=%s)->decorate(%s)'
              % (active, func))
        if active:  # <4>
            registry.add(func)
        else:
            registry.discard(func)  # <5>

        return func  # <6>

    return decorate  # <7>


@register(active=False)  # <8>等於 @decorate
def f1():
    print('running f1()')


@register()  # <9>
def f2():
    print('running f2()')


def f3():
    print('running f3()')

import time

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


def clock(fmt=DEFAULT_FMT):  # <1>
    def decorate(func):  # <2>
        def clocked(*_args):  # <3>
            t0 = time.time()
            _result = func(*_args)  # <4>
            elapsed = time.time() - t0
            name = func.__name__
            args = ', '.join(repr(arg) for arg in _args)  # <5>
            result = repr(_result)  # <6>
            print(fmt.format(**locals()))  # <7>
            return _result  # <8>

        return clocked  # <9>

    return decorate  # <10>


if __name__ == '__main__':

    @clock()  # <11>
    def snooze(seconds):
        time.sleep(seconds)


    for i in range(3):
        snooze(.123)

相关文章:

  • kali:加速WEP黑客攻击,ARP请求重播攻击
  • DM8127-UART驱动
  • 利用表格分页显示数据的js组件datatable的使用
  • RAID磁盘阵列种类及区别
  • Linux LVM 之LV
  • 语音识别技术受追捧,无法独立工作的“速记神器”何时才能成为新亮点?
  • 还在啃老?是该来场逼格满满的产品展示了!
  • 2018年微信小程序风口最新发展趋势分析
  • Fortinet安全能力融入华为CloudEPN 联合防御网络威胁
  • 【Java资源免费分享,网盘自己拿】
  • 洛谷2774:[网络流24题]方格取数问题——题解
  • 第五届全球云计算大会暨国际网络通信展览会·中国站圆满落幕
  • BlockChange | 区块链将如何颠覆金融服务业
  • html学习笔记
  • Can I use 一款前端兼容性自查工具
  • [译]CSS 居中(Center)方法大合集
  • Gradle 5.0 正式版发布
  • JavaScript中的对象个人分享
  • Js实现点击查看全文(类似今日头条、知乎日报效果)
  • PAT A1017 优先队列
  • Redux 中间件分析
  • 创建一种深思熟虑的文化
  • 电商搜索引擎的架构设计和性能优化
  • 关于 Cirru Editor 存储格式
  • 听说你叫Java(二)–Servlet请求
  • 微信开源mars源码分析1—上层samples分析
  • 微信支付JSAPI,实测!终极方案
  • linux 淘宝开源监控工具tsar
  • Spring第一个helloWorld
  • ​Base64转换成图片,android studio build乱码,找不到okio.ByteString接腾讯人脸识别
  • ​决定德拉瓦州地区版图的关键历史事件
  • #大学#套接字
  • #设计模式#4.6 Flyweight(享元) 对象结构型模式
  • (14)Hive调优——合并小文件
  • (二)JAVA使用POI操作excel
  • (附源码)spring boot北京冬奥会志愿者报名系统 毕业设计 150947
  • (六)库存超卖案例实战——使用mysql分布式锁解决“超卖”问题
  • (三)Hyperledger Fabric 1.1安装部署-chaincode测试
  • (十)T检验-第一部分
  • (新)网络工程师考点串讲与真题详解
  • (转)Windows2003安全设置/维护
  • (轉貼) 2008 Altera 亞洲創新大賽 台灣學生成果傲視全球 [照片花絮] (SOC) (News)
  • (自适应手机端)响应式新闻博客知识类pbootcms网站模板 自媒体运营博客网站源码下载
  • ****** 二 ******、软设笔记【数据结构】-KMP算法、树、二叉树
  • ./configure,make,make install的作用(转)
  • .bat文件调用java类的main方法
  • .NET Core SkiaSharp 替代 System.Drawing.Common 的一些用法
  • .net core控制台应用程序初识
  • .NET Core日志内容详解,详解不同日志级别的区别和有关日志记录的实用工具和第三方库详解与示例
  • .NET 中 GetProcess 相关方法的性能
  • .NET/C# 在 64 位进程中读取 32 位进程重定向后的注册表
  • /ThinkPHP/Library/Think/Storage/Driver/File.class.php  LINE: 48
  • [] 与 [[]], -gt 与 > 的比较
  • [2016.7 Day.4] T1 游戏 [正解:二分图 偏解:奇葩贪心+模拟?(不知如何称呼不过居然比std还快)]
  • [20171102]视图v$session中process字段含义