一、名称空间
又名name space,比如变量x=1,那么名称空间正是存放名字x与1绑定关系的地方
名称空间共3种,分别如下:
- locals: 是函数内的名称空间,包括局部变量和形参
- globals: 全局变量,函数定义所在模块的名字空间
- builtins: 内置模块的名字空间
不同变量的作用域不同是由这个变量所在的命名空间决定的。
作用域即范围
- 全局范围: 全局存活,全局有效
- 局部范围: 临时存活,局部有效
查看作用域方法globals(),locals()
作用域查找顺序:
level = 'L0' n = 22 def func(): level = 'L1' n = 33 print(locals()) def outer(): n = 44 level = 'L2' print(locals(), n) def inner(): level = 'L3' print(locals(), n) # 此处打印的n是多少? inner() outer() func()
问题:在inner()里的打印的n的值是多少?
LEGB 代表名字查找顺序:locals —> enclosing function —> globals —> __builtins__
- locals 是函数内的名字空间,包括局部变量和形参
- enclosing 外部嵌套函数的名字空间
- globals 全局变量,函数定义所在模块的名字空间
- building 内置模块的名字空间
二、闭包
定义: 即函数定义和函数表达式位于另一个函数的函数体内(嵌套函数)。
而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就形成闭包。也就是说,内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必需访问其它外部函数的局部变量、参数以及其它内部函数。这些局部变量,参数和函数声明(最初时)的值是外部函数时的值,但也会收到内部函数的影响。
def outer(): name = 'mike' def inner(): print('在inner里打印外层函数的变量', name) return inner f = outer() f()
闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包囊了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包囊的作用域
三、装饰器
比如一家视频网站,有如下板块
def home(): print("---首页----") def america(): print("----欧美专区----") def japan(): print("----日韩专区----") def henan(): print("----河南专区----")
现在需求是要对“欧美"和”河南“专区进行收费,也就是登录之后才能看到,刚开始实现如下,每个板块里调用就可以了
user_status = False # 用户登录了就把这个改成True def login(): _username = 'mike' _password = '123' global user_staus if user_status == False: username = input('请输入用户名:').strip() password = input('请输入密码:').strip() if username == _username and password == _password: print('欢迎光临,%s' % username) user_staus = True else: print('用户名或密码错误,请重新输入!') else: print('用户已登录,验证通过!') def home(): print('首页'.center(20, '-')) def america(): login() # 执行前加上验证 print('欧美专区'.center(20, '-')) def japan(): print('日韩专区'.center(20, '-')) def henan(): login() # 执行前加上验证 print('河南专区'.center(20, '-')) home() america() henan()
你很自信的提交到代码给team leader审核,没有几分钟之后,代码被打回了,原因是:如果有很多需要加认证模块,你的代码虽然实现了功能,但是需要更改需要加认证的各个模块的代码,这直接违反了软件开发中的一个”开放-封闭“原则,简单来说,它规定已经实现的功能代码不允许被修改,但可以扩展,即:
- 封闭: 已实现的功能代码块不应该被修改
- 开放: 对现有功能的扩展开放
经过思考之后,想到了解决方案,不改源代码可以呀,可以用高阶函数:就是把一个函数当做一个参数传给另外一个函数,只需要写一个认证方法,每次调用需要验证的功能时,直接把这个功能的函数名当做一个参数传给我的验证模块就可以了,代码如下:
user_status = False # 用户登录了就把这个改成True def login(func): _username = 'mike' _password = '123' global user_status if user_status == False: username = input('请输入用户名:').strip() password = input('请输入密码:').strip() if username == _username and password == _password: print('欢迎光临,%s' % username) user_status = True else: print('用户名或密码错误,请重新输入!') if user_status == True: func() # 看这里看这里,只要验证通过了,就调用相应功能 # else: # print('用户已登录,验证通过!') def home(): print('首页'.center(20, '-')) def america(): # login() # 执行前加上验证 print('欧美专区'.center(20, '-')) def japan(): print('日韩专区'.center(20, '-')) def henan(): # login() # 执行前加上验证 print('河南专区'.center(20, '-')) home() login(america) # 需要验证就调用login,把需要验证的功能,当做一个参数传给login # america() # henan() login(henan)
终于实现了老板的要求,不改变原功能代码的前提下,给功能加上了验证,此时隔壁老王来了,你跟他分享了你写的代码,老王看后,笑笑说,你这个代码还是改改吧,要不然会被开除,WHAT?会开除,明明实现了功能呀,老王讲,没错,你功能是实现了,但是你又犯了一个大忌,什么大忌?你改变了调用方式呀,想一想,现在每个需要认证的模块,都必须调用你的login()方法,并把自己的函数名传给你,之前可不是这么调用的,试想,如果有100个模块需要认证,那这100个模块都得改变调用方式,这么多模块肯定不止是一个人写的,让每个人再去修改调用方式才能加上认证,你会被骂死的。。。。你觉得老王说的对,但问题是,如何即不改变原功能代码,又不改变原有调用方式,还能加上认证呢?你酷似不得其解,
老王说:学过匿名函数没有?
你:学过,就是lambda,
老王:那lambda与正常函数的区别是什么?
你:最直接的区别是,正常函数定义时需要写名字,但lambda不需要
老王:没错,那lambda定好后,为了多次调用,可否也给它命个名?
你:可以呀,可以写成plus= lambda x:x+1 类似这样,以后再调用plus就可以了,但这样不就失去了lambda的意义了,明明人家叫匿名函数呀,你起来名字有什么用呢?
老王:我不是要给你讨论它的意义,我想通过这个让你明白一个事实,说这老王在画板上面谢了以下代码:
def plus(n): return n+1 plus2 = lambda x:x+1
老王:上面这种写法是不是代表同样的意思?
你:是的
老王:我给lambda X:X+1 起了个名字叫plus2,是不是相当于def plus2(x)?
你:你还别说,还真是,但老王呀,你想说明什么?
老王:没啥,只想告诉你,给函数赋值变量名就像def func_name 是一样的效果,如下面的plus(n)函数,你调用时可以用plus名,还可以再起个其它名字,如:
calc = plus calc(n)
你明白想传达什么意思了么?
你:不太明白
老王:你之前写的下面这段调用认证的代码
home() login(america) # 需要验证就调用login,把需要验证的功能,当做一个参数传给login # america() # henan() login(henan)
老王:你之所改变了调用方式,是因为用户每次调用时需要执行login(henan),类似的。其实稍一改就可以了呀
home() america=login(america) # 需要验证就调用login,把需要验证的功能,当做一个参数传给login henan=login(henan)
这样,其他人调用henan时,其实相当于调用了login(henan),通过login里的验证后,就会自动调用henan功能
你:我靠,还真是,老王,你牛B,不过,等等,我这样写好之后,那用户调用时,应该是下面这个样子
home() america=login(america) # 需要验证就调用login,把需要验证的功能,当做一个参数传给login henan=login(henan) # 那用户调用时依然写 america()
但问题在于,还不等用户调用,你的america=login(america)就会先自己把america执行了呀。。。,你应该等用户调用的时候,再执行才对呀,不信是给你看。。。
老王:你说的没错,这样搞会出现这个问题?但你想想有没有解决办法呢?
你:你指点思路呀
老王:算了,估计你也想不出来。。。学过嵌套函数没有?
你:yes,然后呢?
老王:想实现一开始你写的america=login(america)不触发你函数的执行,只需要在这个login里面再定义一层函数,第一次调用america=login(america)只调用到外层login,这个login虽然会执行,但不会触发认证了,因为认证的所有代码被封装在login里层的新定义的函数里了,login值返回里层函数的函数名,这样下次再执行america()时,就会调用里层函数啦
你:什么?什么意思?懵逼了
老王:还是给你看代码吧
def login(func): # 把要执行的模块从这里传进来 def inner(): # 再定义一层函数 _username = 'mike' _password = '123' global user_status if user_status == False username = input('请输入用户名:').strip() password = input('请输入密码:').strip() if username == _username and password == _password: print('欢迎登录成功, %s' % username) user_status = True else: print('输入的用户名或密码错误!') if user_status == True: func() # 看这里看这里,只要验证通过了,就调用相应功能 return inner # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数
老王:这是开发中一个常用的玩法,叫语法糖,官方名称:装饰器,其实上面的写法,还可以更简单,可以把下面代码去掉
america = login(america) #你在这里相当于把america这个函数替换了
只在你要装饰的函数上面加上下面代码:
@login def america(): # login() # 执行前加上验证 print('欧美专区'.center(20, '-')) def japan(): print('日韩专区'.center(20, '-')) @login def henan(): # login() # 执行前加上验证 print('河南专区'.center(20, '-'))
效果是一样的,然后你给‘河南专区”板块加上了一个参数,然后出错了
你:老王,这么传个参数就不行了呢?
老王:那必然呀,你调用henan时,其实是相当于调用的login,你的henan第一次调用时nenan=login(henan),login就返回了inner的内存地址,第2次用户自己调用henan(“电影"),实际上相当于调用的时inner,但你的inner定义时病没有设置参数,但你给它传了参数,所以自然报错了呀:
你:但是版块需要传参数呀,不传不行呀。。
老王:稍作改动便可,如果有多个参数,可以用非固定参数:*args,**kwargs...
代码如下:
user_status = False def login(func): # 把要执行的模块从这里传进来 def inner(*args, **kwargs): # 再定义一层函数 _username = 'mike' _password = '123' global user_status if user_status == False: username = input('请输入用户名:').strip() password = input('请输入密码:').strip() if username == _username and password == _password: print('欢迎登录成功, %s' % username) user_status = True else: print('输入的用户名或密码错误!') if user_status == True: func(*args, **kwargs) # 看这里看这里,只要验证通过了,就调用相应功能 return inner # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数 def home(): print('首页'.center(20, '-')) @login def america(): # login() # 执行前加上验证 print('欧美专区'.center(20, '-')) def japan(): print('日韩专区'.center(20, '-')) @login def henan(style): ''' :param style: 喜欢看什么节目,就传进来 :return: ''' # login() # 执行前加上验证 print('湖南专区'.center(20, '-')) home() henan = login(henan) america() henan('快乐大本营')
四、带参数装饰器
新的需求:要允许用户选择用qq\weibo\weixin认证
user_status = False def login(auth_type): # 把要执行的模块从这里传进来 def auth(func): def inner(*args, **kwargs): # 再定义一层函数 if auth_type == 'qq': _username = 'mike' _password = '123' global user_status if user_status == False: username = input('请输入用户名:').strip() password = input('请输入密码:').strip() if username == _username and password == _password: print('欢迎登录成功, %s' % username) user_status = True else: print('输入的用户名或密码错误!') if user_status == True: func(*args, **kwargs) # 看这里看这里,只要验证通过了,就调用相应功能 else: print('只能qq登录') return inner # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数 return auth def home(): print('首页'.center(20, '-')) @login('qq') def america(): # login() # 执行前加上验证 print('欧美专区'.center(20, '-')) def japan(): print('日韩专区'.center(20, '-')) @login('weibo') def henan(style): ''' :param style: 喜欢看什么节目,就传进来 :return: ''' # login() # 执行前加上验证 print('湖南专区'.center(20, '-')) home() henan = login(henan) america() henan('快乐大本营')