看了微信公众号推送的一道面试题,发现了闭包的问题,学习时间短,从来没有遇到过这种问题,研究一下。

Python函数作用域

  global:全局作用域

  local:函数内部作用域

  enclosing:函数与嵌套函数之间的作用域

  build-in:内置作用域

 #global
age = 60#全局变量 def func():
age = 40
#对于func来说是函数内部变量,对于in_func来说是enclosing变量
print('func-->',age)
age = age-40
print("func-->",age)
print('%x' % id(age))
def in_func():
print('in_func-->',age)
return in_func
f = func()
f()
print(f.__closure__)
age = age-40#全局变量
print('global-->',age)
 func--> 40
func--> 0
65d0c690
in_func--> 0
(<cell at 0x000001C4545F0C78: int object at 0x0000000065D0C690>,)
global--> 20 

  对于enclosing作用域,f = func() 就相当于f = in_func() 然后in_func()会运行,但是我之前的func函数已经运行完了按道理系统会回收func函数所定义的多有东西,为什么还会打印输出呢。

  因为in_func函数被返回,不会被回收,引用计数不为零。在infunc函数输出时会查找本地,然后嵌套,然后全局。当查找到嵌套的变量是发现了age。

由于发现了enclosing变量,infunc函数就会把这个变量放到自己的属性中,代码运行结果的输出可以证明,infunc函数的闭包变量的地址和func函数的age属性的地址一模一样。

这个就可以称为闭包。也就是内部函数中对enclosing作用域的变量进行引用。

  那么Python怎么创建闭包:

  1、函数必须有内嵌函数

  2、函数必须返回内嵌函数

  3、内嵌函数必须引用enclosing作用域 的变量

  那么接下来的装饰器就是闭包的一个典型的实现。。

  装饰器的作用就装饰一个函数,通俗的讲就是对已有的函数进行功能上的拓展,但是不能改变被装饰函数的源代码,不能改变被装饰函数的调用方式。

因为可能你的代码已经被放到线上运行,或者你的函数已经被别的部门调用......种种原因吧

  先看看简单例子:

 def foo():
print("in the foo") foo()

deco_1

  举例很简单就是运行了一个函数,输出为“in the foo”

  假设我们现在有了一个需求,我们需要知道这个函数运行了多长时间,怎么办?

  很简单嘛!加一个函数 timer ,将 foo 函数作为参数传进去就好了啊。

  (为了体现运行效果,加入了睡眠3秒,所有这个不能算改变源代码哈)

 import  time

 def foo():
time.sleep(3)
print("in the foo")
def timer(func):
start_time = time.time()
func()
stop_time = time.time()
print ("run time :%s"%(stop_time-start_time)) #foo()
timer(foo)

方法一

  但是这个方法也不好啊,因为你改变了函数的调用方式啊

  原来是   foo()   ,现在是  timer()  这个就有问题了。

  于是我们就模仿上边的方法,写了嵌套函数,然后最终调用还是foo()

  如下啦:

 import  time

 def timer(func):
def wrapper():
start_time = time.time()
func()
stop_time = time.time()
print ("run time : %s"%(stop_time-start_time))
return wrapper
def foo():
time.sleep(3)
print("in the foo") foo = timer(foo)
foo()

  这段代码就是一个简单的没有参数的装饰器了。。。

  逐行解读一下,当我们运行  foo = timer(foo) 时 timer函数先运行def wrapper 把wrapper函数刷入内存,但是并不会执行,然后return   wrapper函数名给foo,那么此时函数名wrapper就和函数名foo指向同一个内存地址了。这句话就结束了。

  下边一句foo()就相当于wrapper()。然后运行wrapper函数。此时那个func参数,就是内存地址为之前的foo的那个地址,所以这句话就是wrapper函数运行调用了以前的foo函数。。。。

  有点绕,当时学的时候可以说的一脸XX啊。在pycharm中加断点调试会看的更清晰。

  但是Python觉得这么写代码还是多,于是搞出了一个叫  语法糖  的东西。。就是下边这个样子

 import  time

 def timer(func):
def wrapper():
start_time = time.time()
func()
stop_time = time.time()
print ("run time : %s"%(stop_time-start_time))
return wrapper @timer#语法糖 其实就是相当于在调用foo函数前加了foo = timer(foo)这个代码
def foo():
time.sleep(3)
print("in the foo") #foo = timer(foo)
foo()

  顿时高大上了不少哈

  简单装饰器就写完了,但是这个装饰器基本上没有什么卵用......

  函数运行正常都有参数吧。。。

  所以上边的都是铺垫,接下来进入正戏了:

  模拟一个用户登录验证的小程序

 def verify(func):
def wrapper(username,passwd):
if username == "sunqi" and passwd == "":
print ('verify successful')
func(username,passwd)
else:
print ('verify faild...')
exit()
return wrapper @verify
def login(username,passwd):
print ("welcome!") if __name__ == '__main__': user = input("username >>:")
password = input('password >>:')
login(user,password)

后续还有带参数的装饰器

还有那个没懂得面试题……

  

Python自学之路——自定义简单装饰器的更多相关文章

  1. python学习之路 六 :装饰器

    本节重点: 掌握装饰器相关知识 ​ python装饰器就是用于拓展原来函数功能的一种函数,这个函数的特殊之处在于它的返回值也是一个函数,使用python装饰器的好处就是在不用更改原函数的代码前提下给函 ...

  2. python自学第9天,装饰器

    装饰器:本质是函数(装饰其它函数) 就是为其它函数添加附加功能 原则:1.不能修改被装饰函数的源代码 2.不能修改被装饰的函数的调用方式 实现装饰器知识储备: 1.函数即变量 2.高阶函数:a.把一个 ...

  3. Python菜鸟之路:Python基础-逼格提升利器:装饰器Decorator

    一.装饰器 装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理等. 装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身 ...

  4. Python进阶(七)----带参数的装饰器,多个装饰器修饰同一个函数和递归简单案例(斐波那契数列)

    Python进阶(七)----带参数的装饰器,多个装饰器修饰同一个函数和递归简单案例(斐波那契数列) 一丶带参数的装饰器 def wrapper_out(pt): def wrapper(func): ...

  5. Python进阶内容(二)--- 装饰器

    谈装饰器前,需要明白一件事,Python 中的函数和 Java.C++不太一样,Python 中的函数可以像普通变量一样当做参数传递给另外一个函数,例如: def foo(): print(" ...

  6. Python之命名空间、闭包、装饰器

    一.命名空间 1. 命名空间 命名空间是一个字典,key是变量名(包括函数.模块.变量等),value是变量的值. 2. 命名空间的种类和查找顺序 - 局部命名空间:当前函数 - 全局命名空间:当前模 ...

  7. Python基础(七) python自带的三个装饰器

    说到装饰器,就不得不说python自带的三个装饰器: 1.@property   将某函数,做为属性使用 @property 修饰,就是将方法,变成一个属性来使用. class A(): @prope ...

  8. Python之面向对象:闭包和装饰器

    一.闭包 1. 如果一个函数定义在另一个函数的作用域内,并且引用了外层函数的变量,则该函数称为闭包. def outter(): name='python' def inner(): print na ...

  9. Python自学之路---Day13

    目录 Python自学之路---Day13 常用的三个方法 匹配单个字符 边界匹配 数量匹配 逻辑与分组 编译正则表达式 其他方法 Python自学之路---Day13 常用的三个方法 1.re.ma ...

随机推荐

  1. 第一个Python工程

    创建你的第一个Python程序 如果你曾经很熟悉visual studio的工作方式.可能对python不习惯. 工程通常只与你使用的IDLE有关系.这些工具习惯将文档,编译,测试集成一体.所以就存在 ...

  2. CodeForces 137C【贪心+优先队列】

    这种区间的贪心好像都出"烂"了? 不过还是想写一下... 先按照区间左端点排序一下,然后搞个优先队列维护当前最小的右端点. #include <bits/stdc++.h&g ...

  3. 洛谷P4121 [WC2005]双面棋盘(线段树套并查集)

    传送门 先膜一下大佬->这里 据说这题正解是LCT,然而感觉还是线段树套并查集的更容易理解 我们对于行与行之间用线段树维护,每一行内用并查集暴力枚举 每一行内用并查集暴力枚举连通块这个应该容易理 ...

  4. [Xcode 实际操作]四、常用控件-(9)普通警告窗口的使用

    目录:[Swift]Xcode实际操作 本文将演示警告窗口的使用方法. 警告窗口不仅可以给用户展现提示信息,还可以提供若干选项供用户选择. 在项目导航区,打开视图控制器的代码文件[ViewContro ...

  5. Java——利用集合类实现简单斗地主发牌

    import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util ...

  6. ajax异步请求问题

    今天在使用异步请求删除图片时,想在页面测试是不是有效果,使用halt完全没反应,我以为是AJAX请求地址有问题,没有请求到这个方法中,但是在控制台中network的请求地址是正常的,后来反应过来了,异 ...

  7. haoi2018奇怪的背包题解

    题目传送门:https://www.lydsy.com/JudgeOnline/problem.php?id=5302 对于一个物品,设它体积为v,那么,在背包参数为p的情况下,它能达到gcd(v,p ...

  8. MD5-UTF8-大写加密

    private string GetMD5Hash(string str) { string md5Str = ""; byte[] buffer = Encoding.UTF8. ...

  9. Luogu P4551 最长异或路径 01trie

    做一个树上前缀异或和,然后把前缀和插到$01trie$里,然后再对每一个前缀异或和整个查一遍,在树上从高位向低位贪心,按位优先选择不同的,就能贪出最大的答案. #include<cstdio&g ...

  10. 什么是SG?+SG模板

    先,定义一下 状态Position P 先手必败 N x先手必胜 操作方法: 反向转移 相同状态 不同位置 的一对 相当于无 对于ICG游戏,我们可以将游戏中每一个可能发生的局面表示为一个点.并且若存 ...