欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

闭包与装饰器简单概括

程序员文章站 2022-07-12 10:01:14
...

     装饰器在Python2.4 就开始使用了,装饰器可以说是一个比较厉害的功能.但是 我也是刚开始学装饰器的时候,比较不好理解装饰器的思想. 我希望我的这篇文章,能给刚开始学习装饰器的人,带来更简单的理解.当然也非常感谢,有那么多人写过相关的内容,我也是不断看别人的博客,学习.现在我也想分享一下装饰器,希望可以给刚开始学装饰器的你,提供一些帮助.


1 闭包

在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了*变量的函数。这个被引用的*变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。

1.引入 嵌套函数

def print_msg():
    msg = "zen of python"

    def printer():
        # printer是嵌套函数
        print(msg)

    printer()
    
print_msg()

那么有没有一种可能即使脱离了函数本身的作用范围,局部变量还可以被访问得到呢?答案是闭包

def print_msg():
    # print_msg 是外围函数
    msg = "zen of python"
    def printer():
        # printer 是嵌套函数
        print(msg)
    return printer

another = print_msg()

another()

现在来实现一个功能 有一个avg 函数 , 它 的作用是计算不断 增加的均值 例如整个历史中某个商品的的平均收盘价,. 每天都会增加新的价格.因此平均值耀考虑到目前为止所有的价格.

avg(10)
10
avg(15)
12.5
avg(10)
11.66


class Average:

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

    def __call__(self, price, *args, **kwargs):
        self.prices.append(price)
        total = sum(self.prices)
        return total / len(self.prices)


if __name__ == '__main__':
    avg = Average()

    print(avg(10))
    print(avg(15))
    print(avg(10))

这样实现看起来没有问题, 来看下 这个实现

def make_averager():
    prices = []

    def averager(price):
        prices.append(price)
        total = sum(prices)
        return total / len(prices)

    return averager


if __name__ == '__main__':
    avg = make_averager()

    print(avg(10))
    print(avg(15))
    print(avg(10))
    


# avg.__code__.co_varnames
# ('price', 'total')
# avg.__code__.co_freevars
# ('prices',)

思考 ??? avg 如何 存储历史的值的呢?

对 Average 这个应该比较清楚, self.prices 来保存这些信息的呢?
而 make_average 如何保存这些信息的呢?

闭包与装饰器简单概括
闭包与装饰器简单概括闭包与装饰器简单概括

这里我用绿色画出来的东西,就是闭包
prices 是什么呢?
其实就是一个*的变量. (free variable)
闭包与装饰器简单概括

2 装饰器的执行时间

装饰器 是在导入模块 的时候就已经执行.
这和普通的函数有点不太一样

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
@author: Frank 
@contact: aaa@qq.com
@file: test_deco.py
@time: 2018/4/23 下午11:11

"""

registry = []


def register(fun):
    print('running register({})'.format(fun))
    registry.append(fun)
    return fun


@register
def f1():
    print('running f1()')


@register
def f2():
    print('running f2()')


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


def main():
    print('running main()')
    print('registry --> ', registry)


if __name__ == '__main__':
    main()
    
# 结果如下
running register(<function f1 at 0x10eb366a8>)
running register(<function f2 at 0x10eb9b620>)
running main()
registry -->  [<function f1 at 0x10eb366a8>, <function f2 at 0x10eb9b620>]

3 装饰器

装饰器是什么??

装饰器本质是一个函数,用来修饰你要装饰的函数,就是那么简单,不要想的太复杂了. 

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
@author: Frank 
@contact: aaa@qq.com
@file: test_deco2.py
@time: 2018/5/1 上午12:08

"""
import logging


def foo():
    print("I am  foo")


def deco(func2):
    func2()


def use_logging(func):
    logging.warning("%s is running" % func.__name__)
    func()


def use_logging2(func):
    def wrapper():
        logging.warning("%s is running" % func.__name__)
        return func()

    return wrapper


# @use_logging
def fun1():
    print('I am  fun1')


@use_logging2
def fun2():
    print('I am  fun2')


def fun3():
    print('I am  fun3')






if __name__ == '__main__':
    deco(foo)

    use_logging(fun1)

当你的函数需要参数的时候怎么办呢?

def wrapper(*args, **kwargs):
        # args是一个数组,kwargs一个字典
        logging.warn("%s is running" % func.__name__)
        return func(*args, **kwargs)
    return wrapper

2 装饰器 想要保存源信息

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
@author: Frank 
@contact: aaa@qq.com
@file: test_deco.py
@time: 2018/5/7 下午5:54

"""

import time
from functools import wraps

def timethis(func):
    '''
    Decorator that reports the execution time.
    '''

    # @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end - start)
        return result

    return wrapper


@timethis
def countdown(n):
    '''
    Counts down
    '''
    while n > 0:
        n -= 1


countdown(100000)

# countdown = timethis(countdown)


#  在命令行里面看看
countdown(100000)
countdown 0.011469841003417969
countdown.__name__
'wrapper'
countdown.__doc__


# 如果加上 wraps
# func __name__, __doc__ 还保存着.
countdown 0.00823211669921875
countdown.__name__
'countdown'
countdown.__doc__
'\n    Counts down\n    '

3 带参数的装饰器

就是装饰器,也加上参数.
就是在加一层函数,进行包装.

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@author: Frank 
@contact: aaa@qq.com
@file: test_deco.py
@time: 2018/4/28 下午7:43

装饰器功能,检查函数的入参, 如果全为空, 或者0 ,None,
直接不进行模型计算,直接返回一个 假的分数. 
如果参数有一个不为空,则进行 fun函数进行计算. 


"""
import inspect
import functools


def checked_arguments(score, prob):
    def _checked_arguments(f):
        @functools.wraps(f)
        def wrapper(*a, **k):
            d = inspect.getcallargs(f, *a, **k)
            valid_val = check_arguments(d)
            if valid_val:
                return f(*a, **k)
            else:
                return {
                    score: -1,
                    prob: 999
                }

        return wrapper

    return _checked_arguments


def check_arguments(d):
    val = d.values()
    return any(val)


@checked_arguments('payday_loan', 'pydayloan_prob')
def fun(a, b, c, d, *args, **kwargs):
    return a + 1, b + 1, c + 1, d + 1


if __name__ == "__main__":
    values = fun(1, 2, 3, 4, 5)
    # values = fun(None, None, None, None)

    print(values)

来让我们看一下,如何实现,对模型函数,进行入参检查的,

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
@author: Frank 
@contact: aaa@qq.com
@file: test_deco2.py
@time: 2018/5/10 下午11:31

https://docs.python.org/3/library/inspect.html#inspect.Signature.bind
https://docs.python.org/3/library/inspect.html#inspect.BoundArguments

"""
import functools
import inspect


def checked_arguments(f):
    @functools.wraps(f)
    def wrapper(*a, **k):
        sig = inspect.signature(f).bind(*a, **k)
        sig.apply_defaults()
        d = sig.arguments

        valid_val = check_arguments(d)
        if valid_val:
            return f(*a, **k)
        else:
            return {
                'score': -1,
                'prob': 999
            }

    return wrapper



def check_arguments(d):
    args = d.get('args')
    kw = d.get('kwargs')

    if not any(args):
        if not any(kw.values()):
            return False

    return True


@checked_arguments
def run(*args, **kwargs):
    """
    模型函数,计算分值用的.
    :param args:
    :param kwargs:
    :return:
    """
    return (args, kwargs)


class PayDayLoanBD:

    def __init__(self, data):
        self.data = data

    def calculate(self):
        d1 = dict()
        d1.update(self.data)

        result = run(**d1)
        return result




if __name__ == '__main__':
    data = {'name': 'frank', 'tongdun_1': 15, 'hangju': 18, 'jd_prob': 32.5}
    # data = {'name': None, 'tongdun_1': None, 'hangju': None, 'jd_prob': None}
    bd = PayDayLoanBD(data=data)

    val = bd.calculate()

    print(val)



来看下面的方法,如果修饰的不是一个普通的函数,而是一个成员函数,
这里写的有点问题. ... 传入不传入self, 是看你是否用了self, 如果没有用到self,就没有必要显示的传入self, 这个变量 会保存在*args 这个元祖里面...如果装饰器里面需要用到self.xxx ,这个时候就需要显示的传入self,来拿值. 就需要注意了, 需要传 self,写到包装函数里面就可以了.

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@author: Frank 
@contact: aaa@qq.com
@file: test_deco3.py
@time: 2018/4/15 下午7:43

"""
import functools


def run(**kwargs):
    """
    模型函数,计算分值用的.
    :param args:
    :param kwargs:
    :return:
    """
    return (kwargs)


def _check_arguments(d):
    return any(d.values())


def checked_arguments(score, prob):
    def _checked_arguments(f):
        @functools.wraps(f)
        def wrapper(self, *args, **kwargs):
            valid_val = _check_arguments(self.data)
            if valid_val:
                return f(self, *args, **kwargs)
            else:
                return {
                    score: -1,
                    prob: 999
                }

        return wrapper

    return _checked_arguments


class ScorecardLargeModel:

    def __init__(self, data):
        self.data = data

    @checked_arguments('cashloan_ab_risk_score', 'cashloan_ab_risk_prob')
    def fun(self):
        kwargs = dict(id_card_number='19920908059')
        kwargs.update(self.data)
        result = run(**kwargs)
        return result


if __name__ == "__main__":
    data = {'0': None, '1': 1111, '2': 2222, '3': None, '4': None, '5': None, '6': None, '7': None, '8': None, '9': None}
    # data = {'0': None, '1': None, '6': None, '7': None, '8': None, '9': None}
    large = ScorecardLargeModel(data)
    val = large.fun()
    print(val)

4 当装饰器修饰成员函数的时候

这里写的有点问题. ... 传入不传入self, 是看你是否用了self, 如果没有用到self,就没有必要显示的传入self, 这个变量 会保存在*args 这个元祖里面...如果装饰器里面需要用到self.xxx ,这个时候就需要显示的传入self,来拿值.

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""
@author: Frank 
@contact: aaa@qq.com
@file: test_deco4.py
@time: 2018/4/15 下午9:43

#  当装饰器 修饰  成员函数的时候.

该如何处理呢?

"""
import functools
import time


def fn_timer(fn):
    """
    计算 fn 的运算时间
    :param fn:
    :return:
    """

    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = fn(*args, **kwargs)
        end = time.time()
        print(fn.__name__ + '  total running time %s seconds' % str(end - start))
        return result

    return wrapper


def _check_arguments(d):
    return any(d.values())


def checked_arguments(f):
    """
    保存的code
    :param f:
    :return:
    """

    @functools.wraps(f)
    def wrapper(self, *args, **kwargs):
        valid_val = _check_arguments(self.data)
        if valid_val:
            return f(self, *args, **kwargs)
        else:
            return {
                'score': -1,
                'prob': 999
            }

    return wrapper


class ScorecardLargeModel:
    
    def __init__(self, data):
        self.data = data

    @checked_arguments
    def fun1(self, months):
        return 'fun1  running. months={}'.format(months)

    @fn_timer
    def fun2(self, name):
        time.sleep(0.5)
        return "fun2 running. name={}".format(name)


if __name__ == "__main__":
    # data = {'0': None, '1': 1111, '2': 2222, '3': None, '4': None, '5': None, '6': None, '7': None, '8': None, '9': None}
    data1 = {'0': None, '1': None, '6': None, '7': None, '8': None, '9': None}

    large = ScorecardLargeModel(data1)
    val = large.fun1(3)
    print(val)

    val2 = large.fun2('frank')
    print(val2)



# {'score': -1, 'prob': 999}
# fun2  total running time 0.5017671585083008 seconds
# fun2 running. name=frank



参考链接

https://foofish.net/python-closure.html

https://foofish.net/python-decorator.html

Graham Dumpleton 的文章详解Python的装饰器


总结: 本文总结装饰器的一些基本的使用, 装饰器的概念,以及装饰的执行时间,带参数的装饰器等.