轻松初探Python(六)—函数

时间:2022-05-03
本文章向大家介绍轻松初探Python(六)—函数,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

这是「AI 学习之路」的第 6 篇,「Python 学习」的第 6 篇

题外话

这周工作日 5 天,我并没有更新文章,但大家并不要以为小之懒惰了。正好相反,自从上篇的 AI 入门文章后,我自己便开始进行机器学习的系统学习了,这周一到周五,只要有空闲时间,我就开始看吴恩达 Coursera 的视频,可以说是非常痴迷了。

吴教授的课程非常通俗易懂,而且他本人的教学风格也是不紧不慢,循序渐进,甚至有关微积分和线代甚至 Octave 这些知识点都花了比较多的篇幅进行展开讲解,亲身体会后,再次推荐给大家。

目前,机器学习篇我已经学到一半了,实际上本来可以更快一些,但中间的一些微积分和线代的知识点,我又回炉复习了一下。非常庆幸我在大四的时候把高数重新复习了一遍,现在虽说不能完全回想起来,但回炉和记忆的串联算是比较快的,节省了很多的时间。

同时,我现在每天保持一到两题的 LeetCode 刷题量,实际上我不太追求说要刷的多快,刷题的目的一来是巩固基础,二来是每天刷一两道,活动下脑子。我每天早上上班前,先打开一道题,然后把题目阅读一下,过个脑子,上班途中就想想思路,如果思路比较清晰,到公司在别人吃早饭的时间,我就把代码提交了,如果思路不太顺,我就工作空闲或者中午的时候在桌上用纸笔画一画,然后晚上下班之后开始码代码。

如果这样的流程一天时间还是想不起来思路,那我就会直接看一下 discuss 或者 Solution,不追求必须靠自己解答出来,只要学到方法,过个脑子,然后把代码码出来上传到 GitHub 留个记录,对我来说就够了。

好了,回归正题,今天我们来较为细致的讲解下 Python 中的函数。

函数的定义

我们之前已经看了很多函数的使用例子了,比如我们要定义一个函数可以这样写

>>> def testFun(a):
...     print(a)
...
>>> testFun
<function testFun at 0x1060f28c8>
>>> testFun('这是一个方法输出')
这是一个方法输出

我们来看看这段代码。通过 def 语句,依次写出函数名、括号、括号中的参数,还有最后的冒号,千万不要忘记了。

冒号后面,就是具体的函数体了,函数体第一行和函数定义要有一个 Tab 的缩进距离。我们知道 Python 是没有分号和大括号来区分代码的结束和开始的,所以缩进的问题一定要注意,如果你的缩进不正确,可能会报 indented 错误。

在交互式环境下定义函数的时候,冒号输入完毕回车后,会有 ... 的提示,每行结束回车到下一行,连续两次回车定义该函数完毕,重新回到 >>> 提示符下。

Python 是一门面向对象语言,一切都是对象,甚至函数本身也是对象,我们称这种特性为「函数式编程」,上面的例子中,我们直接打印 testFun 是可以打印出它的函数类型的。

像我们熟悉的 Kotlin、Groovy 还有 Go 语言,都有这样的特性,函数式编程可以有非常强大的拓展性,同时可以很轻易的解决很多不支持函数式编程的语言下的一些写法问题。这个我会在后面专门写一篇文章来介绍 Python 的函数式编程。

使用函数很简单, 函数名、括号,加上参数即可调用函数。因为 Python 是动态类型语言,所以我们不需要像 Java 那样,对每一个变量和方法参数都提前在编译期设置好类型,我们定义 testFun(a) 的时候,并没有指示 a 到底是字符串类型还是别的类型。这样的自由肯定是有一定代价的,当函数体内部对参数的使用有较严格的要求的时候,如果传参类型错误,就会报错。

>>> def func_abs(x):
...   if x >= 0:
...      return x
...   else:
...      return -x
...
>>> func_abs(-1)
1
>>> func_abs('string')
TypeError: '>=' not supported between instances of 'str' and 'int'

函数的参数

函数的定义不是很复杂,但搭配参数,就会非常灵活,Python 的参数五花八门,除了我们常用的位置参数外,还有默认参数、可变参数和关键字参数。

位置参数

我们之前定义的 testFun(a) 中的 a 就是一个位置参数,当然你可以设置很多位置参数,顾名思义,参数的意义是和位置一一对应的,比如我们现在改写下 testFun 让它具备输出两个参数的能力

>>> testFun(a,b):
...    print(a,b)
...
>>> testFun('测试','参数')
测试 参数

刚刚说的位置一致性的意思就是我们的输入参数的顺序是和函数定义时候的参数顺序是一致的,第一个参数是'测试',第二个参数是'参数',只能代表 a='测试',b='参数',顺序不能错乱。

默认参数

Java 中,如果需要使用一个函数的多种参数形式,是通过重载的形式的,这种方式是比较麻烦的,比如,上面的例子中,我们想让 testFun 既可以使用一个参数,也可以使用两个参数

public void testFun(String a){
    System.out.println(a);
}

public void testFun(String a,String b){
    System.out.println(a + " " + b);
}

在 Python 中,我们可以使用默认参数来一次性完成这样的函数定义

>>> def testFun(a,b='函数'):
...     print(a,b)
...
>>> testFun('测试')
测试 函数
>>> testFun('测试',"参数")
测试 参数

使用 testFun(a) 的时候,会自动把 b 赋值为 '函数',只有当你需要改变 b 的时候,才需要自己输入参数值。需要注意的是,默认参数定义要放在位置参数的后面。

不仅如此,默认参数的默认值一定要指向的是不可变对象,否则就会出现一些难以预料的问题,这里举一个例子

>>> def app_end(L=[]):
...     L.append('END')
...     return L
...
>>> app_end()
['END']
>>> app_end()
['END','END']

注意到了吗,虽然我们的默认参数 L 是一个空 list,但在调用的过程中,每次添加元素,都会被添加到 L 这个 list 中去,而不是我们预料的那样,每次都是一个新的单元素 list,所以我们的默认参数,尽量使用不可变对象,除非你的设计逻辑就是如此。

可变参数

默认参数虽然可以拓展函数的参数数量,但毕竟数量还是固定的,如果我们想让参数数量是任意的,可以使用可变参数,可变参数很简单,在参数前加 * 号即可

>>> def calcSum(*nums):
...     sum = 0
...     for n in nums:
...         sum = sum + n
...     return sum
...
>>> calcSum(1,2,3)
6
>>> nums = [1,2,3]
>>> calcSum(*nums)
6
>>> nums = (1,2,3)
>>> calcSum(*nums)
6

我们也可以把 list 和 tuple 加星号传入可变参数中

关键字参数

关键字参数和可变参数一样,可以允许你传入 0 到任意个参数,不过这些参数都是含有参数名的,在函数内部是以一个 dict 的形式组装

>>> def testKW(a,b,**kw):
...     print(a,b,kw)
...
>>> testKW('测试','关键字参数',name='小之')
测试 关键字参数 {'name':'小之'}
>>> kw = {'name':'小之','age':23}
>>> testKW('测试','关键字参数',**kw)
测试 关键字参数 {'name':'小之','age':23}

当然,你可以像可变参数那种形式一样,通过提前定义好 dict,再把变量以 ** 开头传入。

关键字参数比较随意,你可以传入不受限制的参数,如果你需要判断传了哪些参数,你还需要在函数体内部进行判断,这个时候,我们就可以用命名关键字参数来作一定的限制

>>> def testKW(a,b,*,name,age):
...     print(a,b,name,age)
...
>>> testKW('测试','命名关键字参数',name='小之',age=23)
测试 命名关键字参数 小之 23

命名关键字参数需要一个分隔符 * ,* 号后面就会被看作是命名关键字参数。如果定义中有一个可变参数,那么后面的命名关键字参数就不需要 * 了,就像这样

>>> def testKW(a,b,*args,name,age):
...     print(a,b,args,name,age)

命名关键字参数是可以有默认值的,有默认值的情况下,可以不传入该参数,这点和默认参数有点类似

>>> def testKW(a,b,*,name='小之',age):
...     print(a,b,name,age)
...
>>> testKW('测试','命名关键字参数',23)
测试 命名关键字参数 小之 23

参数的组合

Python 中,上述参数可以组合使用,但需要注意一定的顺序:必选位置参数、默认参数、可变参数、命名关键字参数、关键字参数,这部分我将用廖大的例子来简单介绍下,我们先定义两个有若干参数的函数:

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)

调用的时候,Python 解释器会自动按照参数位置和参数名把对应参数穿进去

>>> 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 和 dict 完成上述函数的调用

>>> 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) 的形式来调用任意函数,无论它的参数是如何定义的。你去翻看源码,可以看到很多内置的函数都是用这种形式定义函数的。