函数基础-(引子、定义函数、调用函数、函数返回值、函数参数)
函数基础
-
引子
-
定义函数
-
调用函数
-
函数返回值
-
函数参数
一、引子
1.不用函数会引发什么问题?
代码的组织结构不清晰,可读性差;
遇到重复的功能只能重复编写实现代码,代码冗余;
功能需要扩展的时候,需要找出所有实现该功能的地方修改,无法统一管理且维护难度极大;
举个例子:
比如说,len方法突然不能直接用了,然后现在有一个需求,让你计算'hello world'的长度,你怎么计算?
这个需求对于现在的你其实不难,我们一起来写一下。
s1 = "hello world" length = 0 for i in s1: length = length+1 print(length)
好了,功能实现了,非常完美。然后现在又有了一个需求,要计算另外一个字符串的长度,"hello eva".
于是,这个时候你的代码就变成了这样:
s1 = "hello world" length = 0 for i in s1: length = length+1 print(length) s2 = "hello eva" length = 0 for i in s2: length = length+1 print(length)
这样确实可以实现len方法的效果,但是总感觉不是那么完美?为什么呢?
首先,之前只要我们执行len方法就可以直接拿到一个字符串的长度了,现在为了实现相同的功能我们把相同的代码写了好多遍 —— 代码冗余
我们就想啊,要是我们能像使用len一样使用我们这一大段“计算长度”的代码就好了。这种感觉有点像给这段代码起了一个名字,等我们用到的时候直接喊名字就能执行这段代码似的。要是能这样,是不是很完美啊?这时候就需要我们的函数出场啦!
2.函数是什么?
函数的定义:函数就是对功能的封装;
想象生活中的例子,修理工需要事先准备好工具箱里面放好锤子、扳手、钳子等工具,然后遇到锤钉子的时候,拿上锤子用就可以,而无需临时再造一把;
修理工 -------> 程序员
具备某功能的工具 --------> 函数
要想使用工具,需要事先准备好,然后拿来就用且可以重复使用
想要用函数,需要先定义,再使用;.
那么我们如何用函数方式解决上面问题呢?
现在就教大家一个既能,让你们把代码装起来。
def mylen(): s1 = "hello world" length = 0 for i in s1: length = length+1 print(length)
这样我们就定义了一个函数,怎么使用呢,只需要mylen()就可以实现函数调用。
3.函数分类
a.内置函数
为了方便我们的开发,针对一些简单的功能,python解释器已经为我们定义好了的函数即内置函数,对于内置函数,我们可以拿来就用而无需事先定义,另一篇文章里专门有内置函数的讲解;
b.自定义函数
毕竟是内置函数,所能提供的功能是有限的,这样就需要我们自己根据需求,事先定义好我们自己的函数来实现某功能,以后在遇到应用场景时,调用自定义的函数即可。
二、定义函数
1.如何定义函数,函数的结构体。
## 语法 def 函数名(形参1,形参2,形参3,.): '''注释''' 函数体 return 返回的值 ## 函数名要有意义 ##注意: #1.def关键字开头,空格之后接函数名称和圆括号(),最后还有一个“:”; #2.def是固定的,不能变,必须是连续的def三个字母,不能分开; #3.空格,为了将def关键字和函数名分开,必须空开,也可以使用2个或多个空格,正常人都是一个空格; #4.函数名,函数名只能包含字符串,下划线和数字且不能以数字开头,要想变量一样的去定义; #5.括号,是必须加的,不要问为什么,加上就对了; #6.每一个函数都应该对功能和参数进行相应的说明,应该写在函数下面的第一行,以增强代码的可读性; #7.调用,就是函数名(),一定要记得加上括号,不加括号获取的就是函数的内存地址了。
例如:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def auth(user,password): ''' auth function user:用户名 password:密码 return:认证结果 ''' if user == "zjk" and password == "123": return 1 user = input(user:).strip() password = input(pws:).strip() res = auth(user,password) print(res)
2.函数的使用原则:先定义,再调用;
函数即“变量”,变量必须先定义后饮用,未定义而直接引用函数就相当于在引用一个不存在的变量;用例子来说明:
#测试1. >>> def foo(): ... print("from foo") ... bar() ... >>> foo() from foo Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 3, in foo NameError: name 'bar' is not defined #报错,提示bar这个函数没有定义; #测试2. >>> def bar(): ... print("from bar") ... >>> def foo(): ... print("from foo") ... bar() ... >>> foo() from foo from bar ##正常执行; #测试3 >>> def foo(): ... print("from foo") ... bar() ... >>> def bar(): ... print("from bar") ... >>> foo() from foo from bar #说明在代用函数时,函数是不分前后的,但需要注意的时,函数的使用,必须遵循原则,先定义,后使用;不然的话代码调用函数的时候,函数却还没有加载到内存;所以,我们在使用函数时,一定要明确地区分定义阶段和调用阶段。 #定义阶段 >>> def foo(): ... print("from foo") ... bar() ... >>> def bar(): ... print("from bar") ... >>> foo() #调用阶段 >>> foo() from foo from bar
3.函数在定义阶段都干了啥子事情?
只检测语法,不执行代码;
也就是说,语法错误在函数定义阶段就 会检测出来,而代码的逻辑错误只有在执行的时候才知道;
4.定义函数的三种形式;
无参:应用场景仅仅只是执行一些操作,比如与用户交互,打印;
有参:需要根据外部传来的参数,才能执行相应的逻辑,比如统计长度,求最大值最小值;
空函数:设计代码结构;
例如:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#定义阶段 def tell_tag(tag,n): #有参数 print(tag*n) def tell_msg(): #无参数 print('hello world') #调用阶段 tell_tag('*',12) tell_msg() tell_tag('*',12) ''' ************ hello world ************ ''' #结论: #1、定义时无参,意味着调用时也无需传入参数 #2、定义时有参,意味着调用时则必须传入参数
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def auth(user,password): ''' auth function :param user: 用户名 :param password: 密码 :return: 认证结果 ''' pass def get(filename): ''' :param filename: :return: ''' pass def put(filename): ''' :param filename: :return: ''' def ls(dirname): ''' :param dirname: :return: ''' pass #程序的体系结构立见
三、调用函数
1.调用函数
函数的调用:函数名加括号,func()
a.先找到名字
b.根据名字调用代码
2.函数返回值
return 是一个关键字,这个词翻译过来就是“返回”,所以我们管写在return后面的值叫“返回值",一旦遇到return,结束整个函数;
三种类型,无返回值,返回1个值,返回多个值;
无返回值:函数体中没有return,或者return后没有跟任何值,这时此函数会返回一个None;
返回1个值:return 后跟一个值;
返回多个值:return后跟多个值,多个值之间用逗号分隔,此时return返回的是一个元组类型;
那么,什么时候该有返回值呢?
调用函数,经过一系列的操作,最后要拿到一个明确的结果,则必须要有返回值;
通常有参函数需要有返回值,输入参数,经过计算,得到一个最终的结果;
什么时候不需要有返回值?
调用函数,仅仅只是执行一系列的操作,最后不需要得到什么结果,则无需有返回值;
通常无参函数不需要有返回值;
3.函数调用的三种形式
a.语句形式:foo()
b.表达式形式:3 * len("hello")
c.当中另外一个函数的参数:range(len("hello"))
4.函数结束的方式:
a.遇到return结束整个函数;
b.函数体正常执行完毕,整个函数也会结束;
四、函数的参数
我们已经把函数返回值相关的事情研究清楚了,我们自己已经完成了一个可以返回字符串长度的函数,但是现在这个函数还是不完美,之前我们使用len函数的时候得是length = len("hello world"),这样我可以想计算谁就计算谁的长度。但是现在我们写的这个函数,只能计算一个“hello world”的长度,换一个字符串好像就是不行了。这可怎么办?
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#函数定义 def mylen(s1): """计算s1的长度""" length = 0 for i in s1: length = length+1 return length #函数调用 str_len = mylen("hello world") print('str_len : %s'%str_len) 带参数的函数
我们告诉mylen函数要计算的字符串是谁,这个过程就叫做 传递参数,简称传参,我们调用函数时传递的这个“hello world”和定义函数时的s1就是参数。
1.实参和形参:
我们调用函数时传递的这个“hello world”被称为实际参数,因为这个是实际的要交给函数的内容,简称实参。
定义函数时的s1,只是一个变量的名字,被称为形式参数,因为在定义函数的时候它只是一个形式,表示这里有一个参数,简称形参。
2.传递多个参数:
参数可以传递多个,多个参数之间用逗号分割。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def mymax(x,y): the_max = x if x > y else y return the_max ma = mymax(10,20) print(ma)
也正是因为需要传递多个参数、可以传递多个参数,才会有了后面这一系列参数相关的故事。。。
3.根据实参和形参角度出来的不同参数类型:
1)位置参数:
站在实参角度:
a.按照位置传值: 位置形参:必选参数;位置实参:按照位置给形参传值
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def mymax(x,y): #此时x=10,y=20 the_max = x if x > y else y return the_max ma = mymax(10,20) print(ma) 按照位置传参
b.按照关键字传值: 无需按照位置为形参传值;
注意的问题:关键字实参必须在位置实参右面;对同一个形参不能重复传值;并且关键字传值就相当于变量赋值,所以实参中的关键字名称一定要符合变量命名的定义;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def mymax(x,y): #此时x = 20,y = 10 print(x,y) the_max = x if x > y else y return the_max ma = mymax(y = 10,x = 20) print(ma) 按照关键字传参
c.位置、关键字形式混合使用
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def mymax(x,y): #此时x = 10,y = 20 print(x,y) the_max = x if x > y else y return the_max ma = mymax(10,y = 20) print(ma) 位置、关键字混用传参
正确用法:
问题一:位置参数必须在关键字参数的前面;
问题二:对于一个形参只能赋值一次;
站在形参角度:
位置参数必须传值;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def mymax(x,y): #此时x = 10,y = 20 print(x,y) the_max = x if x > y else y return the_max #调用mymax不传递参数 ma = mymax() print(ma) #结果 TypeError: mymax() missing 2 required positional arguments: 'x' and 'y' 位置参数必须传参
2)默认参数:
形参在定义时就已经为其赋值;可以传值也可以不传值,经常需要变得参数定义成位置形参,变化较小的参数定义成默认参数(形参);
注意的问题:
只在定义时赋值一次;默认参数的定义应该在位置形参右面;默认参数通常应该定义成不可变类型;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def stu_info(name,sex = "male"): """打印学生信息函数,由于班中大部分学生都是男生, 所以设置默认参数sex的默认值为'male' """ print(name,sex) stu_info('alex') stu_info('eva','female') 默认参数
参数陷阱:默认参数是一个可变数据类型;
def defult_param(a,l = []): l.append(a) print(l) defult_param('alex') defult_param('egon')
3)动态参数(可变长参数):
可变长指的是实参值的个数不固定,而实参有按位置和按关键字两种形式定义,针对这两种形式的可变长,形参对应有两种解决方案来完整地存放它们,分别是*args,**kwargs;
按位置传值多余的参数都由args统一接收,保存成一个元组的形式;
*args : 接收的是按照位置传参的值,组织成一个元组;
**kwargs: 接受的是按照关键字传参的值,组织成一个字典;
args必须在kwargs之前;
动态传参的另一种方式:
站在形参的角度上,给变量加上*,就是组合所有传来的值形成一个元组;
站在实参的角度上,给一个序列加上*,就是将这个序列按照顺序打散,形成一个个参数传递进去;
-------------- 给形参加* ------------------------------------------ >>> def func(*args): ... print(args) ... >>> func(1,2,3,4,5) (1, 2, 3, 4, 5) >>> def func1(**kwargs): ... print(kwargs) ... >>> func1(a="A",b="b",c="C") {'a': 'A', 'b': 'b', 'c': 'C'} -----------------------给实参加*------------------------------------ >>> def func(a,b,c,d): ... print(a,b,c,d) ... >>> func(*[1,2,3,4]) 1 2 3 4 >>> func(**{"a":"A","b":"B","c":"C","d":"D"}) A B C D
4.关于函数的默认参数隐藏的巨坑
我们先来看一个例子:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#定义一个函数并调用这个函数 def qqxing(l = []): l.append("zjk") print(l) qqxing() qqxing() qqxing() qqxing() -------------打印结果------------------------------ ['zjk'] ['zjk', 'zjk'] ['zjk', 'zjk', 'zjk'] ['zjk', 'zjk', 'zjk', 'zjk'] #我们让第二次调用时添加一个参数[]; def qqxing(l = []): l.append("zjk") print(l) qqxing() qqxing([]) qqxing() qqxing() -------------打印结果------------------------------- ['zjk'] ['zjk'] ['zjk', 'zjk'] ['zjk', 'zjk', 'zjk'] ##那么请问,不加参数调用时和加参数调用时,每次的列表的id是否一致? ##我们再进行一次加参数和不加参数的打印,这次我们带上id def qqxing(l = []): l.append("zjk") print(id(l)) qqxing() qqxing() qqxing() qqxing() ----------------打印结果-------------------------------- 38680776 38680776 38680776 38680776 ##------------加参数[]----------------------------------------- 38811848 36969224 38811848 38811848 ##这次大家看出结果了吧! ##说明:如果默认参数的值是一个可变数据类型,那么每一次调用函数的时候,如果不传值就公用这个数据类型的资源。
5.关于形参中各参数的顺序:
对于形参:
位置参数,*args,默认参数,**kwargs
对于实参:
位置参数,关键字参数
参数总结图: