Python 的闭包和装饰器
翻译: TheLover_Z
Part I
原文地址: http://blaag.haard.se/Python-Closures-and-Decorators–Pt–1/
回想起来,当初我做出了错误的选择,把 Python 的课程削减到了4个小时以至于把装饰器的部分搞砸了,我答应大家我稍后会对闭包和装饰器做一个更好的解说 —— 我是这么打算的。
函数也是对象。实际上,在 Python 中函数是一级对象——也就是说,他们可以像其他对象一样使用而没有什么特别的限制。这给了我们一些有趣的选择,我会由浅到深解释这个问题。
关于函数就是对象的一个最常见的例子就是 C 中的函数指针;将函数传递到其他的将要使用它的函数。为了说明这一点,我们来看看一个重复函数的实现 —— 也就是,一个函数接受另外一个函数以及一个数字当作参数,并且重复调用指定函数指定次数:
>>> #A very simple function >>> def greeter(): … print("Hello") … >>> #An implementation of a repeat function >>> def repeat(fn, times): … for i in range(times): … fn() … >>> repeat(greeter, 3) Hello Hello Hello >>>
这种模式在很多情况下都有用 —— 比如向一个排序算法传递比较函数,向一个语法分析器传递一个装饰器函数,通常情况下这些做法可以使一个函数的行为 更专一化 ,或者向已经抽象了工作流的函数传递一个待办的特定部分(比如, sort()
知道怎么排序, compare()
知道怎么比较元素)。
函数也可以在其他函数的内部声明,这给了我们另一个很重要的工具。在一般情况下,这可以用来隐藏实用函数的实现细节:
>>> def print_integers(values): … def is_integer(value): … try: … return value == int(value) … except: … return False … for v in values: … if is_integer(v): … print(v) … >>> print_integers([1,2,3,"4", "parrot", 3.14]) 1 2 3
这可能是有用的,但它本身并不算是个强大的工具。相比函数可以当作参数被传递而言,我们可以将它们包装(wrap)在另外的函数中,从而向已经构建好的函数增加新的行为。一个简单的例子是向一个函数增加跟踪输出:
>>> def print_call(fn): … def fn_wrap(*args, **args): #take any arguments … print ("Calling %s" % (fn.func_name)) … return fn(*args, **kwargs) #pass any arguments to fn() … return fn_wrap … >>> greeter = print_call(greeter) #wrap greeter >>> repeat(greeter, 3) Calling fn_wrap Hello Calling fn_wrap Hello Calling fn_wrap Hello >>> >>> greeter.func_name 'fn_wrap'
正如你看到的那样,我们可以使用带日志的函数来替换掉现有函数相应的部分,然后调用原来的函数。在例子的最后两行,函数的名字已经反映出了它已经被改变,这个改变可能是我们想要的,也可能不是。如果我们想包装一个函数同时保持它原来的名字,我们可以增加一行 print_call
函数,代码如下:
>>> def print_call(fn): … def fn_wrap(*args, **kwargs): #take any arguments … print("Calling %s" % (fn.func_name)) … return fn(*args, **kwargs) #pass any arguments to fn() … fn_wrap.func_name = fn.func_name #Copy the original name … return fn_wrap
因为这是一个很长的话题,我明天会来更新第二部分,我们会讲讲闭包,偏函数(partial),还有(终于到它了)装饰器。
至此,如果这些你之前全部没有接触过,可以先用 print_call
函数作为基础,来创建一个能够在正常调用函数之前先打印出这个函数名字的一个修饰器。
Part II
原文地址: http://blaag.haard.se/Python-Closures-and-Decorators–Pt–2/
在第一部分中,我们学习了以函数作为参数调用其他的函数,还有嵌套函数,最终我们把一个函数包装在另外的函数中。我们先把第一部分的答案给出:
>>> def print_call(fn): … def fn_wrap(*args, **kwargs): … print("Calling %s with arguments: \n\targs: %s\n\tkwargs:%s" %fn.__name__, args, kwargs)) … retval = fn(*args, **kwargs) … print("%s returning '%s'" % (fn.func_name, retval)) … return retval … fn_wrap.func_name = fn.func_name … return fn_wrap … >>> def greeter(greeting, what='world'): … return "%s %s!" % (greeting, what) … >>> greeter = print_call(greeter) >>> greeter("Hi") Calling greeter with arguments: args: ('Hi',) kwargs:{} greeter returning 'Hi world!' 'Hi world!' >>> greeter("Hi", what="Python") Calling greeter with arguments: args: ('Hi',) kwargs:{'what': 'Python'} greeter returning 'Hi Python!' 'Hi Python!' >>>
这稍微有那么点儿用了,但它可以变的更好!你可能听说过或者没有听说过*闭包*,你可能听说过成千上万种闭包定义中的某一种或者某几种 —— 我不会那么挑剔,我只是说闭包就是一个捕捉了(或者关闭)非本地变量(自由变量)的代码块(比如一个函数)。如果你不清楚我在说什么,你可能需要进修一下 CS 的相关课程,但是不要担心 —— 我会给你演示例子。闭包的概念很简单:一个可以引用在函数闭合范围内变量的函数。
比如说,看一下这个代码:
>>> a = 0 >>> def get_a(): … return a … >>> get_a() 0 >>> a = 3 >>> get_a() 3
正如你看到的那样, get_a
函数可以取得 a
的值,并且可以读取更新后的值。然而这里有一个限制 —— 被捕获的变量(captured variable,下同)不能被写入。
>>> def set_a(val): … a = val … >>> set_a(4) >>> a 3
为什么会这样?由于闭包不能写入任何被捕获的变量, a = val
这个语句实际上写入了本地变量 a
从而隐藏了模块级别的 a
,这正是我们想写入的内容。为了解决这个限制(也许这并不是一个好主意),我们可以用一个容器类型:
>>> class A(object): pass … >>> a = A() >>> a.value = 1 >>> def set_a(val): … a.value = val … >>> a.value 1 >>> set_a(5) >>> a.value 5
因此,我们已经知道了函数从它的闭合范围内捕捉变量,我们最终可以接触到有趣的东西了,我们先实现一个偏函数(partial,下同)。一个偏函数是一个你已经填充了部分或者全部参数的函数的实例;比如说你有一个存储了用户名和密码的会话,和一个查询后端的函数,这个函数有不同的参数但是*总是*需要身份验证。与其说每次都手动传递身份验证信息,我们可以用偏函数来预填充那些信息。
>>> #Our 'backend' function … def get_stuff(user, pw, stuff_id): … """Here we would presumably fetch data using the … credentials and id""" … print("get_stuff called with user: %s, pw: %s, stuff_id: %s" % (user, pw, stuff_id)) >>> def partial(fn, *args, **kwargs): … def fn_part(*fn_args, **fn_kwargs): … kwargs.update(fn_kwargs) … return fn(*args + fn_args, **kwargs) … return fn_part … >>> my_stuff = partial(get_stuff, 'myuser', 'mypwd') >>> my_stuff(3) get_stuff called with user: myuser, pw: mypwd, stuff_id: 3 >>> my_stuff(67) get_stuff called with user: myuser, pw: mypwd, stuff_id: 67
偏函数可以用在许多地方来消除代码的重复。当然,你没有必要自己手动实现它,只需要 from functools import partial
就可以了。
最后,我们来看看函数装饰器(未来可能有类装饰器)。函数装饰器接收一个函数作为参数然后返回一个新的函数。听起来很熟悉吧?我们已经实现过一个 print_call
装饰器了。
>>> @print_call … def will_be_logged(arg): … return arg*5 … >>> will_be_logged("!") Calling will_be_logged with arguments: args: ('!',) kwargs:{} will_be_logged returning '!!!!!' '!!!!!'
使用@符号标记是一个很方便的方法。
>>> def will_be_logged(arg): … return arg*5 … >>> will_be_logged = print_call(will_be_logged)
但是如果我们想要确定装饰器的参数呢?在这种情况下,作为装饰器的函数会接收参数,并且返回一个包装(wrap)了装饰器函数的函数。
>>> def require(role): … def wrapper(fn): … def new_fn(*args, **kwargs): … if not role in kwargs.get('roles', []): … print("%s not in %s" % (role, kwargs.get('roles', []))) … raise Exception("Unauthorized") … return fn(*args, **kwargs) … return new_fn … return wrapper … >>> @require('admin') … def get_users(**kwargs): … return ('Alice', 'Bob') … >>> get_users() admin not in [] Traceback (most recent call last):File "<stdin>", line 1, in <module>File "<stdin>", line 7, in new_fn Exception: Unauthorized >>> get_users(roles=['user', 'editor']) admin not in ['user', 'editor'] Traceback (most recent call last):File "<stdin>", line 1, in <module>File "<stdin>", line 7, in new_fn Exception: Unauthorized >>> get_users(roles=['user', 'admin']) ('Alice', 'Bob')
就是这样。你现在会写装饰器了,也许你会用这些知识去写面向方面(aspect-oriented)的编程。加入 @cache
, @trace
, @throttle
都是微不足道的(在你添加 @cache
之前,一定要检查 functools
,如果你用的是 Python 3 的话!)
from: http://pycoders-weekly-chinese.readthedocs.io/en/latest/issue3/python-closures-and-decorators.html
Python 的闭包和装饰器相关推荐
- python基础---闭包、装饰器
闭包.装饰器 注意:使用装饰器,最终都会改变装饰函数的函数名称及所属属性,所以在实际的装饰器中如果后续会涉及用到装饰函数函数名或所属属性的,需要加入python自带的模块@wraps确保函数装饰后,不 ...
- python 闭包_一篇文章读懂Python的闭包与装饰器!
什么是装饰器? 装饰器(Decorator)相对简单,咱们先介绍它:"装饰器的功能是将被装饰的函数当作参数传递给与装饰器对应的函数(名称相同的函数),并返回包装后的被装饰的函数", ...
- python提高——闭包、装饰器
目录 1闭包 2装饰器 2.1装饰器功能 2.2示例 1无参数的函数 2被装饰的函数有参数 3被装饰的函数有不定长参数 4装饰器中的return 5装饰器带参数,在原有装饰器的基础上,设置外部变量 6 ...
- Python的闭包和装饰器
什么是闭包 python中函数名是一个特殊的变量,它可以作为另一个函数的返回值,而闭包就是一个函数返回另一个函数后,其内部的局部变量还被另一个函数引用. 闭包的作用就是让一个变量能够常驻内存. def ...
- python的闭包及装饰器
闭包: 闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数.这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外.所以,有另一种说 ...
- Python之闭包与装饰器
闭包 由于闭包这个概念比较难以理解,尤其是初学者来说,相对难以掌握,所以我们通过示例去理解学习闭包. 给大家提个需求,然后用函数去实现:完成一个计算不断增加的系列值的平均值的需求. 例如:整个历史中的 ...
- python装饰器与闭包_python中闭包和装饰器的理解(关于python中闭包和装饰器解释最好的文章)。...
转载:http://python.jobbole.com/81683/ 呵呵!作为一名教python的老师,我发现学生们基本上一开始很难搞定python的装饰器,也许因为装饰器确实很难懂.搞定装饰器需 ...
- python闭包和装饰器的区别_python中闭包和装饰器的理解(关于python中闭包和装饰器解释最好的文章)。(转)...
呵呵!作为一名教python的老师,我发现学生们基本上一开始很难搞定python的装饰器,也许因为装饰器确实很难懂.搞定装饰器需要你了解一些函数式编程的概念,当然还有理解在python中定义和调用函数 ...
- Python之闭包、装饰器及相关习题练习
学会使用装饰器之前,首先要明白什么是闭包函数 1.闭包 (1)什么是闭包? 闭包:内部函数对外部函数作用域里变量的引用 闭包函数必须满足两个条件:1.函数内部定义的函数 2.包含对外部作用域而非全局作 ...
最新文章
- 微信程序跳转到页面底部 scroll-view
- 什么是容器、什么是docker、docker与虚拟机的对比、docker做什么、运行场景,Docker功能和组件,Docker的C/S模式的运行,Docker概念,相关概念
- SpringBoot的Thymeleaf支持,结合SpringMVC做的案例(学习SpringBoot实战)
- 前端还是后端?这些公号教你一锅端!
- Java两则故障分析和常见连接超时时间
- 二分查找的实现---java版本
- 使用apache配置基于IP地址的虚拟主机
- 【观点】微博的弊端和它的真正意义
- 小米11 Pro系列细节再曝光:大杯大升级!
- 计算机网络基础代码,计算机网络基础知识(示例代码)
- 服务器_Windows Server 2012 服务器之Web服务器
- Centos6.4升级glibc_2.14
- NLP-自然语言处理
- COMSOL 固体力学 第一主应力、第二主应力、第三主应力、径向应力、环向应力、切向应力 截面一维曲线
- ubuntu wine qq
- 哈工大人工智能暑期课实践项目——手写体识别四则运算
- 大数据实训室课程体系设计案例分享
- opencv3/C++ SURF特征检测
- JMicroVision教程-应用于测井岩层电镜图像分析
- 51单片机迷宫小车的设计
热门文章
- 详细的mongo工具手册,包含常见CURD+条件操作+聚合+案例
- 一流投资人会关注什么样的区块链初创公司?
- 谷歌发布TensorFlow 1.4与TensorFlow Lattice:利用先验知识提升模型准确度 搜狐科技 10-12 15:29 选自:Google Research Blog 参与:李泽南、
- Julie Desk:这个帮你安排会议的AI助手刚刚融了250万欧元
- 聚焦互联网应用和深度学习
- 白话Elasticsearch49-深入聚合数据分析之 Percentile Ranks Aggregation-percentiles rank以及网站访问时延SLA统计
- Android-2D绘图
- 流媒体实质上是计算机在哪方面的应用,流媒体技术主要用于什么
- android广告页白屏_android 白屏
- Nginx如何启动、停止与重启