美文网首页
Python 内包和装饰器

Python 内包和装饰器

作者: 大雄good | 来源:发表于2018-02-18 19:26 被阅读0次

内包和装饰器

PythonJavaScript(暂时只研究过JS内包😆所以拿来举例)一样都支持内包,而内包又是装饰器的基础,所以本篇文章就简单总结一下本人关于内包装饰器的理解和使用。

1.内包

在统计或者金融领域,可能需要一个函数实现,输入一个数据,来更新当前的平均值,例如下面的avg(此例来自fluent python):

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

如果没有内包,我们可能会定义一个如下class来完成该需求:

class Avg():
    def __init__(self):
        self.num_list = list()
    def __call__(self, num):
        self.num_list.append(num)
        total = sum(self.num_list)
        return total/len(self.num_list)

测试效果如下:

>>> from avg import Avg
>>> avg1 = Avg()
>>> avg1(1)
1.0
>>> avg1(9)
5.0

而使用内包之后,我们可以通过如下代码完成该功能:

def average():
    num_list = list()
    def cal_avg(num):
        num_list.append(num)
        total = sum(num_list)
        return total/len(num_list)
    return cal_avg

测试效果:

>>> from avg import average
>>> avg1 = average()
>>> avg1 = average()
>>> avg1(1)
1.0
>>> avg1(2)
1.5

从上面这个例子我们可以看到, 内包实际上是利用python中一切皆对象(包括函数)的特性, 通过average返回了cal_avg函数对象,而cal_avg函数对象还包括其函数域的局部变量信息。那么有疑问了,num_list并不在cal_avg的函数域(少年你骗我吧😢), 那是因为num_listcal_avg的自由变量(free variable),指不在本定义域中绑定的变量,可以查看__code__来查看编译后的变量类型:

>>> avg1.__code__.co_varnames
('num', 'total')
>>> avg1.__code__.co_f
avg1.__code__.co_filename     avg1.__code__.co_firstlineno  avg1.__code__.co_flags        avg1.__code__.co_freevars
>>> avg1.__code__.co_freevars
('num_list',)

num_list的结果都保存在__closure__:

>>> avg1.__closure__[0]
<cell at 0x10e81f3d8: list object at 0x10ea53cc8>
>>> avg1.__closure__[0].cell_contents
[1, 2]

下面贴出avg1的字节代码:

>>> from dis import dis
>>> dis(avg1)
 12           0 LOAD_DEREF               0 (num_list)
              2 LOAD_ATTR                0 (append)
              4 LOAD_FAST                0 (num)
              6 CALL_FUNCTION            1
              8 POP_TOP

 13          10 LOAD_GLOBAL              1 (sum)
             12 LOAD_DEREF               0 (num_list)
             14 CALL_FUNCTION            1
             16 STORE_FAST               1 (total)

 14          18 LOAD_FAST                1 (total)
             20 LOAD_GLOBAL              2 (len)
             22 LOAD_DEREF               0 (num_list)
             24 CALL_FUNCTION            1
             26 BINARY_TRUE_DIVIDE
             28 RETURN_VALUE

从反汇编结果可以看出,avg1首先执行对num_list的解引用,然后执行append吧啦吧啦,我觉得自己表达能力有限(其实是懒),不过代码比较简单,也容易理解吧。

nonlocal

OK,本来我觉得这个求平均值的函数效率太低,想稍微修改一下:

def adv_avg():
    cnt = 0
    avg = 0
    def cal_avg(num):
        cnt = cnt + 1
        avg = avg + (num - avg)/cnt 
        return avg
    return cal_avg

执行一下咯,尼玛又是一个坑啊:

>>> from avg import adv_avg
>>> avg = adv_avg()
>>> avg(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/xiowang/Documents/Py/decorator/avg.py", line 21, in cal_avg
    cnt = cnt + 1
UnboundLocalError: local variable 'cnt' referenced before assignment

为什么会这样呢?原因就是数字,字符串,元组这些不可变类型执行cnt = cnt + 1时,会创建一个新的cnt,而不是去引用。因此这时就需要用到nonlocal来显示声明变量是外部定义的:

def adv_avg():
    cnt = 0
    avg = 0
    def cal_avg(num):
        nonlocal cnt, avg
        cnt = cnt + 1
        avg = avg + (num - avg)/cnt 
        return avg
    return cal_avg

OK, 修改之后,再运行,perfect:

>>> from avg import adv_avg
>>> avg3 = adv_avg()
>>> avg3(1)
1.0
>>> avg3(2)
1.5
>>> avg3(3)
2.0

2.装饰器

对于装饰器本人理解并不深,但是从名字理解,装饰器就是给用来点缀函数或者类(都是对象)。我打个比方,古代同样两幅画,经过皇家印章盖个印,那么他添加了皇室属性,那收藏价值必须飙升呀,怎么都值个4,5线城市的小房子啊(咋最近老喜欢说房子,还不敢说上海的房子,泪啊😢),其实装饰器就是干盖章这回事呀,完成比如给函数添加打log,计时等功能。

一个关于装饰很经典的例子,给函数添加计时功能:

from time import time, sleep
def decoTime(func):
    def retFunc():
        start = time()
        func()
        now = time()
        print("spend time:{:.2f}".format(now-start))
        print("func_name: {}".format(func.__name__))
    return retFunc

@decoTime
def myfunc():
    sleep(2)

myfunc()
print("myfunc name:{}".format(myfunc.__name__))

执行结果如下:

spend time:2.00
func_name: myfunc
myfunc name:retFunc

这里我们通过上面提到的内包完成一个简单的装饰器,这里执行:

@decoTime
def myfunc():

等价于:

decoTime(myfunc)

所以会执行retFunc函数的内容。

functools.wraps

另外值得注意的是,这里因为我们返回了一个retFunct函数对象,因此myfunc__name__属性也被修改为了retFunc。可是我们想保留__name__属性,那么就可以使用functools.wraps():

from time import time, sleep
from functools import wraps
def decoTime(func):
    @wraps(func)
    def retFunc():
        start = time()
        func()
        now = time()
        print("spend time:{:.2f}".format(now-start))
        print("func_name: {}".format(func.__name__))
    return retFunc

@decoTime
def myfunc():
    sleep(2)

myfunc()
print("myfunc name:{}".format(myfunc.__name__))

使用wraps之后的执行结果:

spend time:2.00
func_name: myfunc
myfunc name:myfunc

带参数的装饰器

装饰器也是能带参数滴:

from time import time, sleep
from functools import wraps
def decoTime(flag):
    if flag=="normal":
        def _decoTime(func):
            @wraps(func)
            def retFunc(*args, **kw):
                start = time()
                func(*args, **kw)
                now = time()
                print("spend time:{:.2f}".format(now-start))
                print("func_name: {}".format(func.__name__))
            return retFunc
        return _decoTime
    elif flag=="double":
        def _decoTime(func):
            @wraps(func)
            def retFunc(*args, **kw):
                start = time()
                func(*args, **kw)
                func(*args, **kw)
                now = time()
                print("spend time:{:.2f}".format(now-start))
                print("func_name: {}".format(func.__name__))
            return retFunc
        return _decoTime

@decoTime("normal")
def myfunc1():
    sleep(2)

@decoTime("double")
def myfunc2():
    sleep(2)

myfunc1()
myfunc2()

执行效果:

spend time:2.00
func_name: myfunc1
spend time:4.01
func_name: myfunc2

说实话,这种带参数的装饰器相当ugly,三层嵌套。

singledispatch

singledispatch是用来实现单分派的装饰器,一个比较实用的装饰器,用来实现类似C++中的重载功能或者说解决函数中if-else过多的问题,简单通过下面的code可以说明:

from functools import singledispatch
@singledispatch
def fun(arg, verbose=False):
    print(arg)
@fun.register(int)
def _(arg, verbose=False):
    print(type(arg))
    print(arg)
@fun.register(list)
def _(arg, verbose=False):
    print(type(arg))
    for i, elem in enumerate(arg):
        print(i, elem)


fun(123)
fun(['a', 'b', 'c'])

执行结果:

<class 'int'>
123
<class 'list'>
0 a
1 b
2 c

这里分别为fun注册了两个匿名函数,根据入参的类型,执行不同的函数。

lru_cathe

装饰器lru_cathe是使用lru缓存算法(底层采用key-value保存结果)为函数结果保存结果,这样重复调用函数时,会使用之前缓存的结果,而不会重复计算:

from functools import lru_cache
cnt1 = 0
def fib1(n):
    global cnt1
    cnt1 = cnt1 + 1
    if n < 2:
        return n
    return fib1(n-1) + fib1(n-2)

cnt2 = 0
@lru_cache(maxsize=None)
def fib2(n):
    global cnt2
    cnt2 = cnt2 + 1
    if n < 2:
        return n
    return fib2(n-1) + fib2(n-2)

fib1(5)
fib2(5)
print("cnt1 : %d" %cnt1)
print("cnt2 : %d" %cnt2)

执行结果:

cnt1 : 15
cnt2 : 6

其中maxsize是用来指定lru的最大缓存大小,默认是128,如果设置为None缓存大小为无穷大。当然官方API建议,当maxsize大小为2的指数时效率最高。

小结

对于python,经常面试的时候都会问到装饰器相关的内容,其实也有大牛建议使用__call__来代替装饰器的使用,其实工具的使用在于场景,但是脑袋需要知道有这些工具才能有机会比较,不然...google呀!

相关文章

  • Python 内包和装饰器

    内包和装饰器 Python和JavaScript(暂时只研究过JS内包?所以拿来举例)一样都支持内包,而内包又是装...

  • 装饰器实验

    装饰器实验 说明 ts内包含了四个装饰器,类装饰器、属性装饰器、函数装饰器、参数装饰器,本文中测试一下其的使用。 ...

  • 利用Python装饰器来组织Tensorflow代码的结构

    装饰器 定义Python装饰器 装饰器是一种设计模式, 可以使用OOP中的继承和组合实现, 而Python还直接从...

  • python装饰器

    装饰器简述 要理解装饰器需要知道Python高阶函数和python闭包,Python高阶函数可以接受函数作为参数,...

  • 装饰器模式

    介绍 在python装饰器学习 这篇文章中,介绍了python 中的装饰器,python内置了对装饰器的支持。面向...

  • 面向对象进阶

    decorotor - 装饰器/包装器 @property装饰器 之前我们讨论过Python中属性和方法访问权限的...

  • python中的装饰器

    python装饰器详解 Python装饰器学习(九步入门) 装饰器(decorator) 就是一个包装机(wrap...

  • [译] Python装饰器Part II:装饰器参数

    这是Python装饰器讲解的第二部分,上一篇:Python装饰器Part I:装饰器简介 回顾:不带参数的装饰器 ...

  • 函数装饰器(Function Decorators)

    函数装饰器(Function Decorators) python中函数装饰器的使用和Java中注解类似, 直接在...

  • Python中的装饰器

    Python中的装饰器 不带参数的装饰器 带参数的装饰器 类装饰器 functools.wraps 使用装饰器极大...

网友评论

      本文标题:Python 内包和装饰器

      本文链接:https://www.haomeiwen.com/subject/adpbtftx.html