第 5 章 函数
5.1 语法
- 函数必须先定义再调用
def 函数名 (参数列表) :
函数体
[return]
[return]代表 return可以不加,默认返回None
5.2 函数的参数
5.2.1 形参和实参
- 在定义函数时,形参没有分配存储空间,也没有值,相当于一个占位符;
在调用函数时, 会在栈区中给函数分配存储空间, 然后给形参/局部变量分配存储空间,传递的是实际的数据
- 当函数执行结束,函数所占的栈空间会被释放,函数的形参/局部变量也会被释放
程序的执行逻辑:

在堆内存首先创建 模版对象 –就是py文件
创建函数对象
函数对象 指向 模版对象 (存储地址)
程序调用, 在 栈内存 传递 实参 的 地址,进行执行
执行完毕后,栈内存空间释放
5.2.2 函数的参数传递
1)指针
a=10
a=“helloworld”
以上代码中,10是数字类型," helloworld " 是 String 类型,而变量 a 是没有类型,她仅仅是一个对象的引用(一个指针),可以是指向数字类型对象,也可以是指向 String 类型对象。
2)引用
在 Python 中,变量和数据是分开存储的,数据保存在内存中的一个位置,变量中保存着数据在内存中的地址,变量中记录数据的地址,就叫做引用。
3)可变(mutable)与不可变(immutable)类型对象
在Python常见的类型中,数字类型、string、tuple是不可更改的对象,而list、set、dict等则是可以修改的对象。
- 不可变类型
变量赋值 a=500 后再赋值 a=1000,这里实际是新生成一个 int 值对象 1000,再让 a 指向它,而500被丢弃,不是改变a的值,相当于新生成了a。类似c++的值传递,
def change_int(a):
print(f"2在函数体中修改前值:{a},地址{id(a)}")
a = 100
print(f"3在函数体中修改后值:{a},地址{id(a)}")
b = 10
print(f"1在函数外调用函数前值:{b},地址{id(b)}")
change_int(b)
print(f"4在函数体外调用函数后值:{b},地址{id(b)}")
值改变,地址改变
1在函数外调用函数前值:10,地址140707877808856
2在函数体中修改前值:10,地址140707877808856
3在函数体中修改后值:100,地址140707877811736
4在函数体外调用函数后值:10,地址140707877808856
- 可变类型
变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。类似c++的引用传递,如列表,字典。如 fun(la),则是将 la 真正的传过去,修改后fun外部的la也会受影响
def change_int(list1):
print(f"2在函数体中修改前值:{list1},地址{id(list1)}")
list1[2] = 666
print(f"3在函数体中修改后值:{list1},地址{id(list1)}")
list3 = [10,20,30]
print(f"1在函数外调用函数前值:{list3},地址{id(list3)}")
change_int(list3)
print(f"4在函数体外调用函数后值:{list3},地址{id(list3)}")
集体值发生变化,地址不变
1在函数外调用函数前值:[10, 20, 30],地址1873351414144
2在函数体中修改前值:[10, 20, 30],地址1873351414144
3在函数体中修改后值:[10, 20, 666],地址1873351414144
4在函数体外调用函数后值:[10, 20, 666],地址1873351414144

执行逻辑:
生成模版对象,.py, 生成模块命名空间 __dict__, 里边存储各种对象的地址
生成函数对象 0x1234 和 0x2345, 模版对象中存储/指向 函数对象的地址
程序执行的时候,在左侧栈内存中运行,调用函数,先执行赋值操作,list1 = list3 = 0x2345,让list1指向了0x2345地址
list1[2] = 666,会创建一个666对象(小整数池中大小为 [-5~256]),然后直接在0x2345中的 第三个值0x3030处修改它的地址为666
5.2.3 var1*=2和var1=var1*2的区别
-
var1 *= 2使用原地址。
-
var1 = var1 * 2开辟了新的空间。
-
var1 = var1 * 2:
def change_int(list1):
print(f"2在函数体中修改前值:{list1},地址{id(list1)}")
list1 = list1 *2
print(f"3在函数体中修改后值:{list1},地址{id(list1)}")
list3 = [10,20,30]
print(f"1在函数外调用函数前值:{list3},地址{id(list3)}")
change_int(list3)
print(f"4在函数体外调用函数后值:{list3},地址{id(list3)}")
地址发生改变:
1在函数外调用函数前值:[10, 20, 30],地址2302819176832
2在函数体中修改前值:[10, 20, 30],地址2302819176832
3在函数体中修改后值:[10, 20, 30, 10, 20, 30],地址2302821366784
4在函数体外调用函数后值:[10, 20, 30],地址2302819176832
- var1*=2:
def change_int(list1):
print(f"2在函数体中修改前值:{list1},地址{id(list1)}")
list1 *= 2
print(f"3在函数体中修改后值:{list1},地址{id(list1)}")
list3 = [10,20,30]
print(f"1在函数外调用函数前值:{list3},地址{id(list3)}")
change_int(list3)
print(f"4在函数体外调用函数后值:{list3},地址{id(list3)}")
地址不变:
1在函数外调用函数前值:[10, 20, 30],地址2274329563520
2在函数体中修改前值:[10, 20, 30],地址2274329563520
3在函数体中修改后值:[10, 20, 30, 10, 20, 30],地址2274329563520
4在函数体外调用函数后值:[10, 20, 30, 10, 20, 30],地址2274329563520
5.2.4 函数的参数形式
1)必须参数
基于位置把每个相应位置的实参和形参相关联,调用时的数量必须和声明时的一样。
def func(a, b, c):
print(a, b, c)
func(1, 2, 3) # 1 2 3
2)关键字参数
函数调用使用关键字参数来确定每个变量传入的参数值,使用关键字参数允许函数调用时参数的顺序与声明时不一致。
def printInfo(name,age) :
print("姓名:",name)
print("年龄:",age)
# Python解释器可以通过age和name这样的关键字去和形参进行匹配
printInfo(age = 18,name = "zhangsan")
3)默认值参数
非默认参数必须放在默认参数之前
def printInfo(name,age = 20) :
print("姓名:",name)
print("年龄:",age)
printInfo("zhangsan")
printInfo("lisi",30)
printInfo(age = 40,name = "wangwu")
4)不定长参数
- 形式一: *参数名, 这个可以接受多个参数,将多个参数放到元组中处理
def print_info(num,*var,age):
print(num)
print(var)
print_info(10,20,30,age=40)
20和30都传给var,如果要给age传值,此时必须用关键词传参
- 形式二: **参数名,以字典的形式处理,后面不能再处理普通函数了
def print_info(num,**var):
print(num)
print(var)
print_info(10,a=20 ,b=30)
10
{'a': 20, 'b': 30}
4)解包传参
形式一: 解包非字典,用 *
def print_info(a,b,c):
print(a,b,c)
tup1 = (10,20,30)
print_info(*tup1)
形式二: 解包字典
def print_info(a,b,c):
print(a,b,c)
dic = {"a":1,"b":2,"c":3}
print_info(**dic)
#print_info(a=1,b=2,c=3)
print_info(*dic)
1个*代表解包key,2个代表解包value,此外,解包相当于注释操作,因此形参和实参必须一致,如果为aa会报错
5.2.5 强制使用位置参数或关键字参数
/ 前的参数必须使用位置传参,* 后的参数必须用关键字传参。
def print_info(a,b,c,/,c,d,e,*,f,g):
print(a,b,c,d,e,f,g)
print_info(10,20,30,40,50,f = 60,g=70)
5.2.6 防止函数修改列表/深拷贝
5.2.6.1浅拷贝:
正常情况下:
def change_list(list1):
print(f"2在函数体中修改前列表的值{list1},地址{id(list1)}")
list1[2] = 666
print(f"3在函数体中修改前列表的值{list1},地址{id(list1)}")
list2 = [10,20,30]
print(f"1在函数体外修改前列表的值{list2},地址{id(list2)}")
change_list(list2)
print(f"4在函数体外修改前列表的值{list2},地址{id(list2)}")
1在函数体外修改前列表的值[10, 20, 30],地址2306436239744
2在函数体中修改前列表的值[10, 20, 30],地址2306436239744
3在函数体中修改前列表的值[10, 20, 666],地址2306436239744
4在函数体外修改前列表的值[10, 20, 666],地址2306436239744
修改了外部的列表的值,为了防止修改,我们采用了copy方法:
def change_list(list1):
print(f"2在函数体中修改前列表的值{list1},地址{id(list1)}")
list1[2] = 666
print(f"3在函数体中修改前列表的值{list1},地址{id(list1)}")
list2 = [10,20,30]
print(f"1在函数体外修改前列表的值{list2},地址{id(list2)}")
change_list(list2.copy())
print(f"4在函数体外修改前列表的值{list2},地址{id(list2)}")
1在函数体外修改前列表的值[10, 20, 30],地址2747885228416
2在函数体中修改前列表的值[10, 20, 30],地址2747887418368
3在函数体中修改前列表的值[10, 20, 666],地址2747887418368
4在函数体外修改前列表的值[10, 20, 30],地址2747885228416
但如果是这样:
def change_list(list1):
print(f"2在函数体中修改前列表的值{list1},地址{id(list1)}")
list1[3].append(400)
print(f"3在函数体中修改前列表的值{list1},地址{id(list1)}")
list2 = [10,20,30,[1,2,3]]
print(f"1在函数体外修改前列表的值{list2},地址{id(list2)}")
change_list(list2.copy())
print(f"4在函数体外修改前列表的值{list2},地址{id(list2)}")
1在函数体外修改前列表的值[10, 20, 30, [1, 2, 3]],地址2417730721984
2在函数体中修改前列表的值[10, 20, 30, [1, 2, 3]],地址2417732909120
3在函数体中修改前列表的值[10, 20, 30, [1, 2, 3, 400]],地址2417732909120
4在函数体外修改前列表的值[10, 20, 30, [1, 2, 3, 400]],地址2417730721984
值依旧放生了变化,因为它的执行逻辑是:

-
调用的时候,copy了一个新的对象,叫0x5678
-
在执行的时候,追加了一个新的参数,修改的是0x3456,而原本list2指向的0x2345也指向0x3456,就会导致list2也发生变化。
5.2.6.2 深拷贝
这样就不会影响函数体外的数据
import copy
def change_list(list1):
print(f"2在函数体中修改前列表的值{list1},地址{id(list1)}")
list1[3].append(400)
print(f"3在函数体中修改前列表的值{list1},地址{id(list1)}")
list2 = [10,20,30,[1,2,3]]
print(f"1在函数体外修改前列表的值{list2},地址{id(list2)}")
change_list(copy.deepcopy(list2))
print(f"4在函数体外修改前列表的值{list2},地址{id(list2)}")

5.3 函数的说明文档
将注释写在函数体内第一行代表函数的说明文档, 将鼠标放在函数身上可以查看该函数的作用,也可以通过help函数来查看(显示的内容就是第一行注释)
def adult(age):
*"""该函数判断是否成年"""*
* *if age >= 18:
print("成年")
else:
print("未成年")
help(adult)
5.4 return
-
不带表达式的 return 语句,返回 None。
-
函数中如果没有 **return **语句,在函数运行结束后也会返回 None。
-
return 语句可以返回多个值,多个值会放在一个元组中。
def f(a, b, c):
return a, b, c, [a, b, c]
print(f(1, 2, 3)) # (1, 2, 3, [1, 2, 3])
5.5 变量的作用域
在认识变量作用域前,需要先知道闭包
5.5.1 闭包
def outer():
num = 10
def inner():
print(num)
return inner
*# outer()()*
*#===== 相当于下边 ======*
*#获取内函数对象*
inn = outer()
*#调用内函数*
inn()
会打印出来结果10,但正常来讲,会报错,为什么没有报错,这里用到了闭包

正常的执行逻辑如图:
-
创建模块对象 .py
-
执行到inn = outer()创建函数对象 -outer,模块对象的outer执行函数对象的地址0x1234,
-
outer()内部会返回inner,inn = outer()调用结束,会创建一个inner函数对象,在栈内存中执行,堆内存中的inn指向 inner对象
-
inn() 调用, 然后打印print(num),此时应该会报错,因为num实际只在outer函数对象中,但为什么没有报错?
因为闭包延长了外层函数局部变量的生命周期,和内函数绑定,当内函数执行完毕之后,对应的局部变量才会被释放
5.5.2 作用域
分为
-
内建作用域
-
*全局作用域 *
-
嵌套作用域(闭包)
-
局部作用域
aa = int(1.1) *#内建作用域 Builtin*
a = 10 *#全局作用域 Global*
def outer():
a = 20 *#嵌套作用域(闭包) Enclosing*
* *def inner():
a = 30 *#局部作用域 Local*
* *print(a)
return inner
outer()()
查找范围:局部作用域>嵌套作用域(闭包)>全局作用域>内建作用域
在python中,只有类、模块、函数才会引入新的作用域, if/while/for/try不会引入新的作用域
aa = 10
if aa ==10:
num1 = 20
print(num1)
print(num1)
- 在if中定义的是全局作用域
5.5.3 global 关键字
如果在局部作用域中,要对全局变量进行赋值操作,python会默认创建新的局部变量
var1 = 100
def func1():
*# 如果在局部作用域中,要对全局变量进行赋值操作,python会默认创建新的局部变量*
* #var1 +=10 #会报错*
* #如果在局部变量中 要修改全局变量*
* *global var1
var1 = 200
print(f"局部{var1}")
func1()
print(f"全局{var1}")
5.5.4 nonlocal 关键字
同理,如果要在局部中修改嵌套变量,需要使用nonlocal:
def outer():
num1 = 10
def inner():
nonlocal num1
num1 = 20
print(f"局部{num1}")
inner()
print(f"嵌套{num1}")
outer()
5.6 递归
求一个整数n的阶乘!
def get_factorial(n):
if n == 1:
return 1
else:
return n * get_factorial(n – 1)
print(get_factorial(5))

如果不添加if,每次调用,因为没有出栈,每次都会形成新的占据内存,一直到报错
右侧每返回一个给它的上一个,出栈一个
5.7 匿名函数
就是bambda表达式
def my_map(func,list1):
list2 = []
for item in list1:
list2.append(func(item))
return list2
list33 =[10,20,30]
print(my_map(lambda item:item *2,list33))
匿名函数作为内置函数的参数
- sorted()
有三名学生的姓名和年龄,按年龄排序。
student_list = [{"name": "zhang3", "age": 36}, {"name": "li4", "age": 14}, {"name": "wang5", "age": 27}]
print(sorted(student_list, key=lambda x: x["age"]))
- map()
map() 函数对序列中元素逐一处理。
map_result = map(lambda x: x * x, [0, 1, 3, 7, 9])
print(list(map_result)) # [0, 1, 9, 49, 81]
- filter()
filter() 函数对序列中元素过滤。
filter_result = filter(lambda x: x >= 0, [-0, -1, -3, 7, 9])
print(list(filter_result)) # [0, 7, 9]
- reduce()
reduce() 函数对序列中元素进行累积。
from functools import reduce
reduce_result = reduce(lambda x, y: x * y, [1, 2, 3, 4, 5])
print(reduce_result) # 120
5.8 函数的注释
写上注释,name.的时候能快速找到其中的方法
# 普通的自定义函数:
def dog(name, age, species):
return (name, age, species)
# 添加了注释的自定义函数:
def dog(name:str, age:(1, 99), species:'狗狗的品种') -> tuple:
return (name, age, species)
print(dog.__annotations__)
tuple是返回的类型
查看这些注释可以通过自定义函数的特殊属性__annotations__获取,结果会以字典的形式返回
{'name': <class 'str'>, 'age': (1, 99), 'species': '狗狗的品种', 'return': <class 'tuple'>}
网硕互联帮助中心



评论前必须登录!
注册