Python之函数进阶
1.1 命名空间
执行以下代码,在函数外面引用函数里面的变量,会发现执行报错了
def func1(): m = 1 print(m) print(m) #这行报的错 报错了: NameError: name 'm' is not defined # m未定义
Python执行代码原理回顾
从Python解释器开始执行代码之后,就在内存中开辟一个空间,每当遇到一个变量的时候,就把变量名和值之间对应的关系记录下来。但是当遇到函数定义的时候,解释器只是象征性的将函数名读入内存,表示已经知道这个函数的存在了。至于函数内部的变量和逻辑,解释器根本不关心。
等执行到函数调用语句的时候,Python解释器会在新开辟一块内存来存储这个函数里面的内容。这个时候,才关注函数里面有哪些变量。而函数中的变量会存储咋新开辟出来的内存中,函数中的变量只能在函数内部使用,并且会随着函数的执行完毕,这块内存中的内容也会被清空。
存放变量名与值的关系的空间,就叫做---命名空间。
代码在运行伊始,创建的存储‘变量名与值的关系’的空间,叫做全局命名空间。
在函数的运行中开辟的临时的空间,叫做局部命名空间。
1.2 命名空间和作用域
命名空间一共分为三种:
全局命名空间
局部命名空间
内置命名空间
* 内置命名空间中存放了python为我们提供的默认的方法,比如input、len、str等等
1.2.1 命名空间的加载顺序
内置命名空间(程序运行前加载) --> 全局命名空间(程序运行中,从上到下加载) -->局部命名空间(程序运行中,调用函数时才加载)
1.2.2 命名空间的取值顺序
在局部调用:局部命名空间 --> 全局命名空间 --> 内置命名空间
在全局调用: 全局命名空间 --> 内置命名空间 (无法调用到局部命名空间)
综上所诉,在寻找变量时,从小范围,一层一层到大范围去寻找
1.2.3 作用域
作用域就是作用范围,按照生效范围可以分为全局作用域和局部作用域。
全局作用域:
包含内置命名空间、全局命名空间,在整个文件的任意位置都能被引用,全局有效。
局部作用域:
局部命名空间,只能在局部范围内生效
1.2.4 global nonlocal
执行以下代码会报错,为什么呢?
count = 1 def func1(): count = count + 1 print(count) func1()
因为count是全局变量,在函数中不能直接对全局变量进行更改
global
name = 'wusir' def func1(): global name name = 'alex' return func1() print(name)
# # global 1,在函数中声明一个全局变量
# # 2,在函数中更改一个全局变量
nonlocal
def func1(): name1 = 'alex' print('+',name1) def inner(): nonlocal name1 name1= 'wusir' print('*',name1) def inner1(): pass inner() print('%',name1) func1()
nonlocal 在子函数中声明、更改一个外层的局部变量
1.3 函数名的操作
1.3.1 函数名的赋值
# 函数名可以相互赋值 def func1(): print(666) f1 = func1 f1()
1.3.2 函数名做参数
# 函数名可以当成其他函数的参数 def func1(): print(666) def func2(argv): argv() print(777) func2(func1)
1.3.3 函数名可以当成容器类数据类型的参数
# 函数名当做数据类型的参数 def func1(): print(666) def func2(): print(777) def func3(): print(888) l1 = [func1, func2, func3] for i in l1: i()
1.3.4 函数名当做函数的返回值
def func1(): print(666) def func2(argv): print(777) return argv ret = func2(func1) ret()
1.4 闭包
闭包的简单定义:内层函数对外层函数非全局变量的引用,叫做闭包。
闭包的好处:python的一个机制,如果python检测到闭包,它的局部作用域是不会随着函数的结束而结束的。
判断一个函数是否闭包的方法:
def wrapper(): name1 = '老男孩' def inner(): print(name1) inner() print(inner.__closure__) # cell wrapper() ###输出结果 老男孩 (<cell at 0x101a950a8: str object at 0x1040e58d0>,) #检测结果是 闭包 # name1 是外层函数wrapper中的变量,是局部变量。 name1 = '老男孩' def wrapper(): def inner(): print(name1) inner() print(inner.__closure__) # None wrapper() ### 输出结果 老男孩 None ##检测结果 不是闭包 # name1 是全局变量。 name = 'alex' def wrapper(argv): def inner(): print(argv) inner() print(inner.__closure__) # cell wrapper(name) ### 输出结果 alex (<cell at 0x101a950a8: str object at 0x101eb9618>,) 上述函数也是闭包 为什么呢? 因为: 函数中argv接收的names的变量,等于是在函数中重新定义了一个变量:args='alex' ,仍旧是一个局部变量,所有仍旧形成了闭包
闭包的用处:
python的一个机制,如果python检测到闭包,它的局部作用域是不会随着函数的结束而结束的。
因为有以上机制,如果你需要执行爬虫,不会每次都新开内存。第一次运行爬虫后,会把爬取的内容计入内存。下次爬取的时候,因为局部作用域没有关闭,所以可以直接读取到内存中的内容。
闭包的另一大用处:装饰器!
1.5 装饰器
1.5.1 认识装饰器
写学习写一个最简单的装饰器
## 简单的装饰器 def timer(f1): def inner(): sta_time = time.time() f1() end_time = time.time() print('代码执行效率为%s' %(end_time - sta_time)) return inner @timer # <==> func1 = timer(func1) def func1(): print("晚上回去吃烧烤") time.sleep(0.3) @timer def func2(): print('晚上喝啤酒') time.sleep(0.5) func1() func2()
装饰器的作用:在不改变原函数及原函数的调用的情况下,为原函数增加一些额外的功能,如打印日志、执行时间、登录认证。
1.5.2 带参数的装饰器
# 带函数的装饰器 def timer(f1): def inner(*args, **kwargs): sta_time = time.time() f1(*args, **kwargs) end_time = time.time() print('代码执行效率为%s' % (end_time - sta_time)) return inner @timer #func3 = timer(func3) def func3(a, b): print(a, b) time.sleep(0.2) func3('鹏', '鸿')