是时候写一下Python装饰器了。

装饰器记录好,就剩下多进程,多线程,携程了,前面还欠着re,不知道什么时候写,re实在太强大了。

 

装饰器,记得第一次看到啥都不懂,也不知道为什么要设计这玩意,后面接触了Flask,Django才知道那些所谓的中间件都是一些装饰器。

首先,Python是万物皆对象,所以一个函数作为对象实在太正常,但刚接触Python的时候,我至少很长时间不能接受这种行为。

 

首先上一个最简单的装饰器:

# -*- coding:utf-8 -*-
import random

def check(func):
    print('I am func,I will start first')
    def wrap(*args, **kwargs):
        res = func(*args, **kwargs)
        if res > 0:
            return res
        else:
            return 0
    print('I go out')
    return wrap

@check
def foo(m, n ):
    return random.randint(m, n)

 这是一个极其普通的装饰器,就是负数输出为0。

@check所谓的语法糖很有意思,你不执行check或者foo的任意一个函数,单还是会输出
I am func,I will start first
I go out

 

这个说明check函数已经执行了,check函数返回的是另一个需要被装饰的函数wrap。这里面到底发生了什么。

当用了@check的语法糖以后,其实在执行该脚本时,Python内部先执行了foo = check(foo)
所以,check函数会执行,而且foo也变成了check函数执行返回的wrap.
print(foo.__name__)

 

wrap

 在前面代码中,如果打印foo的名字代码,就会发现foo的名字都已经变了。

 

现在我给foo与wrap都加上一些docstring的说明。

import random

def check(func):
    print('I am func,I will start first')
    def wrap(*args, **kwargs):
        '''My name is wrap,hello'''
        res = func(*args, **kwargs)
        if res > 0:
            return res
        else:
            return 0
    print('I go out')
    return wrap

@check
def foo(m, n ):
    '''My name is foo,hello'''
    return random.randint(m, n)

print('foo_docstring---->',foo.__doc__)

 

foo_docstring----> My name is wrap,hello

 从执行可以看出foo的docstring都已经发生了改变,这样对原函数实在太不友好了,不是说装饰器不影响原函数的执行,单原函数的一些内部属性发送了变化,装饰器你也太霸道了。

Python想到了这些,有一个原装的模块可以解决。

import random
import functools

def check(func):
    print('I am func,I will start first')
    @functools.wraps(func)
    def wrap(*args, **kwargs):
        '''My name is wrap,hello'''
        res = func(*args, **kwargs)
        if res > 0:
            return res
        else:
            return 0
    print('I go out')
    return wrap

@check
def foo(m, n ):
    '''My name is foo,hello'''
    return random.randint(m, n)

print('foo_docstring---->',foo.__doc__)
print(foo(2, 6))
print('foo_name---->',foo.__name__)

 

foo_docstring----> My name is foo,hello
3
foo_name----> foo

 从输出可以看出foo的内部属性在装饰器装饰后,没有发生任何改变。

 

接下来,我说一下多层装饰器,理解了这个多那些框架的中间件应该有更好的理解,至少我是这么认为的。
多层装饰器就是有多个@,这个多层装饰器,其实有那么一点点递归的感觉。

def deco1(func):
    print("This is deco1")

    def wrap1(*args, **kwargs):
        print('enter deco1:')
        result = func(*args, **kwargs)
        print('exit deco1')
        return result

    return wrap1


def deco2(func):
    print("This is deco2")

    def wrap2(*args, **kwargs):
        print('enter deco2')
        result = func(*args, **kwargs)
        print('exit deco2')
        return result

    return wrap2


def deco3(func):
    print("This is deco3")

    def wrap3(*args, **kwargs):
        print('enter deco3')
        result = func(*args, **kwargs)
        print('exit deco3')
        return result

    return wrap3


@deco1
@deco2
@deco3
def foo2(x, y):
    return x ** y

print('_' * 100)

foo2(3, 4)

 

This is deco3
This is deco2
This is deco1
____________________________________________________________________________________________________
enter deco1:
enter deco2
enter deco3
exit deco3
exit deco2
exit deco1

 上面的代码做的是一个三层的装饰的,首先Python还是根据语法糖,分别执行了三个装饰器,所以先输出了三个装饰函数里面的打印输出。

从初始化来看,装饰器初始化是从下到上的。再来分析一下,装饰器在执行装饰器函数的时候,做了什么赋值。

 下面是啰嗦的解释,不想看啰嗦的解释,应该foo2 = deco1(deco2(deco3(foo2)))

初始化过程中,首先foo2被装饰

foo2 = deco3(foo2)的返回函数wrap3

接下来:wrap3的函数继续传递给deco2

foo2 = deco2(wrap3)返回函数wrap2

再接下来:wrap2的函数继续传递给deco1

foo2 = deco1(wrap2)返回函数wrap1

所以初始化的最后,foo2变成了wrap1

所以在执行foo2()的过程,就变成了先执行wrap1,执行wrap1就要执行wrap1里面的func为wrap2,执行wrap2就是执行wrap2里面的wrap3,最后wrap3执行它里面的foo2函数。

但foo2函数执行后,从wrap3开始执行返回,通过result传递,最后传递到wrap1的result,为了执行foo,其实装饰器做了很多,感觉很绕。

很多框架的多层装饰器也差不多,为了判断你的函数能否执行,前面加了很多装饰器来帮助过滤参数,当你执行函数以后,也可以通过向外传递的过程中,设置一些条件,来限制最终的函数执行结果能不能传递出去。其实这也是我为什么现在不急着上手Django,Flask的原因,希望学好了基础,再去学那些框架,不仅知道如何用,更加知道为什么这么用,这样我觉的学起来会轻松很多。

 

再来一个带参数的装饰器,带参数的装饰器本质就是一个装饰器的生产机器,就是再装饰器的外面又套了一层函数,只不过返回了是这个装饰器。

 

def sum_rand(n):
    def lll(func):
        def wrap(*args, **kwargs):
            c = 0
            for i in range(n):
                c += func(*args, **kwargs)
            return c
        return wrap
    return lll




@sum_rand(90000)           # 执行了生成一个lll的装饰器
def fxx(x, y):
    return x * y

print(fxx(2, 3))

 上面是一个带参数的装饰器,返回的是一个两数乘积累加多次的和。

 

@sum_rand(90000)就是一个装饰器的生产器
其实@sum_rand(90000)就是lll装饰器函数,只不过为什么需要读取他的参数,套在装饰器的外层,然后让它返回一个装饰器。


写到这里函数的装饰器差不多就这些,回头来看,其实也就那么一回事,下一个章节,我会记录一下类的装饰器。

 


 
posted @ 2019-11-27 17:07  就是想学习  阅读(269)  评论(0编辑  收藏  举报