抽象
抽象可以节省人力,更重要的是,抽象是程序能够被人理解的关键所在。
自定义函数
现在,如果我们需要计算一个裴波那契序列,我们编写如下面的代码:
1 | fibs = [0, 1] |
这样,我们得到了一个数列
1 | [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] |
现在需求变了,我们需要用户可以指定序列的长度,再次编写如下代码:
1 | fibs = [0, 1] |
现在,已经像是一个可用的程序的样子了,但是我们需要写出的代码是可以复用的,我们便会想到把它封装在一个自定义的函数里面
1 | def fibs(num): |
这样,以后只需要直接调用函数 fibs()
就可以轻松获得序列了。
这是一个函数定义的过程,也是问题抽象的过程。
参数魔法
位置参数
函数参数传入的位置是有区别的,如下面的实例:
1 | def hello(greeting, name): |
输出: Hello, akashi!
如果我们传入参数的位置发生了变化,结果也会发生变化:
1 | hello('akashi', 'Hello') |
输出:akashi, Hello!
程序并不能按照我们的意图执行,它不知道参数的正确位置,需要我们传入时就规定好位置
关键字参数和默认值
想要程序知道参数的正确位置,可以使用关键字参数
1 | hello(name='akashi', greeting='Hello') |
这样,指明了关键字,就可以不按位置传递参数
如果直接在定义函数中规定默认参数的值,调用时可以不用传递,但传入即会覆盖默认的参数
收集参数
- 通过
*
可以传入一个元组
1 | def print_params(*params): |
输出:(1 2 3)
当收集的元组参数在中间位置时,需要指定后续参数:
1 | def in_the_middle(x, *y, z): |
输出: 1 (2, 3, 4) 5
- 通过
**
可以传入一个字典
1 | def print_params(**params): |
输出:
1 | {'x': 1, 'y': 2, 'z': 3} |
分配参数
相反,也可以用于分配参数
1 | def add(x, y): |
字典同理
递归
递归,简单来说就是一个函数自己调用自己。递归函数一般包含两个条件:基线条件
和递归条件
基线条件:针对最小问题的解,满足条件时,函数直接返回一个值
递归条件:包含一个或多个调用,这些调用旨在解决问题的一部分
下面来看几个例子:
阶乘
计算
n
的阶乘,公式为1x2x...x(n-1)xn
1 | def factorial(n): |
基线条件:1
的阶乘为 1
递归条件:对于一个大于 1
的数字 n
,其阶乘为 (n-1)*n
那么,满足这两个条件,可以实现为:
1 | def factorial(n): |
幂
计算
x
的n
次幂,x*x*x...
(n
次)
1 | def power(x, n): |
基线条件:任何数字 x
,它的 0
次幂为 1
递归条件:当 n>0
时,x
的 n
次幂为 x
乘以 x
的 n-1
次幂,即 power(x, n-1)*x
那么,满足这两个条件,可以实现为:
1 | def power(x, n): |
二分查找
定义两个指针用来确定搜索范围(lower
, upper
),不断折中缩小范围,如果两指针(上限和下限)相同,则指向的位置就是数字所在的位置,将数字返回
1 | def search(sequence, number, lower=0, upper=None): |
输出:
1 | [4, 8, 34, 67, 95, 100, 123] |