原文发表在我的博客主页,转载请注明出处

前言

如果把python当作脚本语言,每次就是写个几十行上百行来处理数据的话,装饰器也许不是很必要,但是如果要开发一个大型系统,装饰器是躲不开的,最开始体会ryu的装饰器之美是在阅读ryu源码的时候,用python官网的一句话来说,learning about descriptors creates a deeper understanding of how python works and an appreciation for the elegance of its design。本篇文章从闭包讲起,也从闭包结束,因为如果理解了闭包,也就理解了装饰器。

python函数作用域LEGB

不论在什么语言中,变量都是必不可少的,在python中,一般存在以下几个变量类型,local:函数内部作用域;enclosing:函数内部与内嵌函数之间;global:全局作用域;build-in:内置作用域。解释器在遇到变量的时候,会按照如下的顺序进行查找:L > E > G > B,简称LEGB。下面以一个简单的函数来说明上述过程:

#coding:utf-8
#global var
passline = 60
def func(val):
# for func, it's local var
# for in_func, it's enclosing var
passline = 90
if val >= passline:
print "pass"
else:
print "fail"
def in_func():
print val
return in_func def Max(val1, val2):
# max is a built-in fun
return max(val1, val2) f = func(89)
f()
print Max(90, 100)

local,global,build-in这三个类型比较容易理解,enclosing变量呢?下一节详细讲解。

闭包理解与使用

首先理解下python中的函数,在python中,函数是一个对象(可以通过type函数查看),在内存中占用空间;函数执行完成之后内部的变量会被解释器回收,但是如果某变量被返回,则不会回收,因为引用计数器的值不为0;既然函数也是一个对象,他也拥有自己的属性;对于python函数来说,返回的不一定是变量,也可以是函数。

上一节提到了enclosing变量,在func函数中又定义了一个函数in_func,它输出了变量val,在输出过程中查找变量的时候发现本地没有,所以他就会去func函数里面找并且找到了,这个变量就是enclosing变量。在上面一段代码中,敏感的人可能已经发现了问题,分析代码执行的过程,func函数返回了in_func函数给了f,但是没有返回变量,所以在调用func完成之后val变量应该已经被解释器回收,但是在执行了f函数之后却仍然输出了val的值89,为什么呢?其原因就是:如果引用了enclosing作用域变量的话,会将变量添加到函数属性中,当再次查找变量时,不是去代码中查找,而是去函数属性中查找。可以通过如下代码进行验证:

#coding:utf-8
passline = 60
def func(val):
print "%x" %id(val)
if val >= passline:
print "pass"
else:
print "fail"
def in_func():
print val
in_func()
return in_func f = func(89)
f() #in_func
print f.__closure__

执行上面的代码,可以发现f函数的__closure__属性拥有一个变量,这个变量的ID和func函数中val变量的ID一样。

上面的代码是用来判断学生的成绩是否及格,即在百分制中60分及格,如果现在需要添加新的功能,即150分制中90分作为及格线,如何完成代码呢?最简单的就是我们创建两个函数,分别为func_100和func_150来完成判断,判断逻辑完全一样,代码如下:

#coding:utf-8

def func_150(val):
passline = 90
if val >= passline:
print "pass"
else:
print "fail" def func_100(val):
passline = 60
if val >= passline:
print "pass"
else:
print "fail" func_100(89)
func_150(89)

如果再增加应用场景呢?这样重复而没有任何技术含量的代码的增添十分繁琐,我们可以使用新的方法——闭包来解决这个问题。什么是闭包呢?闭包就是内部函数中对enclosing作用域的变量进行引用。代码如下:

#coding:utf-8
def set_passline(passline):
def cmp(val):
if val >= passline:
print "pass"
else:
print "fail"
return cmp f_100 = set_passline(60)
f_150 = set_passline(90)
print type(f_100)
print f_100.__closure__
f_100(89)
f_150(89)

在上述代码中,我们定义了一个set_passline函数,这个函数返回cmp函数,在这个函数中定义了cmp函数,在cmp函数中是我们之前的逻辑。在之后不论要进行多少分制的及格判断,只需要调用set_passline函数设置及格线就好,我们以百分制60分及格为例,分析上述代码的执行过程,基本分为两步,一是set_passline函数把返回值cmp函数给f_100,同时将60作为属性给cmp函数,二是f_100函数的执行其实相当于执行存储了passline的cmp函数。

接下来考虑一个问题,上面的passline是整数,能否换成函数?既然变量和函数都是对象,而且以python的灵活性,答案是肯定的。下面的代码同时描述了闭包的另一个应用场景,同时展示了如何使用。

#coding:utf-8
def my_sum(*arg):
if len(arg) == 0:
return 0
for val in arg:
if not isinstance(val, int):
return 0
return sum(arg) def my_average(*arg):
if len(arg) == 0:
return 0
for val in arg:
if not isinstance(val, int):
return 0
return sum(arg)/len(arg) def dec(func):
def in_dec(*arg):
if len(arg) == 0:
return 0
for val in arg:
if not isinstance(val, int):
return 0
return func(*arg)
return in_dec # 1.dec return in_dec -> my_sum
# 2.my_sum = in_dec(*arg)
my_sum = dec(my_sum)

在上面的代码中,首先定义了两个函数,这两个函数的功能大同小异,分别为求和函数和求平均值函数,由于求平均值元祖的长度不能为空,同时元祖中的数据都应该为整数,所以在每个函数中都先需要参数检查,本着以人为本的方针,这部门代码应该复用。所以在下面定义了另外一个函数dec,这个函数的参数为一个函数,返回值为一个内嵌函数,这个内嵌函数的逻辑和上面求和求平均值的逻辑一样,先判断,再返回结果。这个函数如何使用呢?比如现在我们要求和,代码如下:

def my_sum(*arg):
return sum(arg) def dec(func):
def in_dec(*arg):
if len(arg) == 0:
return 0
for val in arg:
if not isinstance(val, int):
return 0
return func(*arg)
return in_dec # 1.dec return in_dec -> my_sum
# 2.my_sum = in_dec(*arg)
my_sum = dec(my_sum)

my_sum = dec(my_sum)函数分两步,首先my_sum得到dec返回的in_dec函数,其次调用in_dec函数。

所以,遵上来看,闭包可以在很大程度上实现封装和代码复用。

装饰器

最开始就说,这篇博客始于闭包,终于闭包,所以装饰器不多说,只说四句话:

1.装饰器就是对闭包的使用;

2.装饰器用来装饰函数;

3.返回一个函数对象,被装饰的函数接收;

4.被装饰函数标识符指向返回的函数对象。

总结

接触python装饰器很久了,殊不知从闭包更容易理解装饰器。

python的闭包与装饰器的更多相关文章

  1. Python函数编程——闭包和装饰器

    Python函数编程--闭包和装饰器 一.闭包 关于闭包,即函数定义和函数表达式位于另一个函数的函数体内(嵌套函数).而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量.参数.当其中一个 ...

  2. python之闭包与装饰器

    python闭包与装饰器 闭包 在函数内部定义的函数包含对外部的作用域,而不是全局作用域名字的引用,这样的函数叫做闭包函数. 示例: #-------------------------------- ...

  3. Python之闭包and装饰器

    闭包和装饰器是Python中非常重要的一种语法格式,在日常工作中应用非常广泛. 首先,我先为大家简单的介绍一下闭包的概念. 闭包:闭包是在函数嵌套的基础上,内层函数使用到外层函数的变量,且外层函数返回 ...

  4. 关于python的闭包与装饰器的实验

    首先看闭包,在嵌套函数内添加返回值,可以通过外部函数读取内部函数信息 #encoding=utf-8 #闭包应用 #先定义闭包函数,并使用 def outer(func): def inner(): ...

  5. python中闭包和装饰器

    前言: 编程语言发展的过程中,我们为了提高代码利用率,发明了函数式编程.函数将代码封装起来,我们需要用到此功能函数的时候,调用一下就可以了.但是使用的过程中,也遇到了一些问题,比如函数实现的功能不够, ...

  6. Python的闭包和装饰器

    什么是闭包 python中函数名是一个特殊的变量,它可以作为另一个函数的返回值,而闭包就是一个函数返回另一个函数后,其内部的局部变量还被另一个函数引用. 闭包的作用就是让一个变量能够常驻内存. def ...

  7. Python高级--闭包与装饰器

    前言:在Python中,闭包是一种非常有用的功能!它通常与装饰器一起搭配使用,可以在不改变被装饰函数的功能的基础上,完成更多的功能.如权限认证. 一.如何定义闭包 1.闭包就是两个嵌套的函数,外层函数 ...

  8. python中闭包和装饰器的理解(关于python中闭包和装饰器解释最好的文章)

    转载:http://python.jobbole.com/81683/ 呵呵!作为一名教python的老师,我发现学生们基本上一开始很难搞定python的装饰器,也许因为装饰器确实很难懂.搞定装饰器需 ...

  9. Python作用域-->闭包函数-->装饰器

    1.作用域: 在python中,作用域分为两种:全局作用域和局部作用域. 全局作用域是定义在文件级别的变量,函数名.而局部作用域,则是定义函数内部. 关于作用域,我要理解两点:a.在全局不能访问到局部 ...

随机推荐

  1. nginx添加模块 (非覆盖安装)

    nginx添加模块(非覆盖安装) 原已经安装好的nginx,现在需要添加一个未被编译安装的模块: 查看原来编译时都带了哪些参数# /usr/local/nginx/sbin/nginx -V ngin ...

  2. Spring学习笔记之 Spring IOC容器(一)之 实例化容器,创建JavaBean对象,控制Bean实例化,setter方式注入,依赖属性的注入,自动装配功能实现自动属性注入

    本节主要内容:       1.实例化Spring容器示例    2.利用Spring容器创建JavaBean对象    3.如何控制Bean实例化    4.利用Spring实现bean属性sett ...

  3. HttpClent4.3 的例子

    package com.unbank.robotspider.util; import java.io.IOException; import java.net.MalformedURLExcepti ...

  4. Ubuntu升级内核

    看到微软开源.兼容.豁达,很高兴,今天研究一下Docker,查看Ubuntu内核版本,发现2.3有点低,不符合当前版本. 最低内核版本要求3.10. 升级Ubuntu内核. 首先 下载内核 网站htt ...

  5. Python Backup Files

    近来书写 Python 脚本进行替换以前的 shell 脚本,发现 Python 优于 shell 的最直观的一点Python 结构明了,可读性高(本人认为)在此做一些记录 本次记录利用 Python ...

  6. IOS版本被拒的经历

    IOS版本被拒的经历: 1,登陆方式依赖外部平台 因为我们的APP是只用微博登陆,想做成类似meerkat类型的,也能各种消息都同步微博. 结果当然行不通,这个确实是不听好人言,网上多个人都说过这个问 ...

  7. Varchar2 size how to decide?

    When you execute a complicate store procedure, maybe it will execute a long time, maybe you want to ...

  8. javaScript事件(四)event的公共成员(属性和方法)

    一.事件 二.事件流 以上内容见:javaScript事件(一)事件流 三.事件处理程序 四.IE事件处理程序 以上内容见javaScript事件(二)事件处理程序 五.事件对象 以上内容见javaS ...

  9. 《致命接触》:人畜共患传染病的故事,SARS一章非常精彩,四星推荐

    讲人畜共患的传染病的故事:亨德拉.埃博拉.疟疾.SARS.Q热.鹦鹉热.莱姆病.艾滋病等.作者比较会讲故事,又熟悉病毒传播相关的各种学科的知识(病毒学.生态学,还有一些我没记住的相关小学科),能把相关 ...

  10. HDU 4782 Beautiful Soup --模拟

    题意: 将一些分散在各行的HTML代码整理成标签树的形式. 解法: 模拟,具体见代码的讲解. 开始没考虑 '\t' .. 代码: #include <iostream> #include ...