一、函数及变量的作用

在python程序中,函数都会创建一个新的作用域,又称为命名空间,当函数遇到变量时,Python就会到该函数的命名空间来寻找变量,因为Python一切都是对象,而在命名空间中,都是以字典形式存在着,这些变量名,函数名都是索引,而值就是,对应的变量值和函数内存地址。在python中可以用globals()查看全局变量,locals()局部变量。

  1. >>> global_v = '全局变量'
  2. >>> def func():
  3. ... local_v = '局部变量'
  4. ... print(locals()) #调用locals()输出局部变量local_v
  5. >>> func()
  6. {'local_v': '局部变量'} #命名空间中都是以字典形式保存
  7. >>> print(globals())
  8. {.........,'global_v': '全局变量', 'func': <function foo at 0x00000092446C7F28>} #可以看到除了变量,函数名也作为索引,映射函数内存地址,是主程序命名空间的内容

可以看到内置函数globals()返回了一个所有python能识别的变量的字典,而func 拥有自己的命名空间,里面包含了一个{'local_v': '局部变量'}元素

 

二、变量的作用规则
  • 在python中的变量作用域规则是:

    1.变量的创建,变量的创建总是会在函数命名空间创建一个新的变量。

    2.变量的访问,是先在函数内部,访问局部变量所在的函数命名空间,当找不到后再到外层,再到整个外层作用域去寻找该变量。 LEGB法则:索变量名的优先级:局部作用域 > 嵌套作用域 > 全局作用域 > 内置作用域

     

    当在函数中使用未确定的变量名时,Python会按照优先级依次搜索4个作用域,以此来确定该变量名的意义。首先搜索局部作用域(L),之后是上一层嵌套结构中def或lambda函数的嵌套作用域(E),之后是全局作用域(G),最后是内置作用域(B)。按这个查找原则,在第一处找到的地方停止。找不到就报错。NameError: name 'xxx' is not defined

     

    3.变量的修改,当函数尝试修改外层变量时,这是不行的,函数只能修改自己命名空间的变量。

     如果你非改不可,那只能在变量前面声明global 这样就可以改了
  1. >>> name = 'lina'
  2. >>> age = 22
  3. >>> list_1 = [1, 2, 3]
  4. >>> def fun():
  5. ... name = 'alex' #1 尝试修改,重赋值alex 给name
  6. ... print(name)
  7. ... print(age) #2 尝试查找函数命名空间中不存在的变量age, 没找到就去外层作用域找到
  8. ... list_1.append('local') # 4 此处修改list_1
  9. ... list_1.pop(0)
  10. ... print(list_1)
  11. >>> fun()
  12. 'alex'
  13. 22
  14. [2, 3, 'local']
  15. >>> print(name) #3 查看全局变量name 是否被函数修改成功,显然没有
  16. 'lina'
  17. >>> print(list_1)
  18. [2, 3, 'local'] #4 此处修改成功

通过上一个例子,我们可以从#1处看到,尝试给name赋值,在函数中成功了。

可是在#3处发现并没有改变name的值,这是因为函数已经开辟内存复制了一份name的值到自己的命名空间中,创建一个同名的新变量name,当fun()运行结束后该内存释放,而在#3处python寻找变量时直接在自己的作用域中找到name = 'lina'。

2处在自身的内存空间没有找到age变量,就去外层找到age= 22输出。

而在#1处就是所说的函数只能修改自身的变量,#4处对于列表、字典这种,可变对象,传过来的是内存地址,函数是复制了内存地址,然后直接去内存地址修改了,不能同变量混为一谈

 

三、函数的形参和实参

对于Python来说参数的传递是引用传递(不是值传递),形参名在函数中为局部变量。
对于不可变类型:str、number、tuple命名空间中的复制值改变不会影响外层空间的值。
但是对于可变类型如:list、dict 在函数体中的操作,可能就会改变他的值。

  1. >>> def fun(parameter): #形式参数
  2. ... parameter = parameter*2
  3. ... print(parameter)
  4. >>> fun(2) #实际参数
  5. 4

 

四、内嵌函数

python中的内嵌函数,即在函数内部声明函数,它所有的周期和生命力仍然适用。

  1. >>> def out_fun():
  2. ... a = '外层变量'
  3. ... def inner():
  4. ... print(a) #1
  5. ... inner() #2
  6. >>> out_fun()
  7. 外层变量
  • 处inner搜索自身命名空间,没找到变量a然后在外层的out_fun的局部变量中寻找到,inner作为内嵌函数拥有访问外层作用域的权限(有读和修改的权限-指复制到自身命名空间后修改)当函数调用结束就释放了。
  • 处函数out_fun在自身命名空间中找到变量名'inner',拿到内存地址然后执行函数

 

五、Python中的闭包

我们现在把内嵌函数作为out_fun的返回值,当out_fun()被执行时,就会定义inner函数,然后返回给fun变量

  1. >>> def out_fun():
  2. ... a = 'out变量'
  3. ... def inner():
  4. ... print(a) #1
  5. ... return inner
  6. >>> fun = out_fun()
  7. >>> fun.__closure__
  8. (<cell at 0x000000A3B57F0B28: str object at 0x000000A3B5AC3088>,)

现在来理解下这个函数,如果按照变量的作用域规则,在#1处inner首先会在自己的命名空间中去寻找变量a,没找到然后再去外层out_fun寻找。

所以当我们执行由out_fun()返回的fun时,按照道理这个程序是会报错的。因为当out_fun()执行完毕后就会释放内存,a变量就不存在了,所以当你执行fun时,inner无法找到a变量就会报错。我们试试看结果如何:

  1. >>> def out_fun():
  2. ... a = 'out变量'
  3. ... def inner():
  4. ... print(a)
  5. ... return inner
  6. >>> fun = out_fun()
  7. >>> fun()
  8. out变量

程序并没有报错,这并不矛盾,因为python支持一个名为闭包的特性,从fun.__closure__属性我们看见了,cell at 0x000000A3B57F0B28: str object at 0x000000A3B5AC3088,

即在不是全局的定义中,定义函数inner(即嵌套函数)时,会记录下外层函数的命名空间,这个对象就保存在.__closure__属性中,去这儿就是找到外层函数的命名空间。

 

六、装饰器

装饰器的核心原理就是上面我们理解到的了。装饰器是一个以函数作为参数并返回一个替换函数的可执行函数。

  1. >>> def out_fun(fun): #1接受函数作为参数
  2. ... def inner(a, b= 0, *args):
  3. ... print('装饰器先运行0.0')
  4. ... result = fun(a) + b #2运行传过来的被装饰函数
  5. ... print('装饰后结果为:',result)
  6. ... return result
  7. ... return inner
  8. >>> def foo(x): #3定义foo函数
  9. ... print('---------------\n这是被装饰函数')
  10. ... result = 2*x
  11. ... print('被装饰函数执行结果为:{}\n--------------'.format(result))
  12. ... return 2*x
  13. >>> decorate_foo = out_fun(foo) #4将foo函数作为jout_fun参数执行out_fun
  14. >>> foo =decorate_foo #把装饰过的foo函数decorate_foo 重赋值给foo,再调用foo()
  15. >>> foo()
  16. 装饰器先运行0.0
  17. ---------------
  18. 这是被装饰函数
  19. 被装饰执行结果为:4
  20. ---------------
  21. 装饰后结果为: 2

现在来理解下这段程序,#1处定义了一个函数,他只接受函数作为参数,#2出运行传过来的被装饰函数,#3定义了一个函数,#4处将#3定义的foo作为参数传给out_fun(foo)得到被装饰后decorate_foo,然后再将装饰后的函数重新赋值给foo,然后当你再次运行foo函数的时候,永远都是得到被装饰后的结果了。

讲到这儿就说个实际应用列子吧!

如汽车在公路上行驶,按照某地交通规则,在国道上限速最高80迈,无下限,高速公路上最低60迈最高120迈。

我们原始程序,通过测速传感器传来的参数计算出汽车当前速度,并返回该速度。

  1. >>> status = 1
  2. >>> def car_speed(angular_speed, radius = 0.35) #根据传来的角速度参数,以及半径计算出当前速度
  3. ... current_speed = angular_speed*radius*3.6
  4. ... return current_speed
  5. >>>
  6. >>> def slowdown():
  7. ... pass #假设调用此函数是调用刹车、减速系统,会减慢汽车速度
  8. >>>
  9. >>> def decorate_fun(fun):
  10. ... def inner(*args, **kwargs):
  11. ... current_speed = fun(args[0]) if len(args) = 1 else fun(args[0], radius = args[1])
  12. ... if current_speed >110:
  13. ... sys.stdout.write('您已超速!')
  14. ... sys.stdout.flush()
  15. ... elif current_speed > 160:
  16. ... sys.stdout.write('超速50%系统已限速,请注意安全')
  17. ... sys.stdout.flush()
  18. ... slowdown()
  19. ... elif current_speed < 60:
  20. ... sys.stdout.write('该路段限速60,请注意')
  21. ... sys.stdout.flush()
  22. ... else: pass
  23. ... return current_speed
  24. ... return inner
  25. >>>
  26. >>> decorator_car_speed = decorate_fun(car_speed)
  27. >>> decorato_car_speed(120)
  28. 您已超速!

这段程序,当汽车在国道等非限速区域是,直接调用car_speed()函数就可以得到速度,而当行驶上高速公路后,就存在边界值问题,我们可以使用装饰后的decorate_car_speed()函数来处理。

 

七、装饰器符号@ 的应用

通过前面已经了解了装饰器原理了,这儿就简单说下@ 的应用。@ 只是python的一种语法糖而已,让程序看起更美观,简洁

  1. >>> def decorator_foo(fun):
  2. ... def inner(*args, **kwargs):
  3. ... fun(*args, **kwargs)
  4. ... pass
  5. ... return inner
  6. >>>
  7. >>> @decorator_foo #1
  8. >>> def foo(*args, **kwargs): #2
  9. ... pass
  10. >>>

在#1处@decorator_foo 使用@符号+装饰器函数,在被装饰函数的上方,记住一定要正上方挨着不能空行,就等于前面所学的decorator = decorator_foo(foo) + foo = decorator() 这样以后你调用foo就是调用的被装饰后的foo了

 

八、讲一个厉害的装饰器应用

- 情形和需求是这样的,比如我在django view 下做用户验证(不用session),有home函数处理普通用户请求,index处理管理员请求,bbs返回论坛请求,member处理会员请求。

  • 当然我们如果在每一个函数内都做一次验证,那代码重复就太多了,所以选择用装饰器,不失为一个好方法。可是现在们要求,根据不同的函数,home、bbs、member都在本地数据库验证,而index做ldap验证,意思就是我们要在一个装饰器里面,根据不同的函数做不同的验证。

一般的验证:

  1. def _authentication(r):
  2. print('假设使用这个函数做本地用户认证,过了返回True,错误返回False')
  3. return #返回验证结果
  4. def auth(fun): #装饰器函数
  5. def wrapper(request, *args, **kwargs):
  6. if _authentication(request): #调用验证函数
  7. result = fun(request)
  8. return result
  9. else:
  10. return '用户名或密码错了,重新登录吧!'
  11. return wrapper
  12. @auth
  13. def index(request):
  14. pass
  15. @auth
  16. def home(request):
  17. pass
  18. @auth
  19. def bbs(request):
  20. pass
  21. @auth
  22. def member(request):
  23. pass

全部代码我就不写了,太多复杂了,就用伪代码,逻辑描述来代替了。

可以看出来,我们这个函数可以实现用户验证功能,不管你使用cookie也好,去本地数据库取数据也罢。但是我们上面说的需求,把index来的请求分离出来,做ldap验证,显然这样的装饰器是没法做到的。无法识别谁来的请求。

@装饰器还提供了一功能,能解决这个问题,往下看:

  1. def _authentication(r):
  2. print('假设使用这个函数做本地用户认证,过了返回True,错误返回False')
  3. return #返回验证结果
  4. def _ldap(r):
  5. print('ldap验证')
  6. return #返回ldap验证结果
  7. def auth(souce_type):
  8. #这儿的souce_type参数就是@auth(v)运行时传过来的参数
  9. def outer(fun):
  10. def wrapper(request, *args, **kwargs):
  11. if souce_type == 'local': #* 1 如果请求来源标记是'local'就本地验证
  12. if _authentication(request):
  13. result = fun(request)
  14. return result
  15. else:
  16. return '用户名或密码错了,重新登录吧!'
  17. elif souce_type == 'ldap': #* 1 如果请求来源标记是'ldap'就ldap验证
  18. if _ldap(request):
  19. return fun(request)
  20. else:
  21. return '用户名或密码错了,重新登录吧!'
  22. return wrapper
  23. return outer
  24. @auth(souce_type = 'ldap') #3 装饰
  25. def index(request):
  26. pass
  27. @auth(souce_type = 'local') #4
  28. def home(request):
  29. pass
  • 注意#3,#4处,我们把auth('parameter')加参数运行了一次,而装饰器函数auth里面进行了三层嵌套,auth---->outer----->wrapper,你可以这样理解,原来的@auth @符号会把后面的内容auth运行一次直接就返回了wrapper, 现在,我们自己把auth('parameter')加参数运行了一次得到outer,@auth(parameter)就等同于 @outer,@符号把后面的outer运行一次后再得到wrapper并赋给被修饰函数,而函数souce_type来源也被我们带进了装饰器。

python 函数及变量作用域及装饰器decorator @详解的更多相关文章

  1. python函数知识七 闭包、装饰器一(入门)、装饰器二(进阶)

    21.闭包 闭包:在嵌套函数内,使用非全局变量(且不使用本层变量) 闭包的作用:1.保证数据的安全性(纯洁度).2.装饰器使用 .__closure__判断是否是闭包 def func(): a = ...

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

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

  3. 进阶Python:装饰器 全面详解

    进阶Python:装饰器 前言 前段时间我发了一篇讲解Python调试工具PySnooper的文章,在那篇文章开始一部分我简单的介绍了一下装饰器,文章发出之后有几位同学说"终于了解装饰器的用 ...

  4. Python函数篇(5)-装饰器及实例讲解

    1.装饰器的概念   装饰器本质上就是一个函数,主要是为其他的函数添加附加的功能,装饰器的原则有以下两个: 装饰器不能修改被修饰函数的源代码 装饰器不能修改被修改函数的调用方式   装饰器可以简单的理 ...

  5. Python函数小结(2)-- 装饰器、 lambda

    本篇依然是一篇学习笔记,文章的结构首先讲装饰器,然后讲lambda表达式.装饰器内容较多,先简要介绍了装饰器语法,之后详细介绍理解和使用不带参数装饰器时应当注意到的一些细节,然后实现了一个简单的缓存装 ...

  6. Python函数进阶:闭包、装饰器、生成器、协程

    返回目录 本篇索引 (1)闭包 (2)装饰器 (3)生成器 (4)协程 (1)闭包 闭包(closure)是很多现代编程语言都有的特点,像C++.Java.JavaScript等都实现或部分实现了闭包 ...

  7. python装饰器学习详解-函数部分

    本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理 最近阅读<流畅的python>看见其用函数写装饰器部分写的很好,想写一些自己的读书笔记. ...

  8. python函数(3):装饰器

    昨天学了很多函数方面的概念和知识其中有一个闭包的函数.很多人都对闭包的作用不是很清楚,今天我们就来认识一个新的知识点装饰器.它就是闭包函数的一个经典应用. 预习: 编写装饰器,为多个函数加上认证的功能 ...

  9. python装饰器大详解

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

随机推荐

  1. 使用python scrapy爬取知乎提问信息

    前文介绍了python的scrapy爬虫框架和登录知乎的方法. 这里介绍如何爬取知乎的问题信息,并保存到mysql数据库中. 首先,看一下我要爬取哪些内容: 如下图所示,我要爬取一个问题的6个信息: ...

  2. Swift中的反射

    原文:http://www.cocoachina.com/applenews/devnews/2014/0623/8923.html Swift 事实上是支持反射的.只是功能略弱. 本文介绍主要的反射 ...

  3. centos7下安装docker(10容器底层--cgroup和namespace)

    cgroup和namespace是实现容器底层的重要技术 cgroup:实现资源限制 namespace:实现资源隔离 1.cgroup:control group Linux操作系统通过cgroup ...

  4. Java逻辑运算

    逻辑运算是在关系运算基础之上的运算,能处理更加复杂的问题 逻辑运算的结果是 true 或 false 一.逻辑运算的种类: 在java的逻辑运算符中,有这么四类&&(短路与).& ...

  5. Spring Security 用户认证原理分析

    本文基于 spring-security-core-5.1.1 和 tomcat-embed-core-9.0.12. 核心原理 用户通过 username 和 password 登录时,后端会经过一 ...

  6. 运行Android Studio自带模拟器报:Guest isn't online after 7 second...

    今天在运行Android Studio自带的手机模拟器时,出现如下异常情况 : 解决办法: 1.打开Android Virtue Device Manager,点击编辑选项 2.点击show Adva ...

  7. SkylineGlobe API 如何以图层的方式导入MPT地形

    测试环境:TerraExplorer Pro 6.6; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&qu ...

  8. python游戏开发之俄罗斯方块(一):简版

    编程语言:python(3.6.4) 主要应用的模块:pygame (下面有源码,但是拒绝分享完整的源码,下面的代码整合起来就是完整的源码) 首先列出我的核心思路: 1,图像由"核心变量&q ...

  9. jquery $.each()遍历json数组

    使用jQuery的$.each()方法来遍历一个数组对象 var json=[ {"id":"1","tagName":"appl ...

  10. 查看CentOS/Linux的版本信息

    今天在安装MySql的时候,想选择linux的版本对应的MySql. 1.查看内核版本和x86/x64版本 方法一.cat /proc/version [root@sxl129 Desktop]# c ...