例子1:随机验证码
设计一个生成随机验证码的函数,验证码由数字和英文大小写字母构成,长度可以通过参数设置。
import random import string ALL_CHARS = string.digits + string.ascii_letters def generate_code(*, code_len=4): """ 生成指定长度的验证码 :param code_len: 验证码的长度(默认4个字符) :return: 由大小写英文字母和数字构成的随机验证码字符串 """ return ''.join(random.choices(ALL_CHARS, k=code_len))
说明1:string模块的digits代表0到9的数字构成的字符串'0123456789',string模块的ascii_letters代表大小写英文字母构成的字符串'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'。
说明2:random模块的sample和choices函数都可以实现随机抽样,sample实现无放回抽样,这意味着抽样取出的元素是不重复的;choices实现有放回抽样,这意味着可能会重复选中某些元素。这两个函数的第一个参数代表抽样的总体,而参数k代表样本容量,需要说明的是choices函数的参数k是一个命名关键字参数,在传参时必须指定参数名。
可以用下面的代码生成5组随机验证码来测试上面的函数。
for _ in range(5): print(generate_code())
输出:
59tZ QKU5 izq8 IBBb jIfX
或者
for _ in range(5): print(generate_code(code_len=6))
输出:
FxJucw HS4H9G 0yyXfz x7fohf ReO22w
说明:我们设计的generate_code函数的参数是命名关键字参数,由于它有默认值,可以不给它传值,使用默认值4。如果需要给函数传入参数,必须指定参数名code_len。
例子2:判断素数
设计一个判断给定的大于1的正整数是不是质数的函数。质数是只能被1和自身整除的正整数(大于1),如果一个大于 1 的正整数 N 是质数,那就意味着在 2 到 N−1 之间都没有它的因子。
def is_prime(num: int) -> bool: """ 判断一个正整数是不是质数 :param num: 大于1的正整数 :return: 如果num是质数返回True,否则返回False """ for i in range(2, int(num ** 0.5) + 1): if num % i == 0: return False return True
说明1:上面is_prime函数的参数num后面的: int用来标注参数的类型,虽然它对代码的执行结果不产生任何影响,但是很好的增强了代码的可读性。同理,参数列表后面的-> bool用来标注函数返回值的类型,它也不会对代码的执行结果产生影响,但是却让我们清楚的知道,调用函数会得到一个布尔值,要么是True,要么是False。
说明2:上面的循环并不需要从 2 循环到 N−1 ,因为如果循环进行到 √N 时,还没有找到$\small{N}$的因子,那么 √N 之后也不会出现 N 的因子,大家可以自己想一想这是为什么。
例子3:最大公约数和最小公倍数
设计计算两个正整数最大公约数和最小公倍数的函数。 x 和 y 的最大公约数是能够同时整除 x 和 y 的最大整数,如果 x 和 y 互质,那么它们的最大公约数为 1; x 和 y 的最小公倍数是能够同时被 x 和 y 整除的最小正整数,如果 x 和 y 互质,那么它们的最小公倍数为 x*y 。需要提醒大家注意的是,计算最大公约数和最小公倍数是两个不同的功能,应该设计成两个函数,而不是把两个功能放到同一个函数中。
def lcm(x: int, y: int) -> int: """求最小公倍数""" return x * y // gcd(x, y) def gcd(x: int, y: int) -> int: """求最大公约数""" while y % x != 0: x, y = y % x, x return x
说明:函数之间可以相互调用,上面求最小公倍数的lcm函数调用了求最大公约数的gcd函数,通过 x*y/gcd(x,y) 来计算最小公倍数。
例子4:双色球随机选号
我们用函数重构之前讲过的双色球随机选号的例子(《第09课:常用数据结构之列表-2》),将生成随机号码和输出一组号码的功能分别封装到两个函数中,然后通过调用函数实现机选N注号码的功能。
""" 双色球随机选号程序 """ import random RED_BALLS = [i for i in range(1, 34)] BLUE_BALLS = [i for i in range(1, 17)] def choose(): """ 生成一组随机号码 :return: 保存随机号码的列表 """ selected_balls = random.sample(RED_BALLS, 6) selected_balls.sort() selected_balls.append(random.choice(BLUE_BALLS)) return selected_balls def display(balls): """ 格式输出一组号码 :param balls: 保存随机号码的列表 """ for ball in balls[:-1]: print(f'\033[031m{ball:0>2d}\033[0m', end=' ') print(f'\033[034m{balls[-1]:0>2d}\033[0m') n = int(input('生成几注号码: ')) for _ in range(n): display(choose())
说明:大家看看display(choose())这行代码,这里我们先通过choose函数获得一组随机号码,然后把choose函数的返回值作为display函数的参数,通过display函数将选中的随机号码显示出来。重构之后的代码逻辑非常清晰,代码的可读性更强了。如果有人为你封装了这两个函数,你仅仅是函数的调用者,其实你根本不用关心choose函数和display函数的内部实现,你只需要知道调用choose函数可以生成一组随机号码,而调用display函数传入一个列表,就可以输出这组号码。将来我们使用各种各样的 Python 三方库时,我们也根本不关注它们的底层实现,我们需要知道的仅仅是调用哪个函数可以解决问题。
总结
在写代码尤其是开发商业项目的时候,一定要有意识的将相对独立且重复使用的功能封装成函数,这样不管是自己还是团队的其他成员都可以通过调用函数的方式来使用这些功能,减少工作中那些重复且乏味的劳动。