python教程(三)·自定义函数

前面介绍了如何使用函数,这一节我们就来学习怎么创建自己的函数!

自定义函数

创建函数非常简单,它使用关键字 “def”,下面的代码创建了一个带有一个参数的函数,并使用不同的参数调用

1
2
3
4
5
def hello(name):
print('hello', name)

hello('feather') # 调用函数,传入参数 'feather'
hello('csdn') # 调用函数,传入参数 'csdn'

运行程序可以得到下面输出:

1
2
hello feather
hello csdn

可以看到,传入的参数被赋值给变量name,然后执行函数内的代码块,对!这也是一种代码块,注意后面的冒号

函数并非一定要有参数,我们完全可以定义一个不带参数的函数,比如这个

1
2
def hello():
print('hello world!')

像获取输入的函数input,返回用户的输入,我们也可以在函数中使用return语句来返回数据,下面是一个计算斐波那契数列第n项的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def fibs(num):
if num == 1:
return 0
elif num == 2:
return 1
elif num > 2:
previous = 0 # 前一项
current = 1 # 当前项
for i in range(num-2):
temp = current
current = previous + current # 当前项等于前两项和
previous = temp
return current
else:
# num < 1
return None

num = int(input('计算第几项:'))
print(fibs(num))

简单说下斐波那契数列,除了第一和第二个数,后面的数等于前两个数相加,这样的一组数可以叫做斐波那契数列。

就算不懂斐波那契数列也没关系,只需要知道当函数内的代码执行到return语句时,马上结束函数,并把return后面的值(如果有)返回给调用者,上面把返回值马上又做为参数去调用print函数输出了。

函数的末尾无论是否有return,都会返回到调用处,其实相当于函数的末尾有一个隐含的没有返回值的return语句(前面提到过,所谓的“没有返回值”,其实返回了一个None,None是“空”的意思,这很好理解)

默认情况下,参数值和参数名称是按函数中定义的顺序匹配起来的。

下面总结函数的定义的基本格式:

1
2
def 函数名(参数1,参数2,参数3):
函数体


参数可以改变吗?

看看下面的代码,有没有一点出乎意料?

1
2
3
4
5
6
7
8
>>> def change(n):
... n = 'abc'
...
>>> name = 'feather'
>>> change(name)
>>> name
'feather'
>>>

可以看到,函数内的操作并没有改变函数外的变量,实际上,函数的调用类似于下面代码

1
2
3
4
5
6
>>> name = 'feather'
>>> n = name # 相当于传递参数
>>> n = 'abc' # 函数内部的代码
>>> name
'feather'
>>>

显然,变量n和变量name是完全不同的变量,变量n的改变并不影响到变量name,就算是定义函数时,函数名后的变量名和name一样,此name也并非彼name,如下:

1
2
3
4
5
6
7
8
>>> def change(name):
... name = 'abc'
...
>>> name = 'feather'
>>> change(name)
>>> name
'feather'
>>>

通常来说,函数名后面的变量叫做形参,而调用函数时提供的值叫做实参(或者叫做参数),形参是实参的一份拷贝。

既然形参是实参的一份拷贝,那么形参的改变不影响实参咯

这么理解的同学要注意下面这段代码:

1
2
3
4
5
6
7
8
>>> lst =[1, 2, 3]
>>> def change(a):
... a[0] = 100
...
>>> change(lst)
>>> lst
[100, 2, 3]
>>>

嗯?不是说“形参是实参的一份拷贝”吗?

其实,上面传入了一个列表类型的参数,形参a拷贝的是实参lst这个变量本身,a[0]=100是通过变量a改变列表里的元素,而不是改变a这个形参本身,而像a=123a+=1这种对变量操作的才叫做改变a参数,可以对比下面的代码来理解

1
2
3
4
5
6
7
8
>>> lst =[1, 2, 3]
>>> def change(a):
... a = 123
...
>>> change(lst)
>>> lst
[1, 2, 3]
>>>

作用域

变量都有一定的使用范围,这个范围称为作用域,在执行赋值语句x=1后,名字x就对应到数值1,变量引用值,这其实就是个不可见的字典,这个字典叫做命名空间也是我们的作用域,python内置的vars函数可以返回当前作用域的字典

1
2
3
4
5
6
7
8
9
10
>>> def func():
... x = 1
... y = 2
... print(vars())
...
>>> vars()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'func': <function func at 0x7f7e191e70d0>}
>>> func()
{'x': 1, 'y': 2}
>>>

可以看到,函数内外获得的字典不一样,也就是说函数内外的作用域不一样,最外面的作用域称为全局作用域,每个函数调用都会创建一个新的作用域。

看下面代码

1
2
3
4
5
6
7
8
>>> def func():
... x=123
...
>>> x = 1
>>> func()
>>> x
1
>>>

这里的func函数里面为x变量赋值,但并为影响外面的x变量,不难想到,当调用func函数时,创建了一个新的命名空间,语句x=123只是在这个新的命名空间中起作用,所以它不影响外部的x。

函数内的变量(包括定义的参数)称为局部变量,最外层的变量称为全局变量,如果函数内想访问全局变量,如果只是想读取全局变量的值,一般来说是没什么问题的:

1
2
3
4
5
6
7
>>> x = 1
>>> def func():
... print(x)
...
>>> func()
1
>>>

但是想要在函数里存在和全局变量同名的局部变量就不能像这样访问了,全局变量会被同名的局部变量屏蔽,直接访问到的是局部变量,如果需要的话,可以使用globals函数获取全局变量,这个函数返回一个全局变量的字典,可以直接操作这个字典进行访问全局变量,比如下面代码

1
2
3
4
5
6
7
8
9
10
>>> x = 123
>>> def func():
... x = 1
... print('局部变量x', x)
... print('全局变量x', globals()['x'])
...
>>> func()
局部变量x 1
全局变量x 123
>>>

在函数内部将值赋给一个变量,这个变量就会自动成为局部变量,那么怎么才能在函数里给全局变量重新赋值呢?

这个时候可以是用关键字global声明变量为全局变量:

1
2
3
4
5
6
7
8
9
>>> x = 1
>>> def func():
... global x
... x = 2
...
>>> func()
>>> x
2
>>>

关键字参数和默认参数

在上面,我们使用的参数都叫做 “位置参数”,函数定义时参数的位置决定了调用时参数的位置,有时候,调用函数弄乱了参数的位置可不是间好事,下面介绍的 “关键字参数”可以忽略这种位置的问题。

关键字参数其实是对于函数调用来说的,而不是函数的定义,怎么说呢,还是看看下面的代码吧

1
2
3
4
5
6
7
8
>>> def hello(greeting, name):
... print(greeting, name) # greeting是问候语,name是名字
...
>>> hello('hello', 'feather') # 参数位置一一对应
hello feather
>>> hello(name='feather',greeting='hello') # 关键字参数
hello feather
>>>

虽然第二种调用传递的参数的位置不同与函数定义时的位置,但仍能正常的工作,像第二种这样,使用参数名提供的参数叫做 “关键字参数”,它可以明确参数的作用,就算弄乱了参数的位置也无妨。


默认参数可以在函数的定义时给参数提供默认值

1
2
3
4
5
6
7
8
9
10
11
12
>>> def hello(greeting='hello',name='feather'):
... print(greeting, name)
...
>>> hello() # 不提供参数,使用默认值
hello feather
>>> hello('bye') # 提供第一个参数
bye feather
>>> hello('bye', 'Lee') # 提供两个参数
bye Lee
>>> hello(name='Lee') # 只提供name参数
hello Lee
>>>

提示:关键字参数和默认参数其实就是分别针对函数的调用和函数的定义同类事物两种说法(至少本人这么理解 ( ̄_, ̄ )),它们可以和位置参数联合使用,不过要注意的是,对于关键字参数,也就是调用的时候,位置参数应该在关键字参数的前面,不然python解释器会不知道哪个是哪个,不信你可以这样调用上面的函数:hello(name='feather', hello);而对于默认参数,也就是定义的时候,和关键字参数一样,位置参数要放在默认参数的前面,同理︿( ̄︶ ̄)︿

下面给个例子自己感受\( ̄ˇ ̄)>:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> def hello(name, greeting='hello', end='bye~'):
... print(greeting, name)
... print("I'm fine")
... print(end)
...
>>> hello('feather')
hello feather
I'm fine
bye~
>>> hello('feather', 'nice to meet you,')
nice to meet you, feather
I'm fine
bye~
>>> hello('feather', end='bye bye!')
hello feather
I'm fine
bye bye!
>>>> hello() # 这句报错,因为name没有默认值
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: hello() missing 1 required positional argument: 'name'
>>>

提示 :上面输出的是英语常用社交语句,赶紧记下!!! (❍ᴥ❍ʋ)


不写不知道,一写吓一跳,发现函数的内容挺多的,还有部分内容要留到下一篇文章再讲了,先消化吧 。

ヾ( ̄▽ ̄)Bye~Bye~