Python装饰器

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

Python装饰器

爱摸鱼的菜鸟码农.   2022-09-28 我要评论

装饰器的价值不言而喻,可以用来增强函数功能、简化代码、减少代码冗余。

它的使用场景同样很多,比较简单的场景包含打印日志、统计运行时间,这类例子和用法网上已经很多了:

def time_dec(func):
​
  def wrapper(*arg):
      t = time.clock()
      res = func(*arg)
      print func.func_name, time.clock()-t
      return res
​
  return wrapper
​
​
@time_dec
def myFunction(n):
    ...

再进阶一些的,可以用来校验函数传入参数类型、线程同步、单元测试等:

@parameters(
   (2, 4, 6),
   (5, 6, 11),
)
def test_add(a, b, expected):
    assert a + b == expected

目前可以用的装饰器可以分为如下几类:

  • 自定义
  • 第三方工具包
  • 内置

下面就分别来介绍一下。

自定义

关于自定义的装饰器在前面已经提到了,我在开发过程中经常用到的就是日志打印、计时、数据校验等场景,通过装饰器可以提高代码的简洁性,避免重复造轮子。

除了这些基本的,也有一些比较实用的地方。

作为开发同学,肯定会遇到不同的运行环境:

  • 开发环境
  • 测试环境
  • 生产环境

有时候,我们期望一个函数在不同环境下执行不同的过程,产出不同的结果,做一些环境的隔离和差异化处理。

通过装饰器就可以很好的解决:

production_servers = [...]
​
def production(func: Callable):
    def inner(*args, **kwargs):
        if gethostname() in production_servers:
            return func(*args, **kwargs)
        else:
            print('This host is not a production server, skipping function decorated with @production...')
    return inner
​
def development(func: Callable):
    def inner(*args, **kwargs):
        if gethostname() not in production_servers:
            return func(*args, **kwargs)
        else:
            print('This host is a production server, skipping function decorated with @development...')
    return inner
​
def sit(func: Callable):
    def inner(*args, **kwargs):
        print('Skipping function decorated with @sit...')
    return inner
​
@production
def foo():
    print('Running in production, touching databases!')
​
foo()
​
@development
def foo():
    print('Running in production, touching databases!')
​
foo()
​
@inactive
def foo():
    print('Running in production, touching databases!')
​
foo()

简单的介绍一下这段代码。

在这里,先是罗列了生产环境的服务列表,然后分别定义了生产、开发、测试环境的装饰器,然后给同名的函数就可以配上对应的装饰器。

在执行代码的过程中,这段代码会首先获取hostname,自动判断所在环境,然后执行对应函数。

第三方工具包

上面是根据我们在开发过程中遇到的个性化场景进行来自定义一个装饰器。

作为一款以工具包著称的编程语言,Python中也有很多工具包提供了一些实用的装饰器。

以日志为例,这是每个程序员都无法绕开的。

调试程序对于大多数开发者来说是一项必不可少的工作,当我们想要知道代码是否按照预期的效果在执行时,我们会想到去输出一下局部变量与预期的进行比对。目前大多数采用的方法主要有以下几种:

  • Print函数
  • Log日志
  • IDE调试器

但是这些方法有着无法忽视的弱点:

  • 繁琐
  • 过度依赖工具

其中有一款不错的开源工具PySnooper就通过装饰器把这个问题巧妙的解决了。

PySnooper的调用方式就是通过@pysnooper.snoop的方式进行使用,该装饰器可以传入一些参数来实现一些目的,具体如下:

参数描述:

  • None输出日志到控制台
  • filePath输出到日志文件,例如'log/file.log'
  • prefix给调试的行加前缀,便于识别
  • watch查看一些非局部变量表达式的值
  • watch_explode展开值用以查看列表/字典的所有属性或项
  • depth显示函数调用的函数的snoop行

举个例子:

import numpy as np
import pysnooper
​
@pysnooper.snoop()
def one(number):
    mat = []
    while number:
        mat.append(np.random.normal(0, 1))
        number -= 1
    return mat
​
one(3)

然后,就会给出如下输出:

Starting var:.. number = 3
22:17:10.634566 call         6 def one(number):
22:17:10.634566 line         7     mat = []
New var:....... mat = []
22:17:10.634566 line         8     while number:
22:17:10.634566 line         9         mat.append(np.random.normal(0, 1))
Modified var:.. mat = [-0.4142847169210746]
22:17:10.634566 line        10         number -= 1
Modified var:.. number = 2
22:17:10.634566 line         8     while number:
22:17:10.634566 line         9         mat.append(np.random.normal(0, 1))
Modified var:.. mat = [-0.4142847169210746, -0.479901983375219]
22:17:10.634566 line        10         number -= 1
Modified var:.. number = 1
22:17:10.634566 line         8     while number:
22:17:10.634566 line         9         mat.append(np.random.normal(0, 1))
Modified var:.. mat = [-0.4142847169210746, -0.479901983375219, 1.0491540468063252]
22:17:10.634566 line        10         number -= 1
Modified var:.. number = 0
22:17:10.634566 line         8     while number:
22:17:10.634566 line        11     return mat
22:17:10.634566 return      11     return mat
Return value:.. [-0.4142847169210746, -0.479901983375219, 1.0491540468063252]

局部变量值、代码片段、局部变量所在行号、返回结果等,这些关键信息都输出了,既方便,又清晰。

内置

除了自定义和第三方工具包之外,Python还内置了很多不错的装饰器,例如@abc.abstractmethod、@asyncio.coroutine、@classmethod等等。

这里着重提一个非常强大的装饰器,能够极大的提升Python的运行速度和效率,通过一个装饰器能够将Python代码的执行速度提升上万倍,这个装饰器就是@functools.lru_cache。

以比较知名的斐波那契数列的例子来演示一下。

由于它递归计算的过程中,还会用到之前计算的结果,因此会涉及较多的重复计算,下面先看一下正常计算的耗时情况。

import time as tt
​
def fib(n):
  if n <= 1:
    return n
  return fib(n-1) + fib(n-2)
​
t1 = tt.time()
fib(30)
print("Time taken: {}".format(tt.time() - t1))
# 0.2073

n等于30时,耗时0.2073。

加上@functools.lru_cache装饰器再看一下:

import time as tt
import functools
​
@functools.lru_cache(maxsize=5)
def fib(n):
  if n <= 1:
    return n
  return fib(n-1) + fib(n-2)
​
t1 = tt.time()
fib(30)
print("Time taken: {}".format(tt.time() - t1))
# 1.811981e-05

耗时为1.811981e-05,足足差了4个量级,快了10000+倍!

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们