闭包与装饰器简单概括

2024-05-12 01:48
文章标签 简单 闭包 装饰 概括

本文主要是介绍闭包与装饰器简单概括,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

     装饰器在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 printeranother = 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 averagerif __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: frank.chang@shoufuyou.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: frank.chang@shoufuyou.com
@file: test_deco2.py
@time: 2018/5/1 上午12:08"""
import loggingdef 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: frank.chang@shoufuyou.com
@file: test_deco.py
@time: 2018/5/7 下午5:54"""import time
from functools import wrapsdef 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 resultreturn wrapper@timethis
def countdown(n):'''Counts down'''while n > 0:n -= 1countdown(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: frank.chang@shoufuyou.com
@file: test_deco.py
@time: 2018/4/28 下午7:43装饰器功能,检查函数的入参, 如果全为空, 或者0 ,None,
直接不进行模型计算,直接返回一个 假的分数. 
如果参数有一个不为空,则进行 fun函数进行计算. """
import inspect
import functoolsdef 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 wrapperreturn _checked_argumentsdef 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 + 1if __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: frank.chang@shoufuyou.com
@file: test_deco2.py
@time: 2018/5/10 下午11:31https://docs.python.org/3/library/inspect.html#inspect.Signature.bind
https://docs.python.org/3/library/inspect.html#inspect.BoundArguments"""
import functools
import inspectdef checked_arguments(f):
    @functools.wraps(f)def wrapper(*a, **k):sig = inspect.signature(f).bind(*a, **k)sig.apply_defaults()d = sig.argumentsvalid_val = check_arguments(d)if valid_val:return f(*a, **k)else:return {'score': -1,'prob': 999}return wrapperdef check_arguments(d):args = d.get('args')kw = d.get('kwargs')if not any(args):if not any(kw.values()):return Falsereturn True@checked_arguments
def run(*args, **kwargs):"""模型函数,计算分值用的.:param args::param kwargs::return:"""return (args, kwargs)class PayDayLoanBD:def __init__(self, data):self.data = datadef calculate(self):d1 = dict()d1.update(self.data)result = run(**d1)return resultif __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: frank.chang@shoufuyou.com
@file: test_deco3.py
@time: 2018/4/15 下午7:43"""
import functoolsdef 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 wrapperreturn _checked_argumentsclass 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 resultif __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: frank.chang@shoufuyou.com
@file: test_deco4.py
@time: 2018/4/15 下午9:43#  当装饰器 修饰  成员函数的时候.该如何处理呢?"""
import functools
import timedef 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 resultreturn wrapperdef _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 wrapperclass ScorecardLargeModel:def __init__(self, data):self.data = data    @checked_argumentsdef fun1(self, months):return 'fun1  running. months={}'.format(months)    @fn_timerdef 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的装饰器


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

这篇关于闭包与装饰器简单概括的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/981261

相关文章

使用IntelliJ IDEA创建简单的Java Web项目完整步骤

《使用IntelliJIDEA创建简单的JavaWeb项目完整步骤》:本文主要介绍如何使用IntelliJIDEA创建一个简单的JavaWeb项目,实现登录、注册和查看用户列表功能,使用Se... 目录前置准备项目功能实现步骤1. 创建项目2. 配置 Tomcat3. 项目文件结构4. 创建数据库和表5.

使用PyQt5编写一个简单的取色器

《使用PyQt5编写一个简单的取色器》:本文主要介绍PyQt5搭建的一个取色器,一共写了两款应用,一款使用快捷键捕获鼠标附近图像的RGB和16进制颜色编码,一款跟随鼠标刷新图像的RGB和16... 目录取色器1取色器2PyQt5搭建的一个取色器,一共写了两款应用,一款使用快捷键捕获鼠标附近图像的RGB和16

四种简单方法 轻松进入电脑主板 BIOS 或 UEFI 固件设置

《四种简单方法轻松进入电脑主板BIOS或UEFI固件设置》设置BIOS/UEFI是计算机维护和管理中的一项重要任务,它允许用户配置计算机的启动选项、硬件设置和其他关键参数,该怎么进入呢?下面... 随着计算机技术的发展,大多数主流 PC 和笔记本已经从传统 BIOS 转向了 UEFI 固件。很多时候,我们也

基于Qt开发一个简单的OFD阅读器

《基于Qt开发一个简单的OFD阅读器》这篇文章主要为大家详细介绍了如何使用Qt框架开发一个功能强大且性能优异的OFD阅读器,文中的示例代码讲解详细,有需要的小伙伴可以参考一下... 目录摘要引言一、OFD文件格式解析二、文档结构解析三、页面渲染四、用户交互五、性能优化六、示例代码七、未来发展方向八、结论摘要

MyBatis框架实现一个简单的数据查询操作

《MyBatis框架实现一个简单的数据查询操作》本文介绍了MyBatis框架下进行数据查询操作的详细步骤,括创建实体类、编写SQL标签、配置Mapper、开启驼峰命名映射以及执行SQL语句等,感兴趣的... 基于在前面几章我们已经学习了对MyBATis进行环境配置,并利用SqlSessionFactory核

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu2289(简单二分)

虽说是简单二分,但是我还是wa死了  题意:已知圆台的体积,求高度 首先要知道圆台体积怎么求:设上下底的半径分别为r1,r2,高为h,V = PI*(r1*r1+r1*r2+r2*r2)*h/3 然后以h进行二分 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#includ

usaco 1.3 Prime Cryptarithm(简单哈希表暴搜剪枝)

思路: 1. 用一个 hash[ ] 数组存放输入的数字,令 hash[ tmp ]=1 。 2. 一个自定义函数 check( ) ,检查各位是否为输入的数字。 3. 暴搜。第一行数从 100到999,第二行数从 10到99。 4. 剪枝。 代码: /*ID: who jayLANG: C++TASK: crypt1*/#include<stdio.h>bool h

uva 10387 Billiard(简单几何)

题意是一个球从矩形的中点出发,告诉你小球与矩形两条边的碰撞次数与小球回到原点的时间,求小球出发时的角度和小球的速度。 简单的几何问题,小球每与竖边碰撞一次,向右扩展一个相同的矩形;每与横边碰撞一次,向上扩展一个相同的矩形。 可以发现,扩展矩形的路径和在当前矩形中的每一段路径相同,当小球回到出发点时,一条直线的路径刚好经过最后一个扩展矩形的中心点。 最后扩展的路径和横边竖边恰好组成一个直

poj 1113 凸包+简单几何计算

题意: 给N个平面上的点,现在要在离点外L米处建城墙,使得城墙把所有点都包含进去且城墙的长度最短。 解析: 韬哥出的某次训练赛上A出的第一道计算几何,算是大水题吧。 用convexhull算法把凸包求出来,然后加加减减就A了。 计算见下图: 好久没玩画图了啊好开心。 代码: #include <iostream>#include <cstdio>#inclu