六 Python函数

调用

函数的调用非常简单,python提供了很多内置函数供我们使用,比如:

获取绝对值:

1
2
>>> abs(-123)
123

求多个数中的最大值:

1
2
>>> max(1, 2, 3)
3

求多个数中的最小值:

1
2
>>> min(0, -1, 99)
-1

类型转换

1
2
3
4
>>> int('123')
123
>>> str(-99)
'-99'

以上的方法和形式与Swift语言十分相似,abs()函数max()函数min()函数甚至和SwiftAPI一模一样,包括形式和用法。

再比如,python也提供了hex()函数将一个整数(注意:必须是整数,否则就会报错)转换成一个十六进制表示的字符串。比如:

1
2
3
4
>>> hex(22)
'0x16'
>>> hex(99)
'0x63'

定义

使用def定义函数

例如,创建一个demo.py文件,定义一个test(a, b)函数,并传入参数:

1
2
3
4
5
6
7
8
def test(a, b):
if a > b:
print('a > b')
elif a == b:
print('a = b')
else:
print('a < b')
test(5, 10)

执行:

1
python3 demo.py

输出结果:

1
a < b

注意:我们也可以在当前目录下,进入python交互式环境,然后使用from demo import test的形式导出该demo.py里面具体函数,demo是文件名,注意没有.py后缀,test是函数名,然后在交互式环境下使用test(a, b)函数。

pass占位符

有时候,我们可以在函数或一些程序中使用pass来作为占位符,比如定义一个函数,但是里面没有任何内容,执行就会报错,比如:

1
def fuck():

执行:

1
python3 demo.py

报错:

1
2
3
4
5
➜  Desktop python3 demo.py
File "demo.py", line 2

^
SyntaxError: unexpected EOF while parsing

那想留住这个函数,后面在写函数体里面的内容怎么办呢?有2种方式,第一,使用pass,第二,使用return,例如:

1
2
def fuck():
pass

或者

1
2
def fuck():
return

这样执行python文件,就可以执行里面的程序而不会因为一个空函数而报错。

类型检查

数据类型检查直接使用内置函数isinstance()函数,例如:

1
2
3
4
5
6
7
8
9
def test1(a):
if isinstance(a, (int, float)):
print('you passed an int type')
else:
raise TypeError('What the fucking are you doing?')

def test2(b):
if not isinstance(b, (str)):
print('the parameter you passed is not str type')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> from demo import test1
>>> test1(1)
you passed an int type
>>> test1(2.2)
you passed an int type
>>> test1('1')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/langke/Desktop/demo.py", line 5, in test1
raise TypeError('What the fucking are you doing?')
TypeError: What the fucking are you doing?

>>> from demo import test2
>>> test2(1)
the parameter you passed is not str type

函数返回多个值

python的函数返回多个值的时候,事实上是返回一个元组(tuple),例如:

1
2
3
4
def func1(a, b):
a = pow(a, 2)
b = pow(b, 2)
return a, b

执行:

1
2
3
4
5
6
7
8
9
10
11
12
➜  Desktop python3
Python 3.7.4 (default, Sep 7 2019, 18:27:02)
[Clang 10.0.1 (clang-1001.0.46.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from demo import func1
>>> m, n = func1(3, 4)
>>> print(m)
9
>>> print(n)
16
>>> func1(5, 7)
(25, 49)

参数

位置参数

位置参数就是正常的参数传递,别想复杂了,例如:

1
2
def fuckYou(name):
print('name is %s' % name)

在上面的fuckYou函数中,name就是所谓的位置参数

默认参数

默认参数就是在函数调用的时候,给部分参数设置一个默认的初始值的参数。这个其实也很好理解,现在很多语言都提供默认参数了,比如JavaScriptSwift等等,默认参数在有的时候可以简化我们的代码。例如:

1
2
def fuck(name, gender = 'girl', age = 18):
print('name is {0}, gender is {1}, age is {2}'.format(name, gender, age))
1
2
3
4
5
6
7
>>> from demo import fuck
>>> fuck('langke')
name is langke, gender is girl, age is 18
>>> fuck('Alice', 'boy')
name is Alice, gender is boy, age is 18
>>> fuck('Joe', age = 38)
name is Joe, gender is girl, age is 38

注:在python中,当提供多个默认参数,并且在调用的时候,如果不按顺序调用后面的某个默认参数,就需要指定需要改变的那个默认参数名,比如上面的调用fuck('Joe', age = 38),需要改变默认参数age的值,但是默认参数age前面的默认参数gender保持不变(继续使用默认参数值),那么就需要指定age这个参数名,否则,直接传入参数的话,在函数内部就会一一对应。

另外,当使用默认参数的时候,也要注意,一般情况下,默认参数尽量是不可变参数值,如果是可变的话(比如数组,数组是一个引用类型对象),那么就会造成数据修改。例如:

1
2
3
def appendElement(arr = []):
arr.append('python')
return arr

执行结果:

1
2
3
4
5
6
7
>>> from demo import appendElement
>>> appendElement()
['python']
>>> appendElement()
['python', 'python']
>>> appendElement()
['python', 'python', 'python']

当然,我们也可以使用None空值来改造一下可变的对象参数:

1
2
3
4
5
def appendElement(arr = None):
if arr is None:
arr = []
arr.append('python')
return arr

执行结果:

1
2
3
4
5
6
>>> appendElement()
['python']
>>> appendElement()
['python']
>>> appendElement()
['python']

因此,为了安全起见,建议一般默认参数都是用不可变对象,如果非要使用可变对象,一定要做好安全检查。

可变参数

可变参数就是可以传入任意个数参数,对参数不限制。在python中,可变参数可以在参数名前面加*号来表示这个一个可变参数,传入的可变参数在内部会转换成一个元组(tuple)。例如:

1
2
3
4
5
6
def sum(*params):
print('params type is %s' % type(params))
sum = 0
for param in params:
sum += param
return sum

执行:

1
2
3
4
5
6
7
8
9
10
>>> from demo import sum
>>> sum()
params type is <class 'tuple'>
0
>>> sum(1, 2)
params type is <class 'tuple'>
3
>>> sum(1, 2, 3)
params type is <class 'tuple'>
6

那如果我们要传入一个数组元组将其中的元素作为可变参数传入怎么办呢?同样地,我们还是可以在要传入的数组元组前面加*号,表示将数组元组中的元素作为可变参数。例如:

1
2
3
4
5
6
>>> sum(*[1, 2, 3])
params type is <class 'tuple'>
6
>>> sum(*(4, 5, 6))
params type is <class 'tuple'>
15

哇,就是这么吊!

关键字参数

关键字参数在参数名前面加**表示可传入一个关键字参数,传入关键字参数后,内部会组装成一个字典(dict)。例如:

1
2
3
def obtainInfo(name, age, **params):
print('params type is %s' % type(params))
print('name: %s\nage: %d\nparams: %s' % (name, age, params))

执行:

1
2
3
4
5
6
7
8
9
10
>>> obtainInfo('langke', 18)
params type is <class 'dict'>
name: langke
age: 18
params: {}
>>> obtainInfo('langke', 18, gender = 'boy', height = 1.88)
params type is <class 'dict'>
name: langke
age: 18
params: {'gender': 'boy', 'height': 1.88}

类似地,我们也可以在字典(dict)前面加**作为关键字参数传入:

1
2
3
4
5
6
7
8
9
dicParams = {
'gender': 'boy',
'height': 1.88
}
>>> obtainInfo('langke', 18, **dicParams)
params type is <class 'dict'>
name: langke
age: 18
params: {'gender': 'boy', 'height': 1.88}

我们也可以对关键字参数进行判断某个key是否存在,例如:

1
2
3
4
5
def person(name, age, **params):
if 'city' in params:
print('city exists')
print(name, age, params)
person('langke', 18, city = 'Hangzhou')

执行及结果:

1
2
3
➜  Desktop python3 demo.py 
city exists
langke 18 {'city': 'Hangzhou'}

命名关键字参数

命名关键字参数使用*号来分割前后参数,*号后面的参数就叫做命名关键字参数。例如:

1
2
def person(name, age, *, city, height):
print('name:{0}, age:{1}, city:{2}, height:{3}'.format(name, age, city, height))

其中,cityheight就是命名关键字参数,而且所有的命名关键字参数必须指定传入值。例如:

1
2
>>> person('langke', 18, city = 'Hangzhou', height = 163)
name:langke, age:18, city:Hangzhou, height:163

如果少了某个关键字参数,就会报错。例如:

1
2
3
4
>>> person('langke', 18, city = 'Hangzhou')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: person() missing 1 required keyword-only argument: 'height'

关键字参数可以互换位置,但是必须要注意的是,关键字参数和关键字参数前面的位置参数不能调换。例如:

关键字参数cityheight交换位置:

1
2
>>> person('langke', 18, height = 163, city = 'Hangzhou')
name:langke, age:18, city:Hangzhou, height:163

关键字参数位置参数交换位置,比如把关键字参数height放在位置参数age位置就报错:

1
2
3
>>> person('langke', height = 163, 18, city = 'Hangzhou')
File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument

若函数中已经有可变参数,那么命名关键字参数就不再需要*号了。例如:

1
2
3
def person(name, age, *args, city, height):
print(args)
print('name:{0}, age:{1}, city:{2}, height:{3}'.format(name, age, city, height))

调用函数:

1
2
3
4
5
6
7
8
9
10
11
>>> person('langke', 18, city = 'Hangzhou', height = 163)
()
name:langke, age:18, city:Hangzhou, height:163

>>> person('langke', 18, *[1, 2, 3], city = 'Hangzhou', height = 163)
(1, 2, 3)
name:langke, age:18, city:Hangzhou, height:163

>>> person('langke', 18, 1, 2, 3, city = 'Hangzhou', height = 163)
(1, 2, 3)
name:langke, age:18, city:Hangzhou, height:163

最后,命名关键字参数可以有默认值(类似默认参数)。例如:

1
2
def person(name, age, *, city = 'Hangzhou', height):
print('name:{0}, age:{1}, city:{2}, height:{3}'.format(name, age, city, height))

执行:

1
2
>>> person('langke', 18, height = 163)
name:langke, age:18, city:Hangzhou, height:163

参数组合

参数组合也就是使用位置参数默认参数可变参数命名关键字参数关键字参数结合起来使用,但是参数定义的顺序必须是:位置参数默认参数可变参数命名关键字参数关键字参数。例如:

1
2
3
4
5
def f1(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

def f2(a, b, c=0, *, d, **kw):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)

调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}

>>> f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}

>>> f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}

>>> f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}

>>> f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}

最后一点要注意的是,调用上面的函数我们也可以只使用元组(tuple)数组(list)的方式传入参数:

1
2
3
4
5
6
7
8
9
>>> args = (1, 2, 3, 4)
>>> kw = {'d': 99, 'x': '#'}
>>> f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}

>>> args = (1, 2, 3)
>>> kw = {'d': 88, 'x': '#'}
>>> f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}

所以,对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。

递归

递归函数就是自己调用自己的函数。例如:

1
2
3
4
def sum(n):
if n == 0:
return 0
return n + sum(n - 1)

执行:

1
2
3
4
>>> sum(5)
15
>>> sum(10)
55

小结

python的函数其实和其他编程语言都大同小异,需要注意的一点是函数参数的顺序,尤其是当我们使用组合参数时,一不小心很容易出错。