第008课:函数和模块

在讲解本节课的内容之前,我们先来研究⼀道数学题,请说出下⾯的⽅程有多少组正整数解。
第008课:函数和模块_第1张图片

 

你可能已经想到了,这个问题其实等同于将 8 个苹果分成四组且每组⾄少⼀个苹果有多少种⽅案,因此,该问题还可以进⼀步等价于在分隔8 个苹果的 7 个空隙之间插⼊三个隔板将苹果分成四组有多少种⽅案,也就是从7 个空隙选出 3 个空隙放⼊隔板的组合数,所以答案是 C(7,3)=35 。组合数的计算公式如下示。
第008课:函数和模块_第2张图片

 

根据我们前⾯学习的知识,可以⽤循环做累乘的⽅式来计算阶乘,那么通过下⾯的 Python 代码我们就可以计算出组合数 C(M,N) 的值,代码如下所示。
"""
输⼊M和N计算C(M,N)

"""
m = int(input('m = '))
n = int(input('n = '))
# 计算m的阶乘
fm = 1
for num in range(1, m + 1):
 fm *= num
# 计算n的阶乘
fn = 1
for num in range(1, n + 1):
 fn *= num
# 计算m-n的阶乘
fm_n = 1
for num in range(1, m - n + 1):
 fm_n *= num
# 计算C(M,N)的值
print(fm // fn // fm_n)
函数的作⽤
不知⼤家是否注意到,上⾯的代码中我们做了三次求阶乘,虽然 m n m - n 的值各不相同,但是三 段代码并没有实质性的区别,属于重复代码。世界级的编程⼤师Martin Fowler 先⽣曾经说过: 代码有 很多种坏味道,重复是最坏的⼀种! 。要写出⾼质量的代码⾸先要解决的就是重复代码的问题。对于上⾯的代码来说,我们可以将计算阶乘的功能封装到⼀个称为“ 函数 的代码块中,在需要计算阶乘的地⽅,我们只需要“ 调⽤函数 就可以了。
定义函数
数学上的函数通常形如 y = f(x) 或者 z = g(x, y) 这样的形式,在 y = f(x) 中, f 是函数的名字, x
是函数的⾃变量, y 是函数的因变量;⽽在 z = g(x, y) 中, g 是函数名, x y 是函数的⾃量, z
是函数的因变量。 Python 中的函数跟这个结构是⼀致的,每个函数都有⾃⼰的名字、⾃变量和因变量。我们通常把Python 中函数的⾃变量称为函数的参数,⽽因变量称为函数的返回值。
Python 中可以使⽤ def 关键字来定义函数,和变量⼀样每个函数也应该有⼀个漂亮的名字,命名规则跟变量的命名规则是⼀致的(赶紧想⼀想我们之前讲过的变量的命名规则)。在函数名后⾯的圆括号中可以放置传递给函数的参数,就是我们刚才说到的函数的⾃变量,⽽函数执⾏完成后我们会通过return 关键字来返回函数的执⾏结果,就是我们刚才说的函数的因变量。⼀个函数要执⾏的代码块 (要做的事情)也是通过缩进的⽅式来表示的,跟之前分⽀和循环结构的代码块是⼀样的。⼤家不要忘了 def 那⼀⾏的最后⾯还有⼀个 : ,之前提醒过⼤家,那是在英⽂输⼊法状态下输⼊的冒号。 我们可以通过函数对上⾯的代码进⾏重构。所谓重构,是在不影响代码执⾏结果的前提下对代码的结构 进⾏调整。 重构之后的代码如下所示。
"""
输⼊M和N计算C(M,N)

"""
# 定义函数:def是定义函数的关键字、fac是函数名,num是参数(⾃变量)
def fac(num):
 """求阶乘"""
 result = 1
 for n in range(1, num + 1):
 result *= n
 # 返回num的阶乘(因变量)
 return result
m = int(input('m = '))
n = int(input('n = '))
# 当需要计算阶乘的时候不⽤再写重复的代码⽽是直接调⽤函数fac
# 调⽤函数的语法是在函数名后⾯跟上圆括号并传⼊参数
print(fac(m) // fac(n) // fac(m - n))
函数的参数
参数的默认值
如果函数中没有 return 语句,那么函数默认返回代表空值的 None 。另外,在定义函数时,函数也可以没有⾃变量,但是函数名后⾯的圆括号是必须有的。Python 中还允许函数的参数拥有默认值,我们可以把上⼀课“CRAPS 赌博游戏 的摇⾊⼦获得点数的功能封装成函数,代码如下所示。
"""
参数的默认值

"""
from random import randint
# 定义摇⾊⼦的函数,n表示⾊⼦的个数,默认值为2
def roll_dice(n=2):
 """摇⾊⼦返回总的点数"""
 total = 0
 for _ in range(n):
 total += randint(1, 6)
 return total
# 如果没有指定参数,那么n使⽤默认值2,表示摇两颗⾊⼦
print(roll_dice())
# 传⼊参数3,变量n被赋值为3,表示摇三颗⾊⼦获得点数
print(roll_dice(3))
我们再来看⼀个更为简单的例⼦。
def add(a=0, b=0, c=0):
 """三个数相加求和"""
 return a + b + c
# 调⽤add函数,没有传⼊参数,那么a、b、c都使⽤默认值0
print(add()) # 0
# 调⽤add函数,传⼊⼀个参数,那么该参数赋值给变量a, 变量b和c使⽤默认值0
print(add(1)) # 1
# 调⽤add函数,传⼊两个参数,1和2分别赋值给变量a和b,变量c使⽤默认值0
print(add(1, 2)) # 3
# 调⽤add函数,传⼊三个参数,分别赋值给a、b、c三个变量
print(add(1, 2, 3)) # 6
# 传递参数时可以不按照设定的顺序进⾏传递,但是要⽤“参数名=参数值”的形式
print(add(c=50, a=100, b=200)) # 350
注意 :带默认值的参数必须放在不带默认值的参数之后,否则将产⽣ SyntaxError 错误,错误消
息是: non-default argument follows default argument ,翻译成中⽂的意思是 没有默认值的
参数放在了带默认值的参数后⾯
可变参数
接下来,我们还可以实现⼀个对任意多个数求和的 add 函数,因为 Python 语⾔中的函数可以通过星号表达式语法来⽀持可变参数。所谓可变参数指的是在调⽤函数时,可以向函数传⼊0 个或任意多个参数。 将来我们以团队协作的⽅式开发商业项⽬时,很有可能要设计函数给其他⼈使⽤,但有的时候我们并不知道函数的调⽤者会向该函数传⼊多少个参数,这个时候可变参数就可以派上⽤场。下⾯的代码演示了⽤可变参数实现对任意多个数求和的 add 函数。
"""
可变参数

"""
# ⽤星号表达式来表示args可以接收0个或任意多个参数
def add(*args):
 total = 0
 # 可变参数可以放在for循环中取出每个参数的值
 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 ⽂件中定义了两个同名的函数,如下所示。
def foo():
 print('hello, world!')
def foo():
 print('goodbye, world!')
 
foo() # ⼤家猜猜调⽤foo函数会输出什么
当然上⾯的这种情况我们很容易就能避免,但是如果项⽬是团队协作多⼈开发的时候,团队中可能有多个程序员都定义了名为 foo 的函数,这种情况下怎么解决命名冲突呢?答案其实很简单, Python 中每个⽂件就代表了⼀个模块(module ),我们在不同的模块中可以有同名的函数,在使⽤函数的时候我们通过 import 关键字导⼊指定的模块再使⽤ 完全限定名 的调⽤⽅式就可以区分到底要使⽤的是哪个模块中的 foo 函数,代码如下所示。
module1.py
def foo():
 print('hello, world!')
module2.py
def foo():
 print('goodbye, world!')
test.py
import module1
import module2
# ⽤“模块名.函数名”的⽅式(完全限定名)调⽤函数,
module1.foo() # hello, world!
module2.foo() # goodbye, world!
在导⼊模块时,还可以使⽤ as 关键字对模块进⾏别名,这样我们可以使⽤更为简短的完全限定名。
test.py
import module1 as m1
import module2 as m2
m1.foo() # hello, world!
m2.foo() # goodbye, world!
上⾯的代码我们导⼊了定义函数的模块,我们也可以使⽤ from...import... 语法从模块中直接导⼊需
要使⽤的函数,代码如下所示。
test.py
from module1 import foo
foo() # hello, world!
from module2 import foo
foo() # goodbye, world!
但是,如果我们如果从两个不同的模块中导⼊了同名的函数,后导⼊的函数会覆盖掉先前的导⼊,就像下⾯的代码中,调⽤ foo 会输出 hello, world! ,因为我们先导⼊了 module2 foo ,后导⼊了module1 的 foo 。如果两个 from...import... 反过来写,就是另外⼀番光景了。
test.py
from module2 import foo
from module1 import foo
foo() # hello, world!
如果想在上⾯的代码中同时使⽤来⾃两个模块中的 foo 函数也是有办法的,⼤家可能已经猜到了,还是⽤ as 关键字对导⼊的函数进⾏别名,代码如下所示。
test.py
from module1 import foo as f1
from module2 import foo as f2
f1() # hello, world!
f2() # goodbye, world!
标准库中的模块和函数
Python 标准库中提供了⼤量的模块和函数来简化我们的开发⼯作,我们之前⽤过的 random 模块就为我们提供了⽣成随机数和进⾏随机抽样的函数;⽽ time 模块则提供了和时间操作相关的函数;上⾯求阶乘的函数在Python 标准库中的 math 模块中已经有了,实际开发中并不需要我们⾃⼰编写,⽽ math 模块中还包括了计算正弦、余弦、指数、对数等⼀系列的数学函数。随着我们进⼀步的学习Python 编程知识,我们还会⽤到更多的模块和函数。
Python 标准库中还有⼀类函数是不需要 import 就能够直接使⽤的,我们将其称之为内置函数,这些内置函数都是很有⽤也是最常⽤的,下⾯的表格列出了⼀部分的内置函数。
函数
                                            说明
abs
返回⼀个数的绝对值,例如: abs(-1.3) 会返回 1.3
bin
把⼀个整数转换成以 '0b' 开头的⼆进制字符串,例如: bin(123) 会返回 '0b1111011'
chr
Unicode 编码转换成对应的字符,例如: chr(8364) 会返回 '€'
hex
将⼀个整数转换成以 '0x' 开头的⼗六进制字符串,例如: hex(123) 会返回 '0x7b'
input
从输⼊中读取⼀⾏,返回读到的字符串。
len
获取字符串、列表等的⻓度。
max
返回多个参数或⼀个可迭代对象(后⾯会讲)中的最⼤值,例如: max(12, 95, 37) 会返回 95
min
返回多个参数或⼀个可迭代对象(后⾯会讲)中的最⼩值,例如: min(12, 95, 37) 会返回 12
oct
把⼀个整数转换成以 '0o' 开头的⼋进制字符串,例如: oct(123) 会返回 '0o173'
open
打开⼀个⽂件并返回⽂件对象(后⾯会讲)。
ord
将字符转换成对应的 Unicode 编码,例如: ord('€') 会返回 8364
pow
求幂运算,例如: pow(2, 3) 会返回 8 pow(2, 0.5) 会返回 1.4142135623730951
print
打印输出。
range
构造⼀个范围序列,例如: range(100) 会产⽣ 0 99 的整数序列。
round
按照指定的精度对数值进⾏四舍五⼊,例如: round(1.23456, 4) 会返回 1.2346
sum
对⼀个序列中的项从左到右进⾏求和运算,例如: sum(range(1, 101)) 会返回 5050
type
返回对象的类型,例如: type(10) 会返回 int ;⽽ type('hello') 会返回 str

你可能感兴趣的:(Python语言基础,数据结构,python)