python渐进---函数基本篇

原载于https://mp.weixin.qq.com/s/pdzW_KJW2SzNQdigosxa2g


函数、模块、类,是组织代码的良好工具。实现某个功能的代码块可以通过函数集合在一起;拥有相似功能的代码块可以通过模块集中在某个目录某个文件里面;封装了某个面向对象的代码可以通过类来打包。

有了这些代码组织工具,大型的软件工程和项目才能够条理清晰地被管理起来。这里就先从函数说起。


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=6
l=[3,4]
infunc()
print('ouside funcv='+str(v))
print('ouside funcl='+str(l))

运行一下这个代码,看看infunc()中v和l,以及全局代码块的v和l是否对等。

inside func v=4
inside func l=[]
ouside func v=6
ouside 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=4
inside func l=[]
ouside func v=4
ouside func l=[]

可以看到全局代码块的变量已经可以在函数里进行读写了。


需要注意的是,如果只是在函数里读取全局代码块的变量,并不进行赋值的话,那么函数里依然是可以访问到全局代码块的变量的。以下的代码中,v和l都是在外部定义的,而函数内只是进行了读取。

v=4
l=[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的值。以上的代码演示了带默认参数和不带默认参数的效果,结果为

5
9

如果有形式参数,默认参数需要跟在必选的参数后面。


需要注意的是,默认参数的默认值只会在函数定义的时候初始化一次,不会在每次函数调用的时候再进行初始化了。因此对于可变变量来说,会受到前几次调用的影响。先看一段代码

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's location:4298160680
a's location after add:4298160656
a's value 6
a's location:4298160680
a's location after add:4298160656
a's value 6

可以看到以上的运行结果中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:90
lee:88
zhang: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')

以上代码的运行结果为

windows
c:/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 one
class two
yu:90
lee:88
zhang:98

适合关键字调用的函数,同样也适合**调用。**调用的调用参数是一个字典,调用的时候使用**操作把字典展开。修改函数调用代码

dictpara={'formal':'gradeone','optional':'class two','zhang':'98','lee':'88','yu':'90'}
kwfunc(**dictpara)

运行结果也和关键字调用的一样。



每次很长,浏览器就爱崩溃。赶紧保存发掉好了。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值