函数和模块的使用
在开始今天的内容之前,我们先来研究一道数学题,请说出下面的方程有多少组正整数解。
事实上,换个角度思考,上面的问题等同于将8个苹果分成四组每组至少一个苹果有多少种方案。想到这一点问题的答案就呼之欲出了。
函数的作用
不知道大家是否注意到,在上面的代码中,我们做了3次求阶乘,这样的代码实际上就是重复代码。编程大师Martin Fowler先生曾经说过:“代码有很多种坏味道,重复是最坏的一种!”,要写出高质量的代码首先要解决的就是重复代码的问题。对于上面的代码来说,我们可以将计算阶乘的功能封装到一个称之为“函数”的功能模块中,在需要计算阶乘的地方,我们只需要“调用”这个“函数”就可以了。
定义函数
在Python中可以使用def关键字来定义函数,和变量一样每个函数也有一个响亮的名字,而且命名规则跟变量的命名规则是一致的。在函数名后面的圆括号中可以放置传递给函数的参数,这一点和数学上的函数非常相似,程序中函数的参数就相当于是数学上说的函数的自变量,而函数执行完成后我们可以通过return关键字来返回一个值,这相当于数学上说的函数的因变量。
在了解了如何定义函数后,我们可以对上面的代码进行重构,所谓重构就是在不影响代码执行结果的前提下对代码的结构进行调整,重构之后的代码如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 |
def factorial(num): """求阶乘""" result = 1 for n in range(1, num + 1): result *= n return result m = int(input('m = ')) n = int(input('n = ')) # 当需要计算阶乘的时候不用再写循环求阶乘而是直接调用已经定义好的函数 print(factorial(m) // factorial(n) // factorial(m - n)) |
说明: Python的math模块中其实已经有一个factorial函数了,事实上要计算阶乘可以直接使用这个现成的函数而不用自己定义。下面例子中的一些函数在Python中也都是现成的,我们这里是为了讲解函数的定义和使用才把它们又实现了一遍,实际开发中不建议做这种低级的重复性的工作。
函数的参数
函数是绝大多数编程语言中都支持的一个代码的”构建块”,但是Python中的函数与其他语言中的函数还是有很多不太相同的地方,其中一个显著的区别就是Python对函数参数的处理。在Python中,函数的参数可以有默认值,也支持使用可变参数,所以Python并不需要像其他语言一样支持函数的重载,因为我们在定义一个函数的时候可以让它有多种不同的使用方式,下面是两个小例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
from random import randint def roll_dice(n=2): """摇色子""" total = 0 for _ in range(n): total += randint(1, 6) return total def add(a=0, b=0, c=0): """三个数相加""" return a + b + c # 如果没有指定参数那么使用默认值摇两颗色子 print(roll_dice()) # 摇三颗色子 print(roll_dice(3)) print(add()) print(add(1)) print(add(1, 2)) print(add(1, 2, 3)) # 传递参数时可以不按照设定的顺序进行传递 print(add(c=50, a=100, b=200)) |
我们给上面两个函数的参数都设定了默认值,这也就意味着如果在调用函数的时候如果没有传入对应参数的值时将使用该参数的默认值,所以在上面的代码中我们可以用各种不同的方式去调用add函数,这跟其他很多语言中函数重载的效果是一致的。
其实上面的add函数还有更好的实现方案,因为我们可能会对0个或多个参数进行加法运算,而具体有多少个参数是由调用者来决定,我们作为函数的设计者对这一点是一无所知的,因此在不确定参数个数的时候,我们可以使用可变参数,代码如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# 在参数名前面的*表示args是一个可变参数 def add(*args): total = 0 for val in args: total += val return total # 在调用add函数时可以传入0个或多个参数 print(add()) print(add(1)) print(add(1, 2)) print(add(1, 2, 3)) print(add(1, 3, 5, 7, 9)) |
用模块管理函数
对于任何一种编程语言来说,给变量、函数这样的标识符起名字都是一个让人头疼的问题,因为我们会遇到命名冲突这种尴尬的情况。最简单的场景就是在同一个.py文件中定义了两个同名函数,由于Python没有函数重载的概念,那么后面的定义会覆盖之前的定义,也就意味着两个函数同名函数实际上只有一个是存在的。
1 2 3 4 5 6 7 8 9 10 |
def foo(): print('hello, world!') def foo(): print('goodbye, world!') # 下面的代码会输出什么呢? foo() |
当然上面的这种情况我们很容易就能避免,但是如果项目是由多人协作进行团队开发的时候,团队中可能有多个程序员都定义了名为foo的函数,那么怎么解决这种命名冲突呢?答案其实很简单,Python中每个文件就代表了一个模块(module),我们在不同的模块中可以有同名的函数,在使用函数的时候我们通过import关键字导入指定的模块就可以区分到底要使用的是哪个模块中的foo函数,代码如下所示。
1 |
module1.py |
1 2 |
def foo(): print('hello, world!') |
1 |
module2.py |
1 2 |
def foo(): print('goodbye, world!') |
1 |
test.py |
1 2 3 4 5 6 7 8 9 |
from module1 import foo # 输出hello, world! foo() from module2 import foo # 输出goodbye, world! foo() |
也可以按照如下所示的方式来区分到底要使用哪一个foo函数。
1 |
test.py |
1 2 3 4 5 |
import module1 as m1 import module2 as m2 m1.foo() m2.foo() |
练习
练习1:实现计算求最大公约数和最小公倍数的函数。
1 2 3 4 5 6 7 8 9 10 11 |
def gcd(x, y): """求最大公约数""" (x, y) = (y, x) if x > y else (x, y) for factor in range(x, 0, -1): if x % factor == 0 and y % factor == 0: return factor def lcm(x, y): """求最小公倍数""" return x * y // gcd(x, y) |
练习2:实现判断一个数是不是回文数的函数。
1 2 3 4 5 6 7 8 |
def is_palindrome(num): """判断一个数是不是回文数""" temp = num total = 0 while temp > 0: total = total * 10 + temp % 10 temp //= 10 return total == num |
本文来自这个系列长期转载Python-100-Days ,本文观点不代表蓝洛水深立场,转载请联系原作者。