函数和模块

世界级的编程大师Martin Fowler曾经说过:“代码有很多种坏味道,重复是最坏的一种!

定义函数

def fac(num):
    result = 1
    for n in range(2, num + 1):
        result *= n
    return result


m = int(input('m = '))
n = int(input('n = '))
# 计算阶乘的时候不需要写重复的代码而是直接调用函数
# 调用函数的语法是在函数名后面跟上圆括号并传入参数
print(fac(m) // fac(n) // fac(m - n))

使用函数可以帮助我们将功能上相对独立且会被重复使用的代码封装起来,当我们需要这些的代码,不是把重复的代码再编写一遍,而是通过调用函数实现对既有代码的复用。事实上,Python 标准库的math模块中,已经有一个名为factorial的函数实现了求阶乘的功能,我们可以直接用import math导入math模块,然后使用math.factorial来调用求阶乘的函数;我们也可以通过from math import factorial直接导入factorial函数来使用它。


from math import factorial

m = int(input('m = '))
n = int(input('n = '))
print(factorial(m) // factorial(n) // factorial(m - n))

重复发明轮子”是一件非常糟糕的事情。如果你觉得factorial这个名字太长,书写代码的时候不是特别方便,我们在导入函数的时候还可以通过as关键字为其别名。在调用函数的时候,我们可以用函数的别名,而不再使用它之前的名字


from math import factorial as f

m = int(input('m = '))
n = int(input('n = '))
print(f(m) // f(n) // f(m - n))

函数的参数

位置参数和关键字参数

def make_judgement(a, b, c):
    """判断三条边的长度能否构成三角形"""
    return a + b > c and b + c > a and a + c > b

上面make_judgement函数有三个参数,这种参数叫做位置参数,在调用函数时通常按照从左到右的顺序依次传入,而且传入参数的数量必须和定义函数时参数的数量相同。

print(make_judgement(1, 2, 3))  # False
print(make_judgement(4, 5, 6))  # True

如果不想按照从左到右的顺序依次给出abc 三个参数的值,也可以使用关键字参数,通过“参数名=参数值”的形式为函数传入参数。

print(make_judgement(b=2, c=3, a=1))  # False
print(make_judgement(c=6, b=4, a=5))  # True

在定义函数时,我们可以在参数列表中用/设置强制位置参数positional-only arguments),用*设置命名关键字参数。所谓强制位置参数,就是调用函数时只能按照参数位置来接收参数值的参数;而命名关键字参数只能通过“参数名=参数值”的方式来传递和接收参数。

说明:强制位置参数是 Python 3.8 引入的新特性,在使用低版本的 Python 解释器时需要注意。

# *后面的参数是命名关键字参数
def make_judgement(*, a, b, c):
    """判断三条边的长度能否构成三角形"""
    return a + b > c and b + c > a and a + c > b


# 下面的代码会产生TypeError错误,错误信息提示“函数没有位置参数但却给了3个位置参数”
# TypeError: make_judgement() takes 0 positional arguments but 3 were given
# print(make_judgement(1, 2, 3))

参数的默认值

Python 中允许函数的参数拥有默认值,我们可以把之前讲过的一个例子“CRAPS赌博游戏”中摇色子获得点数的功能封装到函数中,代码如下所示。

from random import randrange


# 定义摇色子的函数
# 函数的自变量(参数)n表示色子的个数,默认值为2
# 函数的因变量(返回值)表示摇n颗色子得到的点数
def roll_dice(n=2):
    total = 0
    for _ in range(n):
        total += randrange(1, 7)
    return total


# 如果没有指定参数,那么n使用默认值2,表示摇两颗色子
print(roll_dice())
# 传入参数3,变量n被赋值为3,表示摇三颗色子获得点数
print(roll_dice(3))

需要注意的是,带默认值的参数必须放在不带默认值的参数之后,否则将产生SyntaxError错误,错误消息是:non-default argument follows default argument,翻译成中文的意思是“没有默认值的参数放在了带默认值的参数后面”。

可变参数

Python 语言中可以通过星号表达式语法让函数支持可变参数。所谓可变参数指的是在调用函数时,可以向函数传入0个或任意多个参数。

# 用星号表达式来表示args可以接收0个或任意多个参数
# 调用函数时传入的n个参数会组装成一个n元组赋给args
# 如果一个参数都没有传入,那么args会是一个空元组
def add(*args):
    total = 0
    # 对保存可变参数的元组进行循环遍历
    for val in args:
        # 对参数进行了类型检查(数值型的才能求和)
        if type(val) in (int, float):
            total += val
    return total


# 在调用add函数时可以传入0个或任意多个参数
print(add())         # 0
print(add(1))        # 1
print(add(1, 2, 3))  # 6
print(add(1, 2, 'hello', 3.45, 6))  # 12.45
# 参数列表中的**kwargs可以接收0个或任意多个关键字参数
# 调用函数时传入的关键字参数会组装成一个字典(参数名是字典中的键,参数值是字典中的值)
# 如果一个关键字参数都没有传入,那么kwargs会是一个空字典
def foo(*args, **kwargs):
    print(args)
    print(kwargs)


foo(3, 2.1, True, name='王一帆', age=24, gpa=4.95)

输出:

(3, 2.1, True)
{'name': '王一帆', 'age': 24, 'gpa': 4.95}

用模块管理函数

不管用什么样的编程语言来写代码,给变量、函数起名字都是一个让人头疼的问题,因为我们会遇到命名冲突这种尴尬的情况。

在使用函数的时候,我们通过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!

我们也可以使用from...import...语法从模块中直接导入需要使用的函数

test.py

from module1 import foo

foo()  # hello, world!

from module2 import foo

foo()  # goodbye, world!

如果我们如果从两个不同的模块中导入了同名的函数,后面导入的函数会替换掉之前的导入,就像下面的代码,调用foo会输出goodbye, world!,因为我们先导入了module1foo,后导入了module2foo

想在上面的代码中同时使用来自两个模块的foo函数还是有办法的,还是用as关键字对导入的函数进行别名

from module1 import foo as f1
from module2 import foo as f2

f1()  # hello, world!
f2()  # goodbye, world!

标准库中的模块和函数

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)会返回8pow(2, 0.5)会返回1.4142135623730951

print

打印输出。

range

构造一个范围序列,例如:range(100)会产生099的整数序列。

round

按照指定的精度对数值进行四舍五入,例如:round(1.23456, 4)会返回1.2346

sum

对一个序列中的项从左到右进行求和运算,例如:sum(range(1, 101))会返回5050

type

返回对象的类型,例如:type(10)会返回int;而 type('hello')会返回str

函数是对功能相对独立且会重复使用的代码的封装。学会使用定义和使用函数,就能够写出更为优质的代码。