一、函数的基本概念
'''原则:先定义后调用'''
1.函数:盛放功能的容器,让代码更加条理清晰,方便使用
2.定义函数
def func() :
""" # type hinting 类型提示
:param x: # 参数的类型
:param y:
:return: # 返回值类型
"""
函数体,即代码块
# 提示信息书写方式
# def func(x:int ,y:'传入字符串') -> '返回值是整型'
# int、'传入字符串'、'返回值是整型',非硬性限制只用来提示
# 推荐:
def func(x:int ,y:str) -> int
3.调用
# 两步
foo()
foo:函数的内存地址
():运行函数体
print(foo)
结果为内存地址<function foo at 0x0000023046EA5F70>
# 例子
def foo(x,y):
print(x,y)
res = foo(1, 2)
print(res) # 结果为None,因为函数没有返回值
res() # res不是函数,未绑定内存地址
4.定义函数的三种格式
# 无参函数
def foo():
pass
# 有参函数
def bar(x,y):
pass
# 空函数:无函数体代码块,用来占位的
def spam():
pass
...
5.调用函数的三种形式
def foo():
return 666
res = foo()*10 # 表达式,加入运算符
foo() # 直接调用,执行命令
print(foo()) # 当作参数传给另外的函数,print()也是函数
6.函数的返回值
def foo():
return 123,'x',[1,2,3] # 返回值是一个元组
a,b,c = foo() # 解压赋值
print(a,b,c)
# return 可以没有,没有或者return 或 return None,返回值都为None
# 函数内可以有多个return,只要执行一次,整个函数结束,将后面的值当作本次调用的值返回
# 好比break,可以结束函数
二、函数的参数
# 基本概念
形参:函数定义阶段,括号内指定的参数(变量名)
实参:函数调用阶段,括号内传入的值(变量值)
两者的关系:在函数调用时,会将实参值绑定给形参名;函数调用完毕后则解除绑定
1.位置形参
定义:在函数定义阶段,按照从左到右的顺序依次定义的形参
特点:必须被传值,多一个不行,少一个不行
def foo(x, y, z):
print(x, y, z)
foo(3, 2, 1) # 3 2 1 xyz一一对应
foo(3, 2, 1, 0) # 报错,多了不行
foo(3, 2) # 报错,少了不行
2.位置实参
定义:在函数调用阶段,按照从左到右的顺序依次传入的值
特点:按照顺序依次为形参赋值,遵循形参的规则
def foo(x, y, z):
print(x, y, z)
foo(1, 2, 3) # 1 2 3
foo(3, 2, 1) # 3 2 1
3.关键字实参
定义:在函数调用阶段,按照key=value的格式来传值
特点:可以打乱顺序,但仍能为指定的形参赋值
def foo(x, y, z):
print(x, y, z)
foo(x=1, y=2, z=3) # 1 2 3
foo(y=2, x=1, z=3) # 1 2 3
# 注意:位置实参与关键字实参可以混用,但是遵循两点:
1)位置实参必须跟在关键字实参前面
2)不能为同一个形参重复赋值
def foo(x, y, z):
print(x, y, z)
foo(1, y=2, z=3)
foo(y=2, 1, z=3) # 报错 1)positional argument follows keyword argument
foo(1, 2, y=4) # 报错 2)foo() got multiple values for argument 'y'
4.默认形参
定义:在函数定义阶段,就已经为形参赋值
特点:定义阶段就已经有值了,意味着在调用阶段可以不用为其传值,若为其传值则以传的值为主
应用:输出大部分参数的值相同时,可以用到默认参数,例如统计性别
def foo(x, y=222):
print(x, y)
foo(111,) # 111 222
foo(111, 333) # 111 333
# 例子
def register(name, age, gender='male')
print(name, age, gender)
register("richer", 20, ) # richer 20 male
register("tom", 19, )
register("jack", 39, )
register("lili", 19, 'female') # lili 19 female
# 注意:位置形参和默认形参可以混用,但要遵循三点:
1)位置形参必须跟在默认形参的前面
2)默认形参的值推荐指向不可变类型,避免后续代码影响函数
3)默认形参的值只在定义阶段被赋值一次,不是每次调用函数都执行默认的参数代码,但函数体代码每次调都会执行
# 例1
m = 100 # m -> 100 的内存地址
def foo(x, y=m): # y -> 100 的内存地址
print(x, y)
m = 200 # m -> 200 的内存地址,解除100的绑定
foo(1) # 1 100 m不影响y,100是不可变类型
# 例2
m = [11, 22, 33] # m -> [ ] 的内存地址
def foo(x, y=m): # y -> [ ] 的内存地址
print(x, y)
m.append(44) # m -> [ ] 的内存地址,内存地址不变
foo(1) # 1 [11, 22, 33, 44] m影响y,[ ]是可变类型
# 例3
def foo(name, hobby, hobbies=[]): # 默认形参的值只在定义阶段被赋值一次
hobbies.append(hobby)
# print(f"{name}喜欢的语言:{hobbies}")
print("%s的爱好%s" % (name, hobbies))
foo('lili', 'read') # lili的爱好['read']
# hobbies=[],赋值的是可变类型,第二次调用,相当于只运行hobbies.append(hobby)
foo('caca', 'play') # caca的爱好['read', 'play']
foo('mimi', 'run') # mimi的爱好['read', 'play', 'run']
# 例3问题解决方案:大前提让hobbies用之前都变为空列表;让hobbies变为不可变类型
# def foo(name, hobby, hobbies=666): # hobbies变为不可变类型
def foo(name, hobby, hobbies=None): # 优化
# if hobbies == 666 # hobbies用之前都变为空列表
if hobbies is None # 优化
hobbies = [] # 没有给hobbies值,则默认None的时候,清空列表
hobbies.append(hobby)
# print(f"{name}喜欢的语言:{hobbies}")
print("%s的爱好%s" % (name, hobbies))
foo('lili', 'read') # lili的爱好['read'] 没有给hobbies值
foo('caca', 'play') # caca的爱好['play']
foo('mimi', 'run') # mimi的爱好['run']
5.可变长参数:* 与 **
1)在形参中带*:* 会将溢出的位置实参值汇总成元组,然后赋值给紧跟其后的形参名
def foo(x,*y): # y = (2,3,4,5,6) 位置形参在前
print(x, y)
foo(1) # 1 ()
foo(1,2,3,4,5,6) # 1 (2, 3, 4, 5, 6)
2)在形参中带**:** 会将溢出的关键字实参值汇总成字典,然后赋值给紧随其后的形参名
def foo(x,**y): # y = {'a':11, 'b':22, 'c':33} 位置形参在前
print(x, y)
foo(1) # 1 {}
foo(1, a=11, b=22, c=33) # 1 {'a':11, 'b':22, 'c':33}
3)在实参中带*:* 会把实参中的可被for循环遍历的数据类型打散,分别对应传值给形参名
def foo(x, y, z):
print(x, y, z)
foo(*[11, 22, 33]) # 11 22 33 相当于foo(11, 22, 33)
foo(*'hello') # 报错 相当于foo("h","e","l","l","o") 打散后传值遵循形参规则,多了不行,少了不行
4)在实参中带**:** 会把实参中的字典打散,分别对应传值给形参名
def foo(x, y, y): # y = {'a':11, 'b':22, 'c':33} 位置形参在前
print(x, y, y)
foo(*{'a':11, 'b':22, 'c':33}) # a b c 相当于 foo('a', 'b', 'c')
foo(**{'a':11, 'b':22, 'c':33}) # 报错 相当于 foo(c=3, a=1, b=2) 字典无序
foo(**{'x':11, 'y':22, 'z':33}) # 11 22 33 相当于 foo(y=2, z=3, x=1)
5)* 与 ** 混合使用
作用:a.函数可以接收任何形式、任意长度的参数
b.函数内部可以把接收到的参数传递给另一个函数
# 例子
def index(x, y, z):
print(x, y, z)
def bar(*args, **kwargs) # args = (1, 2, 3), kwargs = {'x':11, 'y':22, 'z'=33}
index(*args, **kwargs) # index(*(1, 2, 3), **{'x':11, 'y':22, 'z'=33})
# index(1, 2, 3, x=11, y=22, z=33)
bar(1, 2, 3, x=11, y=22, z=33) # 报错 要遵循形参规则
bar(1, c=3,b=2) # 1 2 3 相当于index(1, c=3, b=2)
6)命名关键字参数 # 了解
位置形参、默认形参、*、**的组合,位置参数在前
def foo(x,y,*args,m=666,n):
print(x,y,m,n)
print()
foo(1,2,m=777,n=888)
foo(1,2,n=999)
def foo(x,y=111,*args,m,n,**kwargs):
print(x,y,args,m,n,kwargs)
foo(1,2,3,4,m=777,n=888)
实参传值形式为key = value,其他形式多余的会被args,keargs打包带走