目录
一、函数引入
很多时候,我们需要重复的应用一个功能,比如,我们在设计网页的时候,我们会重复用到“登录”功能,我们就可以把它写成一个函数,把它封装起来,这样方便我们随时调用,这在一定程度上可以增强程序的可读性,让程度看起来更清晰。
二、函数的定义
函数的使用必须遵循先定义,后调用的原则。函数的定义就相当于事先将函数体代码保存起来,然后将内存地址赋值给函数名,函数名就是对这段代码的引用,这和变量的定义是相似的。没有事先定义函数而直接调用,就相当于在引用一个不存在的变量名。
def 函数名(参数1,参数2,...):
"""文档描述"""
函数体
return 值
上面的定义有几点需要说明:
1. def:定义函数的关键字;
2.函数名:函数名指向函数内存地址,是对函数体代码的引用。函数的命名应该反映出函数的功能;
3.括号:括号内定义参数,参数是可有可无的,且无需指定参数的类型;
4.冒号:括号后要加冒号,然后在下一行开始缩进编写函数体的代码;
5.""文档描述""":描述函数功能,参数介绍等信息的文档,非必要,但是建议加上,从而增强函数的可读性;
6.函数体:由语句和表达式组成;
7. return值:定义函数的返回值, return是可有可无的。
#有参函数
def my_min(x,y):
res=x if x < y else y
return res
#无参函数
def interactive():
user=input('user>>: ').strip()
pwd=input('password>>: ').strip()
return (user,pwd)
我们使用有参函数还是无参函数主要看我们的需求
函数体为pass代表什么都不做,称之为空函数。定义空函数通常是有用的,因为在程序设计的开始,往往是先想好程序都需要完成什么功能,然后把所有功能都列举出来用pass充当函数体”占位符”,这将使得程序的体系结构立见,清晰且可读性强。
三、函数的调用
函数的使用分为定义阶段与调用阶段,定义函数时只检测语法,不执行函数体代码,函数名加括号即函数调用,只有调用函数时才会执行函数体代码
#定义阶段
def foo():
print('in the foo')
bar()
def bar():
print('in the bar')
#调用阶段
foo()
若需要将函数体代码执行的结果返回给调用者,则需要用到return, return后无值或直接省略return,则默认返回None, return的返回值无类型限制,且可以将多个返回值放到一个元组内。
return是一个函数结束的标志,函数内可以有多个return,但只执行一次函数就结束了,并把returm后定义的值作为本次调用的结果返回。
>>> def test(x,y,z):
... return x,y,z #等同于return (x,y,z)
...
>>> res=test(1,2,3)
>>> print(res)
(1, 2, 3)
四、形参与实参
函数的参数分为形式参数和实际参数,简称形参和实参:形参即在定义函数时,括号内声明的参数。形参本质就是一个变量名,用来接收外部传来的值。
五、形参与实参的使用
5.1位置参数
1.在定义函数时,按照从左到右的顺序依次定义形参,称为位置形参,凡是按照这种形式定义的形参都必须被传值
2.在调用函数时,按照从左到右的顺序依次定义实参,称为位置实参,凡是按照这种形式定义的实参会按照从左到右的顺序与形参——对应
5.2关键字参数
在调用函数时,实参可以是key=value的形式,称为关键字参数,凡是按照这种形式定义的实参,可以完全不按照从左到右的顺序定义,但仍能为指定的形参赋值,需要注意在调用函数时,实参也可以是按位置或按关键字的混合使用,但必须保证关键字参数在位置参数后面,且不可以对一个形参重复赋值
5.3默认参数
在定义函数时,就已经为形参赋值,这类形参称之为默认参数,当函数有多个参数时,需要将值经常改变的参数定义成位置参数,而将值改变较少的参数定义成默认参数。例如编写一个注册学生信息的函数,如果大多数学生的性别都为男,那完全可以将形参sex定义成默认参数。
定义时就已经为参数sex赋值,意味着调用时可以不对sex赋值,这降低了函数调用的复杂度
需要注意:1.默认参数必须在位置参数之后2.默认参数的值仅在函数定义阶段被赋值一次
>>> x=1
>>> def foo(arg=x):
... print(arg)
...
>>> x=5 #定义阶段arg已被赋值为1,此处的修改与默认参数arg无任何关系
>>> foo()
1
默认参数的值通常应设为不可变类型
>>> def foo(n,arg=[]):
... arg.append(n)
... return arg
...
>>> foo(1)
[1]
>>> foo(2)
[1, 2]
>>> foo(3)
[1, 2, 3]
5.4可变长参数
主要是*和**的使用,参考我的上篇博客,做了详细的说明:*和**的使用
5.5组合使用
综上所述所有参数可任意组合使用,但定义顺序必须是:位置参数、默认参数、*args、**kwargs。可变参数*args与关键字参数*kwargs通常是组合在一起使用的,如果一个函数的形参为args与**kwargs,那么代表该函数可以接收任何形式、任意长度的参数
>>> def wrapper(*args,**kwargs):
... pass
...
六、名称空间
名称空间即存放名字与对象映射/绑定关系的地方。对于x=3, Python会申请内存空间存放对象3,然后将名字x与3的绑定关系存放于名称空间中,delx表示清除该绑定关系。
6.1内建名称空间
伴随python解释器的启动/关闭而产生/回收,因而是第一个被加载的名称空间,用来存放一些内置的名字,比如内建函数名
6.2全局名称空间
伴随python文件的开始执行/执行完毕而产生/回收,是第二个被加载的名称空间,文件执行过程中产生的名字都会存放于该名称空间中,如下名字
6.3局部名称空间
伴随函数的调用/结束而临时产生/回收,函数的形参、函数内定义的名字都会被存放于该名称空间中
名称空间的加载顺序是:内置名称空间->全局名称空间->局部名称空间,而查找一个名字,必须从三个名称空间之一找到,查找顺序为:局部名称空间->局名称空间->内置名称空间。
七、作用域
按照名字作用范围的不同可以将三个名称空间划分为两个区域:
1.全局作用域:位于全局名称空间、内建名称空间中的名字属于全局范围,该范围内的名字全局存活(除非被删除,否则在整个文件执行过程中存活)、全局有效(在任意位置都可以使用) ;
2.局部作用域:位于局部名称空间中的名字属于局部范围。该范围内的名字临时存活(即在函数调用时临时生成,函数调用结束后就释放)、局部有效(只能在函数内使用)
作用域的优先级
在局部作用域查找名字时,起始位置是局部作用域,所以先查找局部名称空间,没有找到,再去全局作用域查找:先查找全局名称空间,没有找到,再查找内置名称空间,最后都没有找到就会抛出异常
x=100 #全局作用域的名字x
def foo():
x=300 #局部作用域的名字x
print(x) #在局部找x
foo()#结果为300
x=100
def foo():
x=300 #在函数调用时产生局部作用域的名字x
foo()
print(x) #在全局找x,结果为100
提示:可以调用内建函数locals()和globals0来分别查看局部作用域和全局作用域的名字,查看的结果都是字典格式。在全局作用域查看到的locals0的结果等于globals()
当实参的值为可变类型时,函数体内对该值的修改将直接反应到原值
num_list=[1,2,3]
def foo(nums):
nums.append(5)
foo(num_list)
print(num_list)
#结果为
[1, 2, 3, 5]
对于嵌套多层的函数,使用nonlocal关键字可以将名字声明为来自外部嵌套函数定义的作用域(非全局)
def f1():
x=2
def f2():
nonlocal x
x=3
f2() #调用f2(),修改f1作用域中名字x的值
print(x) #在f1作用域查看x
f1()
#结果
3