函数进阶
名称空间
名称空间,英文名name space
,顾名思义就是存放名字的地方,其实就是存变量的地方。例如若变量x=1
,1
存放于内存中,那么x
存在哪里呢?
名称空间正是存放名字x
与1
绑定关系的地方。
名称空间一共有三种,分别是:
locals
:是函数内的名称空间,包括局部变量和形参globals
:全局变量,函数定义所在模块的名称空间builtins
:内置模块的名称空间
不同变量的作用域不同就是由这个变量所在的命名空间决定的,
作用域即范围
- 全局范围:全局存活,全局有效
- 局部范围:临时存活,局部有效
查看作用域的方法:globals()
, locals()
作用域的查找顺序
# -*- coding: utf-8 -*-
level = 'L0'
n = 11
def func():
level = 'L2'
n = 22
print(locals())
def outer():
n = 33
level = 'L2'
print(locals(), n)
def inner():
level = 'L3'
print(locals(), n) # inner函数内没有定义n,因此会逐层的往上层函数找变量n,即上层函数outer有变量n=33。
inner()
outer()
func()
作用域的查找顺序rule: LEGB
LEGB:locals
-> enclosing function
-> golbals
-> builtins
locals
是函数内的名称空间,包括局部变量和形参enclosing function
外部嵌套函数的名称空间globals
全局变量,函数定义所在模块的名称空间builtins
内置模块的名称空间
闭包
关于闭包,即函数定义和函数表达式位于另一个函数的函数体内(嵌套函数)。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。也就是谁,内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必须访问其外部函数的局部变量、参数以及其他内部函数。这些局部变量、参数和函数声明(最初时)的值是外部函数返回时的值,但也会受到内部函数的影响。
# -*- coding: utf-8 -*-
def outer():
name = 'hello world'
def inner():
print("在inner里打印外层函数的变量", name)
return inner
f = outer()
f()
闭包的意义: 返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域。
装饰器基础
有几个视频网站,网站有以下几个模块:
# -*- coding: utf-8 -*-
def home():
print("---首页---")
def america():
print("---欧美专区---")
def japan():
print("---欧美专区---")
def henan():
print("---欧美专区---")
需求:对欧美和河南专区进行认证登录后才能观看视频。
改写代码尝试一:在每个需要认证的专区代码中增加认证登录方法
# -*- coding: utf-8 -*-
user_status = False # 用户登录了就把这个变量改为True
def login():
_username = 'alex' # 假装这里是DB里存的用户信息
_password = '123' # 假装着里是DB里存的用户信息
global user_status
if not user_status: # if not user_status 等价于 if user_status == False
username = input("user: ")
password = input("password: ")
if username == _username and password == _password:
print("欢迎登录...")
user_status = True
else:
print("用户名或密码错误!")
if user_status:
print("您已登录,请观看视频。")
def home():
print("---首页---")
def henan():
login()
print("---欧美专区---")
def america():
login()
print("---欧美专区---")
def japan():
print("---欧美专区---")
henan()
america()
# user: alex
# password: 123
# 欢迎登录...
# 您已登录,请观看视频。
# ---河南专区---
# 您已登录,请观看视频。
# ---欧美专区---
代码被打回:代码虽然实现了功能,但是需要更改需要加认证的各个模块的代码,这直接违反了软件开发中的一个原则“开放-封闭”原则,简单来说,它规定已经实现的功能代码不允许被修改,但是可以被扩展,即
- 封闭: 已实现的功能代码块不允许被修改
- 扩展: 对现有功能的扩展开放
改写代码尝试二:使用高阶函数,在认证登录的方法中将专区模块的函数名当作参数传递进去。
# -*- coding: utf-8 -*-
user_status = False # 用户登录了就把这个变量改为True
def login(func):
_username = 'alex' # 假装这里是DB里存的用户信息
_password = '123' # 假装着里是DB里存的用户信息
global user_status
if not user_status: # if not user_status 等价于 if user_status == False
username = input("user: ")
password = input("password: ")
if username == _username and password == _password:
print("欢迎登录...")
user_status = True
else:
print("用户名或密码错误!")
if user_status:
func() # 只要验证通过了,就调用相应功能。
def home():
print("---首页---")
def henan():
print("---河南专区---")
def america():
print("---欧美专区---")
def japan():
print("---日本专区---")
login(henan)
login(america)
# user: alex
# password: 123
# 欢迎登录...
# ---河南专区---
# ---欧美专区---
代码又被打回: 虽然实现了功能,但是改变了调用方式。原本通过henan()
、america()
调用,而现在则通过login(henan)
、login(america)
调用。如果像原调用方式的函数模块有几千几万个,那么每个函数模块都要修改代码。这在软件开发中是不行了。
改下代码尝试三:
思路: lambda
函数。
def plus(n):
return n + 1
plus2 = lambda x: x + 1
calc = plus
calc(12)
同样的,对于第二种代码尝试,已经是修改了调用方式,那么稍微改下:
home()
henan = login(henan) # 这里相当于把henan这个函数替换了
america = login(america)
henan() # 用户调用时依然写henan()。这就实现了调用方式不变。
问题在于,还不等用户调用,henan = login(henan)
就会先自己把henan
执行了,而应该等用户调用的时候才执行henan()
才对。
实现方法: 嵌套函数
想实现henan = login(henan)
不触发函数的执行,只需要在login里面再定义一层函数,第一次调用henan = login(henan)
只调用外层login,这个login虽然会执行,但不会触发认证了,因为认证的所有代码被封装在login里层的新定义的函数里了,login只返回里层函数的函数名
,这样下次再执行henan()
时,就会调用里层函数了。
# -*- coding: utf-8 -*-
user_status = False
def login(func): # 把要执行的函数名从这里传进来
def inner(): # 再定义一层里层的函数
_username = 'alex' # 假装这里是DB里存的用户信息
_password = '123' # 假装着里是DB里存的用户信息
global user_status
if not user_status: # if not user_status 等价于 if user_status == False
username = input("user: ")
password = input("password: ")
if username == _username and password == _password:
print("欢迎登录...")
user_status = True
else:
print("用户名或密码错误!")
if user_status:
func() # 只要验证通过了,就调用相应功能。
return inner # 用户调用login时,只会返回inner的内存地址,需要下次调用时加上()才会执行inner函数
def home():
print("---首页---")
def henan():
print("---河南专区---")
def america():
print("---欧美专区---")
def japan():
print("---日本专区---")
henan = login(henan) # 这里相当于把henan这个函数替换了
america = login(america)
henan()
america()
# user: alex
# password: 123
# 欢迎登录...
# ---河南专区---
# ---欧美专区---
henan = login(henan)
可以用Python中的装饰器语法糖来实现,即在要装饰的函数上面加一行代码。如下:
# -*- coding: utf-8 -*-
user_status = False
def login(func): # 把要执行的函数名从这里传进来
def inner(): # 再定义一层里层的函数
_username = 'alex' # 假装这里是DB里存的用户信息
_password = '123' # 假装着里是DB里存的用户信息
global user_status
if not user_status: # if not user_status 等价于 if user_status == False
username = input("user: ")
password = input("password: ")
if username == _username and password == _password:
print("欢迎登录...")
user_status = True
else:
print("用户名或密码错误!")
if user_status:
func() # 只要验证通过了,就调用相应功能。
return inner # 用户调用login时,只会返回inner的内存地址,需要下次调用时加上()才会执行inner函数
def home():
print("---首页---")
@login # 装饰器语法糖
def henan():
print("---河南专区---")
@login # 装饰器语法糖
def america():
print("---欧美专区---")
def japan():
print("---日本专区---")
henan()
america()
# user: alex
# password: 123
# 欢迎登录...
# ---河南专区---
# ---欧美专区---
手贱给你的“河南专区”版块 加了个参数,然后,结果 出错了。。。
调用henan()
时,其实是相当于调用的login
,你的henan
第一次调用时 henan = login(henan)
,login
就返回了inner
的内存地址,第二次用户自己调用henan("3p")
,实际上相当于调用的是inner
, 但是inner
定义时并没有设置参数,但是你给它传了个参数,自然就报错了
因此,带参数的装饰器完成了,完全遵循“封闭-开放“原则,以及调用方式不变。
# -*- coding: utf-8 -*-
user_status = False
def login(func): # 把要执行的函数名从这里传进来
def inner(*args, **kwargs): # 使用非固定参数; 再定义一层里层的函数
_username = 'alex' # 假装这里是DB里存的用户信息
_password = '123' # 假装着里是DB里存的用户信息
global user_status
if not user_status: # if not user_status 等价于 if user_status == False
username = input("user: ")
password = input("password: ")
if username == _username and password == _password:
print("欢迎登录...")
user_status = True
else:
print("用户名或密码错误!")
if user_status:
func(*args, **kwargs) # 使用非固定参数; 只要验证通过了,就调用相应功能。
return inner # 用户调用login时,只会返回inner的内存地址,需要下次调用时加上()才会执行inner函数
def home():
print("---首页---")
@login # 装饰器语法糖
def henan(style, age): # 加上参数style;这里是两个参数
print("---河南专区---", style, age)
@login # 装饰器语法糖
def america(style): # 加上参数style;这里是一个参数
print("---欧美专区---", style)
@login
def japan(): # 这里没有参数
print("---日本专区---")
# 以上三个专区分别是2个参数、1个参数以及没有参数。如果保证装饰器正常运行?
# 要用到非固定参数了:可以不传参数、也可以传一个或者多个参数。
# henan = login(henan) 第一次执行时,返回的是inner的内存地址,但是inner并没有参数,而调用新henan时给了参数。
henan("3p", 23)
america("5p")
japan()
# user: alex
# password: 123
# 欢迎登录...
# ---河南专区--- 3p 23
# ---欧美专区--- 5p
# ---日本专区---
新需求:允许用户选择用qq|weixin|weibo等认证方式
带参数的装饰器
# -*- coding: utf-8 -*-
user_status = False
def login(auth_type): # 把要执行的函数名从这里传进来
def outer(func):
def inner(*args, **kwargs): # 使用非固定参数; 再定义一层里层的函数
if auth_type == 'qq':
_username = 'alex' # 假装这里是DB里存的用户信息
_password = '123' # 假装着里是DB里存的用户信息
global user_status
if not user_status: # if not user_status 等价于 if user_status == False
username = input("user: ")
password = input("password: ")
if username == _username and password == _password:
print("欢迎登录...")
user_status = True
else:
print("用户名或密码错误!")
if user_status:
func(*args, **kwargs) # 使用非固定参数; 只要验证通过了,就调用相应功能。
if auth_type == 'weixin':
print("暂时不支持微信登录。")
return inner # 用户调用login时,只会返回inner的内存地址,需要下次调用时加上()才会执行inner函数
return outer
def home():
print("---首页---")
# 首先执行 login(qq) -> 返回的outer的内存地址。此时是@outer作为装饰器,然后将函数名henan作为参数传给outer。
# 也就是说: 新henann = login('qq')(henan)
@login('qq')
def henan(style, age): # 加上参数style;这里是两个参数
print("---河南专区---", style, age)
@login('weixin') # 装饰器语法糖
def america(style): # 加上参数style;这里是一个参数
print("---欧美专区---", style)
@login
def japan(): # 这里没有参数
print("---日本专区---")
# 以上三个专区分别是2个参数、1个参数以及没有参数。如果保证装饰器正常运行?
# 要用到非固定参数了:可以不传参数、也可以传一个或者多个参数。
# henan = login(henan) 第一次执行时,返回的是inner的内存地址,但是inner并没有参数,而调用新henan时给了参数。
henan("3p", 23)
america("5p")
# japan()
# user: alex
# password: 123
# 欢迎登录...
# ---河南专区--- 3p 23
# 暂时不支持微信登录。