函数、模块、类,是组织代码的良好工具。实现某个功能的代码块可以通过函数集合在一起;拥有相似功能的代码块可以通过模块集中在某个目录某个文件里面;封装了某个面向对象的代码可以通过类来打包。
有了这些代码组织工具,大型的软件工程和项目才能够条理清晰地被管理起来。这里就先从函数说起。
11.1 函数的定义
使用def语句可以定义一个函数。一个函数一般会有函数名、函数参数,函数体和函数返回值。
下面举一个简单的例子,一个实现加1的功能的函数,对传递进来的参数实现+1,并进行返回。代码如下
def inc(a): return a+1
其中inc是函数的名字,
a是函数的参数,
函数体是return a+1,
而返回值是 a+1。
使用函数名和参数,就可以调用函数,接收函数的返回值了。演示代码如下:
ainc=inc(4)print(ainc)
其中inc是刚刚定义的函数,
4是函数的调用参数,
ainc是接收函数返回值的变量。
运行上述的代码,返回是
5
定义函数的时候是可以没有返回值的,也就是没有return语句。实际上python还是会默认返回一个None的。
下面的例子是一个没有return的函数。
def inclist(l): for i in range(len(l)): l[i]=l[i]+1
这个函数是对列表中的所有元素都加1,调用的演示代码为:
l=[1,2,3,4]inclist(l)print(l)
运行上述代码返回为
[2, 3, 4, 5]
11.2 函数里的变量的定义域
定义一个函数,就开辟了一个新的命名空间,拥有了自己的符号表。在函数里面对一个变量进行赋值操作,这个变量就会被添加到函数的本地符号表里面。此时就会无法使用函数外面的同名变量。
举个例子,在infunc函数里面对变量v和列表l进行了赋值。演示代码为:
def infunc(): v=4 l=[] print('inside func v='+str(v)) print('inside func l='+str(l))
而在全局代码块中也有相同的变量名v和列表l存在。演示代码为:
v=6l=[3,4]infunc()print('ouside funcv='+str(v))print('ouside funcl='+str(l))
运行一下这个代码,看看infunc()中v和l,以及全局代码块的v和l是否对等。
inside func v=4inside func l=[]ouside func v=6ouside func l=[3, 4]
可以看到函数里的变量只在函数里起作用。全局代码块中的同名变量丝毫不受影响。
如果想要在函数内读写全局代码块的变量,需要对变量进行global声明。方法是在函数里面加入声明的语句global v,l
def infunc(): global v,l v=4 l=[] print('inside func v='+str(v)) print('inside func l='+str(l))
此时再运行,结果为:
inside func v=4inside func l=[]ouside func v=4ouside func l=[]
可以看到全局代码块的变量已经可以在函数里进行读写了。
需要注意的是,如果只是在函数里读取全局代码块的变量,并不进行赋值的话,那么函数里依然是可以访问到全局代码块的变量的。以下的代码中,v和l都是在外部定义的,而函数内只是进行了读取。
v=4l=[2,3]def func2(): print(v) print(l)func2()
这段代码的运行结果是
4[2, 3]
之所以产生这样让人困惑的问题,是因为python在定义变量和查找变量的逻辑上存在分歧。
在定义局部变量的时候,python并不会顺藤摸瓜地往上查找。当一个赋值语句到来,python只查找了本地符号表,如果没有则添加一个新变量。
而在查找变量的时候,则复杂得多,python先在本地符号表中查找,在到外围符号表中查找(如果有的话),然后是全局符号表,最后是内置符号表(buildin)。
这两个过程并不对称,所以才产生了这个问题。如果两个函数都需要共享同名变量,都需要对此同名变量进行读写,那么最好把这个同名变量设置为全局的变量。在两个函数当中均使用global进行声明。
11.3 函数的参数
python的函数在定义和调用的时候都可以使用多样化的参数形式。
函数在定义的时候可以使用形式参数,默认参数,*参数(可变列表参数),**参数(可变字典参数)。
形式参数是在函数调用的时候必选的参数。并且调用的时候,实际参数要和定义的形式参数位置和数目保持一致。比如之前的inc(a)函数,a就是形式参数;调用的时候inc(5)中,5就是实际参数。调用inc的时候,必须保证有且只有一个参数,才不会出错。
默认参数也叫做可选参数,默认参数都是key=value格式的,在调用的时候可以带上也可以不带上。函数在定义的时候会给这些参数一个初试值,如果在调用的时候没有带上这些参数,那么函数会使用内存中的当前值。演示代码如下:
def opfunc(oppara=5): print(oppara)opfunc()opfunc(9)
以上的代码定义了一个默认参数名字为oppara,如果函数调用的时候没有带上参数,那么就会使用当前oppara的值。以上的代码演示了带默认参数和不带默认参数的效果,结果为
59
如果有形式参数,默认参数需要跟在必选的参数后面。
需要注意的是,默认参数的默认值只会在函数定义的时候初始化一次,不会在每次函数调用的时候再进行初始化了。因此对于可变变量来说,会受到前几次调用的影响。先看一段代码
def apl(a,L=[]): L.append(a) print(L)apl(1)apl(2)
以上的代码中,apl有一个默认参数L,在定义函数的时候初始化为一个空列表。在之后的两次调用中,L并不会再次被初始化,因此每一次的结果都会被保留下来了。所以这段代码的运行结果为
[1][1, 2]
而对于不可变的变量,就不会存在这种问题。看下面的演示代码:
def m(a=5):
print("a's location:"+str(id(a)))
a=a+1
print("a's location after add:"+str(id(a)))
print(“a's value ”+str(a))
m()
m()
a同样在函数定义的时候进行初始化,并且进行了赋值操作。在代码中把a的存储地址给打印出来看看。运行的结果为:
可以看到以上的运行结果中a的默认值每次都回到了5。这里并不是因为a每次都重新被初始化了。而是a每次+1,a的地址实际上变成了另外一个。而函数每次都只记得默认参数的初始化地址,每次都从初始化的地址取默认值,所以看起来好像a又被初始化了一次一样。
默认参数的初始化,对于可变变量和不可变变量的表现是不一样的,需要注意。
*参数表示函数接受一个可变数量的参数列表。
def listfunc(*args): filepath='' for arg in args: filepath=filepath+'/'+arg print(filepath)
上述的函数通过遍历参数列表,把不同的目录节点连接起来,生成一个文件路径。调用代码为:
listfunc('documents','python','function')
结果为:
/documents/python/function
**表示函数接受一个参数字典。
def dictfunc(**keywords): for k in keywords: print(k+':'+keywords[k])
以上的代码接受一个参数字典,并且把参数字典的key和value打印出来。它可以通过关键字参数的方式进行调用(关键字调用方式在下面介绍)
dictfunc(zhang='98',lee='88',yu='90')
代码运行的结果为
yu:90lee:88zhang:98
以上的内容都是关于函数定义的。函数调用也有一套逻辑,它和以上函数定义的内容有交叉关系,并不是说一种定义方式只能用对应的调用方式来调用。
函数的调用方式有实参调用,关键字调用,*参数调用,**参数调用
实参调用适用于函数定义中有形式参数,默认参数或者*参数列表的函数。
下面的函数定义包括了形式参数,默认参数和*参数。并且在调用的时候使用了实参调用,所有的参数都进行了展开。
def fmfunc(formal,optional='',*args): print(formal) filepath= optional for arg in args: filepath=filepath+'/'+arg print(filepath) fmfunc('windows','c:','documents','python','function')
以上代码的运行结果为
windowsc:/documents/python/function
这个函数也适合用*调用,*调用的参数是一个列表,调用的时候使用*操作把参数展开。修改函数调用代码为
paralist=['windows','c:','documents','python','function']fmfunc(*paralist)
运行结果和实参调用是一致的。
关键字调用适用于函数定义时有形式参数,默认参数或者**参数字典的函数。
def kwfunc(formal,optional='class one',**keywords): print(formal) print(optional) for k in keywords: print(k+':'+keywords[k]) kwfunc(formal='gradeone',optional='class two',zhang='98',lee='88',yu='90')
在函数定义的时候,形式参数放在前面,默认参数放在后面,而参数字典因为长度不定,放在最后。参数字典里面的key值,不能和形式参数以及默认参数一样。否则会出现紊乱。
以上的代码运行结果为
grade oneclass twoyu:90lee:88zhang:98
适合关键字调用的函数,同样也适合**调用。**调用的调用参数是一个字典,调用的时候使用**操作把字典展开。修改函数调用代码
dictpara={'formal':'gradeone','optional':'class two','zhang':'98','lee':'88','yu':'90'}kwfunc(**dictpara)
运行结果也和关键字调用的一样。
每次很长,浏览器就爱崩溃。赶紧保存发掉好了。
领取专属 10元无门槛券
私享最新 技术干货