Day09:函数Part1
一、为什么要有函数?什么是函数?
(1)避免组织结构不清晰,可读性差
(2)防止代码冗余
(3)没有函数,管理维护的难度极大,扩展性差
函数:具备某一个功能的工具
事先准备工具的过程 —– 函数的定义
拿来就用 —– 函数的调用
所以函数的使用必须遵循 先定义,后调用 的规则
1.1 定义函数的语法:
def 函数名(参数1,参数2,…):
“`
文档描述
“`
代码1
代码2
return 值
1.2 调用函数的语法:函数名加括号就是在调用函数
二、定义函数的三种类型
2.1 有参函数:参数是函数体代码用来接收外部传入的值的,当函数体的代码逻辑需要外部传入值,这时候就需要有参函数。
2.2 无参函数:当函数体的代码逻辑不需要函数的调用者传入值的情况下就是无参的。定义时是无参的,意味着调用时也无需传入参数。
2.3 空函数:函数体为pass
三、调用函数:
函数名 +()
如果是print(函数名+())也是在调用函数
注意:在定义阶段只检测语法,不执行函数体代码。只要在调用函数之前,函数已经全被定义过了就没问题。
虽然在定义foo函数时,foo函数的定义用到了bar函数,而此时bar函数还没有被定义,但是我只是定义了foo函数,还没有调用foo函数,等调用foo函数时,bar函数已经被定义了,即内存里已经有了bar函数的定义,所以是完全没问题的。
调用函数的4种形式:
(1)直接调用:
(2)把函数的返回值赋给一个变量
(3)把函数的返回值当做另外一个函数的参数传入:
四、函数的返回值:
当函数体代码运行完毕后需要有一个返回结果给调用者时就需要函数的返回值了。
返回值的三种形式:
1、没有”return”,则返回None
2、有“return”,“return”后跟一个值,返回该值本身
3、return可以逗号分隔,返回多个值,会返回一个元组给调用者
注意:
1、return返回的值,没有类型限制
2、函数内可以写多个return,但执行一次,函数就立刻结束,并把return后的值作为本次调用的返回值
五、函数的参数:
概览:
形参与实参
参数的具体使用(1)位置参数(2)关键字参数(3)默认参数(4)可变长参数
1、形参与实参是什么:
形参(形式参数):指的是在定义函数时,括号内的参数,其实就相当于变量名一样
实参(实际参数):指的是在调用函数时,括号内传入的值,其实就相当于变量值一样
2、注意:实参值(相当于变量的值)与形参(相当于变量名)的绑定关系只在函数调用时才会生效(或者说绑定),在函数调用结束后就立刻解除绑定。
2.1 位置参数:位置即顺序,位置参数指的就是按照从左到右的顺序依次定义的参数。
分两种:
(1)在定义函数时,按照位置定义的形参,称为位置形参。
例如 def foo (x,y,z):
注意:位置形参的特性是,在调用函数时必须为其传值,多一个不行,少一个也不行。
(2)在调用函数时,按照位置定义的实参,称为位置实参。
注意:位置实参会与位置形参一一对应
def foo (x,y,z):
foo(1,2,3)
2.2 关键字参数(实参):在调用函数时,按照 变量名=值 的形式定义的实参,称为关键字参数。
注意:相当于指名道姓的为形参传值,所以位置可以变
位置实参与关键字实参可以混合使用,但必须遵循形参的规则,且(1)不可以为同一个形参重复传值(就算是重复传的值是相同的那也不行!)。(2)位置实参必须放在关键字实参之前。
2.3 默认参数(形参):
在定义阶段就已经为某个参数赋值,那么该形参就称为默认参数。
应用场景:对于经常需要变化的值,需要将对应的形参定义成位置形参。对于大多数情况值都一样的情况,需要将对应的形参定义成默认形参。
注意:位置形参必须在默认形参前面
注意:默认参数的值在定义阶段只赋值一次,也就是说默认参数的值在定义的时候就固定死了。
但调用的时候可以重新赋值。
注意:默认参数的值应该设为不可变类型
例题,默认参数设置为一个空列表,每次调用时输入名字和爱好,希望打印效果为 ” 名字 [‘兴趣’] ” 这样的样子。
可见,每次单独调用函数没什么问题。
再看,当连续调用函数时候,产生了如下结果:
每次调用会延续上一次调用的结果。这就是默认参数 L 被定义成了可变类型的列表所导致的后果。
其实写函数应该本着这么一个原则:函数就是一个独立的个体,最好每调用一次,这就是一次独立的过程,最好不要产生其他的影响,不要说我这一次调用还会影响下一次的调用。所以默认参数应该设置成不可变类型。
上述例子,可以用如下方法解决:
方法一:
每次调用函数都初始化 L=None,然后L用空列表来赋值。
方法二:
每次调用的时候传一个空列表给参数L
2.4 可变长度的参数 (即可变长参数)
可变长度指的是参数的个数可以不固定,但由于实参有按位置定义的实参和按关键字定义的实参,所以可变长度的实参指的就是按照这两种形式定义的实参个数可以不固定。
例:
按位置定义的实参
foo(1,2)
foo(1,2,3)
按关键字定义的实参:
foo(x=1,y=2)
foo(x=1,y=2,z=3)
foo(x=1,y=2,z=3,a=4,b=5)
这时必须要使形参有两种对应解决方案分别处理以上两种形式的可变长度的实参(即形参要能接收可变长度的实参)
形参中的:
“ * ”:专门针对可变长度的位置实参
“ ** ”: 专门针对可变长度的关键字实参
“ * ” 会将溢出的位置实参全部接收,然后保存成元组的形式赋值给args变量(可变长度的形参约定俗成用args表示,当然如果非要用比如’z’来表示也不是不可以)
如果print的是*args,则元组被打散,结果如下:
“ ** ”会将溢出的关键字实参全部接收然后保存成字典的形式赋值给kwargs变量(约定俗成用kwargs表示),注意“ ** ”在形参中时必须对应关键字实参,不可以对应位置实参。
注意:如果print的是(**kwargs)则会报错!与*args不同!
一旦碰到实参加 “ * ” 的就把该实参打散
元组、字符串、字典(打散了得到的所有的Key)、集合都可以打撒
实参里有“ * ”,形参里不是必须要加“ * ”的,如下例子:
一旦碰到实参里加“ * ”,就把实参的值打散
“ * ” 和 “ ** ” 混合使用:
组合使用函数嵌套:
wrapper函数接收到的参数会原封不动地交给index函数,可见我们虽然只调用了wrapper函数但是间接用了index函数,所以实参的规则要兼顾index函数形参规则。那该怎么调wrapper函数呢?
“ * ” 的应用场景:
求2个数的和(OK可以写),求3个数的和(OK可以写),…,求100个数的和(勉强可以写),求10000个数的和怎么办? 即求和的个数不固定怎么办?
“ ** ” 的应用场景:
用户名密码认证,def auth(name,pwd,**kwargs),这里要用**kwargs因为以后还有可能要增加新的字段,比如传入role,gender,…