Python之旅:函数对象、函数嵌套、名称空间与作用域、装饰器

一 函数对象

一 函数是第一类对象,即函数可以当作数据传递

1
2
3
4
##1 可以被引用
##2 可以当作参数传递
##3 返回值可以是函数
##3 可以当作容器类型的元素

二 利用该特性,优雅的取代多分支的if

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def foo():
print('foo')

def bar():
print('bar')

dic={
'foo':foo,
'bar':bar,
}
while True:
choice=input('>>: ').strip()
if choice in dic:
dic[choice]()

二 函数嵌套

一 函数的嵌套调用

1
2
3
4
5
6
7
8
9
def max(x,y):
return x if x > y else y

def max4(a,b,c,d):
res1=max(a,b)
res2=max(res1,c)
res3=max(res2,d)
return res3
print(max4(1,2,3,4))

二 函数的嵌套定义

1
2
3
4
5
6
7
8
9
def f1():
def f2():
def f3():
print('from f3')
f3()
f2()

f1()
f3() #报错,为何?请看下一小节

三 名称空间与作用域

一 什么是名称空间?

1
##名称空间:存放名字的地方,三种名称空间,(之前遗留的问题x=1,1存放于内存中,那名字x存放在哪里呢?名称空间正是存放名字x与1绑定关系的地方)

二 名称空间的加载顺序

1
2
3
4
python test.py
##1、python解释器先启动,因而首先加载的是:内置名称空间
##2、执行test.py文件,然后以文件为基础,加载全局名称空间
##3、在执行文件的过程中如果调用函数,则临时产生局部名称空间

三 名字的查找顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
局部名称空间--->全局名称空间--->内置名称空间

##需要注意的是:在全局无法查看局部的,在局部可以查看全局的,如下示例

## max=1
def f1():
# max=2
def f2():
# max=3
print(max)
f2()
f1()
print(max)

四 作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
##1、作用域即范围
- 全局范围(内置名称空间与全局名称空间属于该范围):全局存活,全局有效
  - 局部范围(局部名称空间属于该范围):临时存活,局部有效
##2、作用域关系是在函数定义阶段就已经固定的,与函数的调用位置无关,如下
x=1
def f1():
def f2():
print(x)
return f2
x=100
def f3(func):
x=2
func()
x=10000
f3(f1())

##3、查看作用域:globals(),locals()


LEGB 代表名字查找顺序: locals -> enclosing function -> globals -> __builtins__
locals 是函数内的名字空间,包括局部变量和形参
enclosing 外部嵌套函数的名字空间(闭包中常见)
globals 全局变量,函数定义所在模块的名字空间
builtins 内置模块的名字空间

五 global与nonlocal关键字

关键字global

Python里只有2种作用域:全局作用域和局部作用域。全局作用域是指当前代码所在模块的作用域,局部作用域是指当前函数或方法所在的作用域。局部作用域里的代码可以读外部作用域(包括全局作用域)里的变量,但不能更改它。如果想更改它,这里就要使用global关键字了

关键字nonlocal

Python 2.x中,闭包只能读外部函数的变量,而不能改写它。为了解决这个问题,Python 3.x引入了nonlocal关键字,在闭包内用nonlocal声明变量,就可以让解释器在外层函数中查找变量名。

注意:关键字nonlocal:是python3.X中出现的,所以在python2.x中无法直接使用.

实例

关键字nonlocal的作用与关键字global类似,使用nonlocal关键字可以在一个嵌套的函数中修改嵌套作用域中的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
## 实例1 global 应用
name = 'pythontab'
def func():
global name
name = 'pythontab.com'
func()
print(name) # pythontab.com

## 实例2 nonlocal error
def foo():
name= 'pythontab.com'
nonlocal name
foo()
## SyntaxError: name 'name' is assigned to before nonlocal declaration

## 实例3 nonlocal 应用
count = 1
def a():
count = 'a函数里面' #如果不事先声明,那么函数b中的nonlocal就会报错
def b():
nonlocal count
print(count) # a函数里面
count = 2
b()
print(count) # 2

if __name__ == '__main__':
a() # 1
print(count)

总结

主要区别有以下两点:

  1. 两者的功能不同。global关键字修饰变量后标识该变量是全局变量,对该变量进行修改就是修改全局变量,而nonlocal关键字修饰变量后标识该变量是上一级函数中的局部变量,如果上一级函数中不存在该局部变量,nonlocal位置会发生错误(最上层的函数使用nonlocal修饰变量必定会报错)。

  2. 两者使用的范围不同。global关键字可以用在任何地方,包括最上层函数中和嵌套函数中,即使之前未定义该变量,global修饰后也可以直接使用,而nonlocal关键字只能用于嵌套函数中,并且外层函数中定义了相应的局部变量,否则会发生错误。

四 闭包函数

一 什么是闭包?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
##内部函数包含对外部作用域而非全局作用域的引用
##提示:之前我们都是通过参数将外部的值传给函数,闭包提供了另外一种思路,包起来喽,包起呦,包起来哇

def counter():
n=0
def incr():
nonlocal n
x=n
n+=1
return x
return incr

c=counter()
print(c()) # 0
print(c()) # 1
print(c()) # 2
print(c.__closure__[0].cell_contents) #查看闭包的元素 # 3

二 闭包的意义与应用

1
2
3
4
5
6
7
8
9
10
11
##闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域
##应用领域:延迟计算(原来我们是传参,现在我们是包起来)
from urllib.request import urlopen

def index(url):
def get():
return urlopen(url).read()
return get

baidu=index('http://www.baidu.com')
print(baidu().decode('utf-8'))

五 装饰器

装饰器就是闭包函数的一种应用场景。

为什么要用装饰器

1
2
3
4
5
6
7
8
9
开放封闭原则:	
软件一旦上线,就应该遵循开放封闭原则,即对修改源代码是封闭的,对功能扩展是开放的。
也就是说我们必须找到一种解决方案:
能够在不修改一个功能源代码以及调用方式的前提下,为其添加新功能
原则如下:
1、不修改源代码
2、不修改调用方式
装饰器目的:
在遵循12原则的基础上扩展新功能

什么是装饰器

装饰他人的器具:本身可以是任意可调用对象,被装饰者也可以是任意可调用对象

强调-装饰器的原则:1、不修改被装饰对象的源代码;2、不修改被装饰对象的调用方式

装饰器的目标:在遵循1和2的前提下,为被装饰对象添加新功能

装饰器的使用

无参装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import time
## 模拟网络延迟,计算home函数运行的时间
def home(name):
time.sleep(3)
print("Welcome {} to moyan blog".format(name))
home('allen')

## 1、修改源代码
def home(name):
startTime=time.time()
time.sleep(3)
print("Welcome {} to moyan blog".format(name))
stopTime=time.time()
print('home time {}'.format(stopTime-startTime))
home('allen')

## 2、不修改源代码,利用率低
def home(name):
time.sleep(3)
print("Welcome {} to moyan blog".format(name))

startTime=time.time()
home('allen')
stopTime = time.time()
print('home time {}'.format(stopTime - startTime))

## 3、不修改源代码,修改了调用方式
def home(name):
time.sleep(3)
print("Welcome {} to moyan blog".format(name))

def wrapper(func):
startTime = time.time()
func('allen')
stopTime = time.time()
print('home time {}'.format(stopTime - startTime))
wrapper(home) # 修改了源代码的调用方式X


## 4、不修改源代码及调用方式,代码高效利用及代码冗余(还记得闭包么?)
def home(name): # 定义一个被装饰的函数
time.sleep(3)
print("Welcome {} to moyan blog".format(name))
## 定义一个外层函数
def outter(func): # 接收的参数是一个函数名 func->home
# func=home
def wrapper(): # 定义了一个内部函数
startTime = time.time() # 新功能
func('allen') # home('allen')
stopTime = time.time() # 新功能
print('home time {}'.format(stopTime - startTime)) # 新功能
return wrapper # 加括号是执行哦

## 用outter函数装饰home函数
## 此时的home是home=wrapper 不再是home(),已被覆盖;也可以用xx==outter(home); xx()
home=outter(home)
## 不改变调用方式
home() # --> 此时函数已经扩展了新功能 # home=wrapper()


## 5、装饰器
import time
## 定义一个外层函数
def outter(func): # 接收的参数是一个函数名 func->home
# func=home
def wrapper(): # 定义了一个内部函数
startTime = time.time() # 新功能
func('allen') # home('allen')
stopTime = time.time() # 新功能
print('home time {}'.format(stopTime - startTime)) # 新功能
return wrapper # 加括号是执行哦

## @ 写在要被装饰函数的头顶
@outter # 使用 home=outter(home) home=wrapper语法装饰的话稍显啰嗦,Python就提供了@语法,让装饰过程更简便
def home(name): # 定义一个被装饰的函数
time.sleep(3)
print("Welcome {} to moyan blog".format(name))

## 不改变调用方式
home() # --> 此时函数已经扩展了新功能 home=wrapper()

被装饰对象无参+返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
## 1、被装饰的对象有返回值
import time
## 定义一个外层函数
def outter(func): # 接收的参数是一个函数名 func->home
# func=home
def wrapper(): # 定义了一个内部函数
startTime = time.time() # 新功能
fee=func('allen') # fee=home('allen'); fee=123
stopTime = time.time() # 新功能
print('home time {}'.format(stopTime - startTime)) # 新功能
return fee
return wrapper # 加括号是执行哦

## @ 写在要被装饰函数的头顶
@outter # 使用 home=outter(home) home=wrapper语法装饰的话稍显啰嗦,Python就提供了@语法,让装饰过程更简便
def home(name): # 定义一个被装饰的函数
time.sleep(3)
print("Welcome {} to moyan blog".format(name))
return 123

## 不改变调用方式
res=home() # --> 此时函数已经扩展了新功能 home=wrapper()
print(res)

被装饰对象有参+返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
## 被装饰对象有参
import time
## 定义一个外层函数
def outter(func): # 接收的参数是一个函数名 func->home
# func=home
def wrapper(name): # 定义了一个内部函数
startTime = time.time() # 新功能
fee=func(name) # fee=home('allen'); fee=123
stopTime = time.time() # 新功能
print('home time {}'.format(stopTime - startTime)) # 新功能
return fee
return wrapper # 加括号是执行哦

## @ 写在要被装饰函数的头顶
@outter # 使用 home=outter(home) home=wrapper语法装饰的话稍显啰嗦,Python就提供了@语法,让装饰过程更简便
def home(name): # 定义一个被装饰的函数
time.sleep(3)
print("Welcome {} to moyan blog".format(name))
return 123

## 不改变调用方式
res=home('allen') # --> 此时函数已经扩展了新功能 home=wrapper()
print(res)

装饰多个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
## 1、当两个对象一个有参一个无参,装饰器我们应该怎么调试
import time
## 定义一个外层函数
def outter(func): # 接收的参数是一个函数名 func->home
# func=home
def wrapper(name): # 定义了一个内部函数
startTime = time.time() # 新功能
fee=func(name) # fee=home('allen'); fee=123
stopTime = time.time() # 新功能
print('home time {}'.format(stopTime - startTime)) # 新功能
return fee
return wrapper # 加括号是执行哦

## @ 写在要被装饰函数的头顶
@outter # 使用 home=outter(home) home=wrapper语法装饰的话稍显啰嗦,Python就提供了@语法,让装饰过程更简便
def home(name): # 定义一个被装饰的函数
time.sleep(3)
print("Welcome {} to moyan blog".format(name))
return 123
@outter
def index():
time.sleep(1)
print('test ok')
## 不改变调用方式
home('allen') # --> 此时函数已经扩展了新功能 home=wrapper('allen')
index() # index=wrapper(); 装饰器需要一个参数,但是我们没给所以报错
## TypeError: wrapper() missing 1 required positional argument: 'name

##2、用*args,**kwargs来接受参数;回顾请看函数基础-函数的参数
import time
## 定义一个外层函数
def outter(func): # 接收的参数是一个函数名 func->home
# func=home
def wrapper(*args,**kwargs): # 定义了一个内部函数
startTime = time.time() # 新功能
fee=func(*args,**kwargs) # fee=home('allen'); fee=123
stopTime = time.time() # 新功能
print('home time {}'.format(stopTime - startTime)) # 新功能
return fee
return wrapper # 加括号是执行哦

## @ 写在要被装饰函数的头顶
@outter # 使用 home=outter(home) home=wrapper语法装饰的话稍显啰嗦,Python就提供了@语法,让装饰过程更简便
def home(name): # 定义一个被装饰的函数
time.sleep(3)
print("Welcome {} to moyan blog".format(name))
return 123
@outter
def index():
time.sleep(1)
print('test ok')
## 不改变调用方式
home('allen') # --> 此时函数已经扩展了新功能 home=wrapper()
index() # index=wrapper();

简单写的认证装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
## eg:一个简单的认证装饰器,仅供参考
name_ok={
'name':None,
# 'login':None,
}

def outer(func):
def inner(*args,**kwargs):
if name_ok['name']:
res = func(name_ok['name'])
return res
name = input('input username: ')
passwd = input('input passwd: ')
if name=='allen' and passwd=='123':
print('登录成功!')
name_ok['name']=name
res = func(*args, **kwargs)
return res
else:
print('name or passwd ERROR')
return inner
@outer
def index():
print('欢迎!!!')
@outer
def home(*args,**kwargs):
print('welcome {} to home'.format(*args,**kwargs))
return 123
index()
home()

叠加多个装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
## 叠加多个装饰器
## 1. 加载顺序(outer函数的调用顺序):自下而上
## 2. 执行顺序(inner函数的执行顺序):自上而下
name_ok={
'name':None,
# 'login':None,
}

def outer(func):
def inner(*args,**kwargs):
if name_ok['name']:
res = func(name_ok['name'])
return res
name = input('input username: ')
passwd = input('input passwd: ')
if name=='allen' and passwd=='123':
print('登录成功!')
name_ok['name']=name
res = func(*args, **kwargs)
return res
else:
print('name or passwd ERROR')
return inner

import time
def timemer(func):
def inner(*args,**kwargs):
startTime = time.time()
res=func(*args,**kwargs)
stopTime = time.time()
print(stopTime-startTime)
return res
return inner

@outer
@timemer # 写在这只统计index运行的时间
def index():
time.sleep(1)
print('欢迎!!!')

@timemer # 写在这统计outer + index运行的时间
@outer
def home(*args,**kwargs):
time.sleep(2)
print('welcome {} to home'.format(*args,**kwargs))
return 123
index()
home()

有参装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
name_ok={
'name':None,
# 'login':None,
}
def auth(engine):
def auth2(func):
def inner(*args,**kwargs):
if engine == 'file':
print('基于file认证')
if name_ok['name']:
res = func(name_ok['name'])
return res
name = input('input username: ')
passwd = input('input passwd: ')
if name=='allen' and passwd=='123':
print('登录成功!')
name_ok['name']=name
res = func(*args, **kwargs)
return res
else:
print('name or passwd ERROR')
elif engine =='mysql':
print('基于mysql认证')
elif engine =='ldap':
print('基于ldap认证')
return inner
return auth2

@auth('mysql') # @auth('mysql') == @auth2 == home=auth2(home) == home=inner
def home():
print('welcome to home')
return 123

home() # home()=inner()

装饰器总结

1
2
3
4
5
6
7
8
## 装饰器模板
def outer(func):
def inner(*args,**kwargs):
# 被装饰对象执行前 执行的功能
res=func(*args,**kwargs)
# 被装饰对象执行后 执行的功能
return res
return inner

装饰器补充:wraps

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from functools import wraps

## @wraps(func)的作用: 不改变使用装饰器原有函数的结构(如__name__, __doc__)
## 不使用wraps可能出现的ERROR: func...endpoint...map...
def deco(func):
@wraps(func) #加在最内层函数正上方
def wrapper(*args,**kwargs):
return func(*args,**kwargs)
return wrapper

@deco
def index():
'''哈哈哈哈'''
print('from index')

print(index.__doc__) # 哈哈哈哈
## 不适用 @wraps(func)
print(index.__doc__) # None