文章目录
一、函数初始
1.1 什么是函数
Python允许我们将常用的代码以固定的格式封装(包装)成一个独立的模块,只要知道这个模块的名字就可以重复使用它,这个模块就叫做函数(Function)。
比如,在程序中定义了一段代码,这段代码用于实现一个特定的功能。问题来了,如果下次需要实现同样的功能,难道要把前面定义的代码复制一次?如果这样做实在太傻了,这意味着每次当程序需要实现该功能时,都要将前面定义的代码复制一次。正确的做法是,将实现特定功能的代码定义成一个函数,每次当程序需要实现该功能时,只要执行(调用)该函数即可。
其实,函数的本质就是一段有特定功能、可以重复使用的代码,这段代码已经被提前编写好了,并且为其起一个“好听”的名字。在后续编写程序过程中,如果需要同样的功能,直接通过起好的名字就可以调用这段代码
在程序中,具备某一功能的‘工具’指的就是函数,‘事先准备工具’的过程即函数的定义,‘拿来就用’即函数的调用
1.2 为何要用函数
解决了两个问题
- 代码冗余,程序的组织结构不清晰,可读性差
- 可维护性,扩展性差
1.3 如何用函数
函数的使用必须遵循**’先定义,后调用**’的原则
二、定义函数
函数的使用必须遵循’先定义,后调用’的原则。函数的定义就相当于事先将函数体代码保存起来,然后将内存地址赋值给函数名,函数名就是对这段代码的引用,这和变量的定义是相似的。没有事先定义函数而直接调用,就相当于在引用一个不存在的’变量名’
定义函数的语法
def 函数名(参数1,参数2,...):
"""文档描述"""
函数体
return 值
- def: 定义函数的关键字
- 函数名:函数名指向函数内存地址,是对函数体代码的引用。函数的命名应该反映出函数的功能
- 括号:括号内定义参数,参数是可有可无的,且无需指定参数的类型
- 冒号:括号后要加冒号,然后在下一行开始缩进编写函数体的代码
- “”“文档描述”"": 描述函数功能,参数介绍等信息的文档,非必要,但是建议加上,从而增强函数的可读性
- 函数体:由语句和表达式组成
- return 值:定义函数的返回值,return是可有可无的
举例说明:函数是工厂,参数相当于原材料,返回值相当于产品
函数是个人,原材料是吃的饭,返回值是拉出来的屎
2.1 定义函数发生的事情
- 申请内存空间保存函数体代码
- 将上述内存地址绑定函数名
- 定义函数不会执行函数体代码,但是会检测函数体语法
2.2 定义函数的形式
定义函数必须有的
def 函数名():
函数体
可以分为三种形式
2.2.1 无参函数
def 函数名():
函数体
2.2.2 有参函数
def 函数名(参数,参数...):
函数体
参数是函数的调用者向函数体传值的媒介,若函数体代码逻辑依赖外部传来的参数时则需要定义为参函数
2.2.3 空函数
def 函数名():
pass
函数体为pass代表什么都不做,称之为空函数。定义空函数通常是有用的,因为在程序设计的开始,往往是先想好程序都需要完成什么功能,然后把所有功能都列举出来用pass充当函数体“占位符”,这将使得程序的体系结构立见,清晰且可读性强。
三、调用函数
函数的使用分为定义阶段与调用阶段,定义函数时只检测语法,不执行函数体代码,函数名加括号即函数调用,只有调用函数时才会执行函数体代码
调用函数发生的事情
- 通过函数名找到函数的内存地址
- 然后加括号就是在触发函数体代码的执行
#定义阶段
def foo():
print('in the foo')
bar()
def bar():
print('in the bar')
#调用阶段
foo()
定义阶段函数foo与bar均无语法错误,而在调用阶段调用foo()时,函数foo与bar都早已经存在于内存中了,所以不会有任何问题
按照在程序出现的形式和位置,可将函数的调用形式分为三种
def add(a,b):
print(a+b)
return a+b
# 1. 语句的形式:只加括号调用函数
add(1,2)
# 2. 表达式形式
# 2.1 赋值表达式
res = add(1,2)
# 2.2 数学表达式
add(1,2)*10
# 3. 函数调用可以当做参数
add(add(1,2),3)
四、函数返回值
若需要将函数体代码执行的结果返回给调用者,则需要用到return。return后无值或直接省略return,则默认返回None,return的返回值无类型限制,且可以将多个返回值放到一个元组内
return是一个函数结束的标志,函数内可以有多个return,但只执行一次函数就结束了,并把return后定义的值作为本次调用的结果返回
return是函数结束的标志,即函数体代码一旦运行到return会立刻终止函数的运行。并且会将return 后的值当做本次运行的结果返回
- 函数体内没有return,或者return后没有任何东西,就是返回一个None
- 返回一个值:return 值
- 返回多个值:用逗号分隔开多个值,会被return自动返回成一个元祖
五、函数的说明
help(函数名)
print(函数名.__doc__)
示例:
def add(a,b):
"""
:param a: 参数a
:param b: 参数b
:return: a+b的值
"""
return a+b
print(help(add))
print(add.__doc__)
六、函数的参数
6.1 形参与实参介绍
函数的参数分为形式参数和实际参数,简称形参和实参:
形参即在定义函数时,括号内声明的参数。形参本质就是一个变量名,用来接收外部传来的值
实参即在调用函数时,括号内传入的值,值可以是常量、变量、表达式或三者的组合
#1:实参是常量
res=my_min(1,2)
#2:实参是变量
a=1
b=2
res=my_min(a,b)
#3:实参是表达式
res=my_min(10*2,10*my_min(3,4))
#4:实参可以是常量、变量、表达式的任意组合
a=2
my_min(1,a,10*my_min(3,4))
形参与实参的关系:
- 在调用阶段,实参(变量值)会绑定给形参(变量名)
- 这种绑定关系只能在函数体内使用
- 实参与形参的绑定关系在函数体调用时生效,函数调用结束后解除绑定关系
6.2 形参与实参的具体使用
6.2.1 位置参数
按照从左到右的顺序依次定义的参数称之为位置参数
位置形参:按照从左到右的顺序直接定义的“变量名”
特点:必须被传值,多一个少一个都不行
位置实参:在函数调用阶段,按照从左到右的顺序依次传入的值
特点:按照顺序与形参一一对应
6.2.2 关键字参数
关键字实参:在函数调用阶段,按照key=value的形式传入的值
特点:指名道姓的给某个形参传值,可以完全不参照顺序
和位置实参混合使用:
- 位置实参必须放在关键字实参前
- 不能为同一个形参重复传值
6.2.3 默认参数
默认形参:在定义阶段,就已经被赋值的形参,称之为默认参数
特点:在定义阶段就已经被赋值,意味着在调用阶段可以不用为其赋值
位置形参与默认形参混用:
-
位置形参必须在默认形参的左边
-
默认形参的值是在函数定义阶段被赋值的,准确的说被赋予的是值的内存地址
m = 2 def func(x, y=m): # y绑定了2的内存地址 print(x, y) m = 3 func(1) # 结果还是 1 2 # 示范2: m = [11, ] def func(x, y=m): print(x, y) m.append(333) func(1) # 结果为:1 [11, 333]
-
虽然默认值可以被指定为任意数据类型,但是不推荐使用可变类型(失去了默认的意义,不可预知结果)
6.2.4 可变长度的参数(*和**的用法)
参数的长度可变指的是在调用函数时,实参的个数可以不固定,而在调用函数时,实参的定义无非是按位置或者按关键字两种形式,这就要求形参提供两种解决方案来分别处理两种形式的可变长度的参数
可变长度的位置实参
*
形参名:用来接收溢出的位置实参,溢出的位置实参会被*
保存成元祖的格式然后赋值给紧跟其后的形参名,
*
后跟的可以使任意名义,但是约定俗成是args
可变长度的关键字实参
**
形参名:用来接收溢出的关键字实参,**
会将溢出的关键字实参保存成字典格式,然后赋值给紧跟其后的形参名,
**
后跟的可以使任意名义,但是约定俗成是kwargs
*,**
可以用在实参中,实参中带*
,先将*
后的值炸开成位置实参
def add(a,b,c):
print(a+b+c)
nums = [1,2,3]
add(*nums)
nums = {'a':1,'b':2,'c':3}
add(**nums)
*和**混合使用:*args必须在**kwargs之前
6.2.5 命名关键字参数(了解)
在定义函数时,*后定义的参数,如下所示,称之为命名关键字参数
def func(a,b,*args,x,y): # 其中,x,y称之为命名关键字参数
pass
func(1,2,x=3,y=4)
命名关键字实参必须按照key=value的形式为其传值
6.2.6 组合使用
所有参数可任意组合使用,但定义顺序必须是:位置参数、默认参数、*args、命名关键字参数、**kwargs
可变参数*args
与关键字参数**kwargs
通常是组合在一起使用的,如果一个函数的形参为*args
与**kwargs
,那么代表该函数可以接收任何形式、任意长度的参数
在该函数内部还可以把接收到的参数传给另外一个函数
def func(x,y,z):
print(x,y,z)
def wrapper(*args,**kwargs):
func(*args,**kwargs)
wrapper(1,z=3,y=2)
#1 2 3
按照上述写法,在为函数wrapper传参时,其实遵循的是函数func的参数规则,调用函数wrapper的过程分析如下:
- 位置实参1被*接收,以元组的形式保存下来,赋值给args,即args=(1,),关键字实参z=3,y=2被**接收,以字典的形式保存下来,赋值给kwargs,即kwargs={‘y’: 2, ‘z’: 3}
- 执行
func(*args,**kwargs)
,即func(*(1,),** {'y': 2, 'z': 3})
,等同于func(1,z=3,y=2)
提示: *args、**kwargs中的args和kwargs被替换成其他名字并无语法错误,但使用args、kwargs是约定俗成的。
6.3 类型提示
从Python3.5 开始支持类型提示(Type Hint)
def index(a:'参数a的类型',b:'参数b的类型')->'返回值的类型':
return a+b
print(index.__annotations__)