python函数高级
Python函数进阶全套学习笔记(函数对象/嵌套/名称空间/闭包/装饰器)
一、函数第一类对象(函数对象)
1.1 核心概念
函数是第一类对象:函数可以像普通变量一样被使用。
Python中所有名字本质都是内存地址绑定关系:
-
变量名:绑定内存地址,调用时解析为变量值
-
函数名:直接绑定函数内存地址,不加括号是引用地址,加括号是执行函数体
1.2 函数对象的四大特性
1、函数名可以被赋值
# 变量赋值
x = 10
y = x
# 函数赋值
def f1():
print("from f1....")
f2 = f1 # f2与f1指向同一个函数内存地址
print(f2)
f2() # 执行函数
2、函数名可以作为函数参数
def f1():
print('from f1...')
# 接收函数对象作为参数
def f3(m):
print(m) # 打印函数内存地址
m() # 执行传入的函数
f3(f1) # 传入函数名,不要加括号
3、函数名可以作为函数返回值
def f1():
print('from f1')
def f3(m):
return m # 返回函数对象
res = f3(f1)
print(res)
res() # 执行返回的函数
4、函数名可以作为容器类型元素
x = 33
def f1():
print("from f1")
# 列表存储函数对象
l = [11,22,x,f1]
l[3]() # 调用列表中的函数
1.3 实战案例:功能映射字典
利用函数对象特性,实现输入编号调用对应功能,替代大量if判断
# 定义功能函数
def login():
print('from login...')
def withdrawal():
print("from withdrawal....")
def transfer():
print("from transfer....")
def recharge():
print("from recharge...")
def xxx():
print("xxx")
# 字典映射:编号 = [函数对象, 功能描述]
func_dic = {
"1": [login,"登录"],
"2": [withdrawal,"提现"],
"3": [transfer,'转账'],
"4": [recharge,"充值"],
"5": [xxx,"xxx功能"],
}
# 循环交互
while True:
# 打印所有功能
for k in func_dic:
print(f"{k} {func_dic[k][1]}")
# 接收用户输入
choice = input("请输入命令编号:").strip()
if choice not in func_dic:
print("必须输入正确的命令编号!")
continue
# 调用对应功能函数
func_dic[choice][0]()
二、函数嵌套(定义嵌套 + 调用嵌套)
2.1 函数嵌套分类
-
函数嵌套定义:在一个函数内部定义另一个函数,默认仅内部可调用
-
函数嵌套调用:调用函数的过程中,再次调用其他函数
2.2 嵌套调用案例
# 求两个数最大值
def max2(x,y):
if x > y:
return x
else:
return y
# 嵌套调用,求四个数最大值
def max4(a,b,c,d):
res1 = max2(a,b)
res2 = max2(res1,c)
res3 = max2(res2,d)
print(res3)
max4(10,100,33,11)
2.3 嵌套定义案例
内层函数默认仅外层函数内部可用,可通过return将内层函数暴露到外部
def f1():
# 嵌套定义内层函数
def f2():
print("f2..............")
f2() # 内部调用
return f2 # 返回内层函数对象
# 外部获取并调用内层函数
res = f1()
res()
2.4 实战案例:圆形计算工具
from math import pi
def circle(radius,mode):
# 嵌套定义:计算周长
def perimeter(radius):
return 2 * pi * radius
# 嵌套定义:计算面积
def area(radius):
return pi * (radius * radius)
# 根据模式调用不同内层函数
if mode == 0:
return perimeter(radius) # 0-周长
elif mode == 1:
return area(radius) # 1-面积
print(circle(10,0)) # 周长
print(circle(10,1)) # 面积
三、名称空间与作用域
3.1 名称空间核心概念
名称空间:专门存放「名字与内存地址绑定关系」的区域,用来管理变量、函数名
3.2 三大名称空间
-
内置名称空间:存放Python内置名字(len、print、input等),解释器启动即创建
-
全局名称空间:存放.py文件顶级代码定义的名字(函数、全局变量),文件运行时创建
-
局部名称空间:存放函数内部定义的名字,函数调用时创建、调用结束销毁
3.3 名称空间加载顺序
启动Python程序:内置名称空间 → 全局名称空间 → 局部名称空间(函数调用时)
3.4 名字查找优先级(LEGB规则)
核心规则:查找优先级在函数定义阶段就固定,与调用位置无关
优先级从高到低:L(局部) → E(外层嵌套) → G(全局) → B(内置)
经典案例验证
# 案例1:层级查找
x = 222
def f1():
x = 111
def f2():
print(x) # 优先找外层f1的x=111
f2()
f1()
# 案例2:定义阶段固定查找规则
len = 12345
def f1():
def f2():
print(len) # 定义时外层无len,调用全局len=12345
f2()
f1()
# 案例3:局部先声明后使用(报错案例)
def f1():
print(len) # 定义阶段检测到局部会定义len,优先局部,未赋值直接调用报错
len = 111
# f1() # 运行报错
3.5 作用域分类
-
全局作用域:内置、全局名称空间,全局存活、全局有效
-
局部作用域:局部名称空间,临时存活、仅函数内部有效
四、global与nonlocal关键字
4.1 global关键字
作用:在局部作用域中修改全局变量
# 基础用法
x = 100
def f1():
global x # 声明使用全局x
x = 200
f1()
print(x) # 输出200
# 可变类型无需global(修改内部数据,不修改绑定地址)
l = [111,222]
def f2():
l.append(333) # 无需声明global
f2()
print(l) # [111,222,333]
# 重新赋值可变类型需要global
def f3():
global l
l = 333
f3()
print(l) # 333
4.2 nonlocal关键字
作用:在内层局部修改外层函数的局部变量,逐层向外查找,顶层无则报错(不查找全局)
def f1():
x = 111
def f2():
nonlocal x # 声明修改外层函数的x
x = 222
f2()
print(x) # 输出222
f1()
五、闭包函数
5.1 闭包函数定义
同时满足两个条件即为闭包函数:
-
函数嵌套定义(内层函数)
-
内层函数引用了外层函数的局部变量
5.2 闭包核心作用
解决:函数无法直接传参时,通过外层函数包裹参数,为内层函数传参,不改变函数作用域
传参两种方式对比
方式一:普通参数传参(简单场景)
def wrapper(x):
print("from wrapper---->", x)
wrapper(111)
wrapper(222)
方式二:闭包传参(函数不支持传参时使用)
def f1(x):
# 闭包函数,引用外层参数x
def wrapper():
print("from wrapper---->", x)
return wrapper
# 提前绑定参数,生成专属函数
wrapper1 = f1(111)
wrapper2 = f1(222)
wrapper1()
wrapper2()
重点:装饰器的底层原理就是闭包函数
六、装饰器核心原理与实战
6.1 装饰器定义
装饰器是基于闭包实现的工具,用于在不修改原函数源代码、不改变原函数调用方式的前提下,为函数拓展新功能
6.2 设计原则:开放封闭原则
-
封闭:不修改已有功能的源代码
-
开放:支持拓展新功能
6.3 装饰器迭代演进
版本1:修改源代码(不推荐,违反原则)
import time
def index():
start = time.time()
time.sleep(3)
print("from index...")
stop = time.time()
print("run time is %s" % (stop - start))
return 200
index()
版本2:调用处加代码(冗余严重)
import time
def index():
time.sleep(3)
print("from index...")
return 200
# 每次调用都要重复写统计代码
start = time.time()
index()
stop = time.time()
print("run time is %s" % (stop - start))
版本3:固定函数装饰(不通用)
import time
def index():
time.sleep(3)
print("from index...")
return 200
# 只能装饰index,无法复用
def wrapper():
start = time.time()
index()
stop = time.time()
print("run time is %s" % (stop - start))
wrapper()
版本4:通用无参装饰器(改变调用方式)
import time
def index():
time.sleep(3)
print("from index...")
return 200
# 通用装饰函数
def wrapper(func):
start = time.time()
func()
stop = time.time()
print("run time is %s" % (stop - start))
wrapper(index) # 调用方式改变,不满足需求
版本5:完整版通用装饰器(闭包实现、不改变调用方式)
import time
def timer(func):
# 兼容任意参数、保留原函数返回值
def wrapper(*args,**kwargs):
# 新增功能:统计耗时
start = time.time()
# 执行原函数
res = func(*args,**kwargs)
# 新增功能结束
stop = time.time()
print("run time is %s" % (stop - start))
# 保留原函数返回值
return res
return wrapper
# 手动替换:不改变调用方式
def index():
time.sleep(3)
print("from index...")
return 200
index = timer(index)
index()
6.4 语法糖 @(简化装饰器调用)
@装饰器名 等价于 函数名 = 装饰器名(函数名)
import time
def timer(func):
def wrapper(*args,**kwargs):
start = time.time()
res = func(*args,**kwargs)
stop = time.time()
print("运行时间:%s" % (stop - start))
return res
return wrapper
# 语法糖简化
@timer
def home(x,y):
time.sleep(2)
print("This is home ....",x, y)
return 200
@timer
def index(y):
time.sleep(3)
print("This is index ....",y)
return 200
index('iii')
6.5 修复装饰器函数属性(wraps)
问题:装饰后的函数会丢失原函数的__name__、__doc__属性
解决:使用functools.wraps保留原函数属性
from functools import wraps
def deco(func):
@wraps(func) # 保留原函数属性
def wrapper(*args, **kwargs):
print("装饰器代码运行啦。。。")
res = func(*args, **kwargs)
return res
return wrapper
@deco
def index():
"""这是xxx功能"""
print("from index")
return 123
help(index) # 正常显示原函数文档
print(index.__name__) # 输出index,而非wrapper
6.6 无参装饰器通用模板
from functools import wraps
def 装饰器名(func):
@wraps(func)
def wrapper(*args, **kwargs):
# 前置新增功能
res = func(*args, **kwargs) # 执行原函数
# 后置新增功能
return res # 返回原函数返回值
return wrapper

浙公网安备 33010602011771号