资讯动态

Python 函数与模块:从组合数问题到标准库复用(Python-100-Days 第14课)

发布时间:2026/10/15 6:23:27 来源:尧图企业网站定制
教程文档【免费下载链接】Python-100-DaysPython - 100天从新手到大师项目地址https://gitcode.com/capable-ai/Python-100-Days点击查看免费下载导读本文基于 Python-100-Days 项目第 14 天的《函数和模块》一课以一道组合数计算问题为切入点系统讲解 Python 函数定义、参数体系位置参数、关键字参数、默认值、可变参数与模块管理函数的完整知识并对照仓库源码展示如何用函数消除重复代码、用import机制解决命名冲突以及如何善用标准库与内置函数写出高质量的代码。读完本文你将掌握从重复代码重构到模块化组织代码的完整实战路径。从一道数学题说起重复代码是最坏的坏味道课程开篇提出了一个经典的组合数学问题方程 $x_{1} x_{2} x_{3} x_{4} 8$ 有多少组正整数解这个问题等价于将 8 个苹果分成四组且每组至少一个苹果有多少种方案也等价于在分隔 8 个苹果的 7 个间隙之间放入三个隔断将苹果分成四组有多少种方案。因此答案是组合数 $\small{C_{7}^{3} 35}$。组合数的计算公式为$$ C_m^n \frac {m!} {n!(m-n)!} $$按照此前学过的循环与累乘知识可以写出如下代码分别用循环累乘计算出 $m!$、$n!$ 和 $(m-n)!$再做除法得到组合数 输入m和n计算组合数C(m,n)的值 Version: 1.0 Author: 骆昊 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的阶乘 fk 1 for num in range(1, m - n 1): fk * num # 计算C(M,N)的值 print(fm // fn // fk)输入m 7、n 3时程序输出35。这段代码虽然能正确运行却隐藏着一个严重的问题它做了三次求阶乘的操作虽然 $m$、$n$、$m-n$ 的值各不相同但三段累乘代码在结构上没有任何实质性区别属于典型的重复代码。世界级的编程大师 Martin Fowler 曾说过代码有很多种坏味道重复是最坏的一种要写出高质量的代码首先就要解决重复代码的问题——将求阶乘的功能封装到一个称为函数的代码块中在需要计算阶乘的地方只需调用函数即可复用。定义函数用 def 关键字完成封装数学上的函数通常形如 $y f(x)$ 或 $z g(x, y)$$f$、$g$ 是函数名$x$、$y$ 是自变量$y$、$z$ 是因变量。Python 中的函数结构与之一致每个函数都有自己的名字、自变量和因变量——在 Python 中函数的自变量被称为参数因变量被称为返回值。使用def关键字可以定义函数函数名的命名规则与变量名一致不能使用 Python 关键字、不能以数字开头、建议使用有意义的英文单词等。在函数名后面的圆括号中可以设置函数的参数自变量函数执行完成后通过return关键字返回执行结果因变量。需要注意几点如果函数中没有return语句函数会返回代表空值的None函数也可以没有参数但函数名后面的圆括号是必须有的函数体要执行的代码通过代码缩进的方式放在函数定义行之后与之前分支和循环结构的代码块规则一致。图中展示了 Python 函数定义的完整语法结构def关键字、函数名、圆括号中的参数列表、冒号以及缩进排列的函数体。下面将求阶乘的操作封装为函数对上面的代码进行重构。所谓重构是在不影响代码执行结果的前提下对代码的结构进行调整 输入m和n计算组合数C(m,n)的值 Version: 1.1 Author: 骆昊 # 通过关键字def定义求阶乘的函数 # 自变量参数num是一个非负整数 # 因变量返回值是num的阶乘 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))对比两个版本可以发现重构后的代码更简单优雅更重要的是fac函数还可以在其他任何需要求阶乘的代码中重复使用。使用函数可以帮助我们将功能上相对独立且会被重复使用的代码封装起来通过调用函数实现对既有代码的复用而不是把重复的代码再编写一遍。仓库源码印证本仓库 Day31-35/code/example03.py 中同样定义了一个fac(num)函数但它采用了递归写法并用assert num 0对参数做了约束num为 0 或 1 时直接返回 1否则返回num * fac(num - 1)def fac(num): 求阶乘 assert num 0 if num in (0, 1): return 1 return num * fac(num - 1)从源码结构看递归版本的fac与本文的循环版本殊途同归都是对求阶乘这一相对独立功能的封装读者可以在掌握基本定义后自行体会两种实现的差异。不重复发明轮子直接使用标准库的 factorial事实上Python 标准库的math模块中已经内置了一个名为factorial的函数实现了求阶乘的功能。可以用import math导入math模块然后通过math.factorial调用也可以通过from math import factorial直接导入该函数 输入m和n计算组合数C(m,n)的值 Version: 1.2 Author: 骆昊 from math import factorial m int(input(m )) n int(input(n )) print(factorial(m) // factorial(n) // factorial(m - n))如果觉得factorial名字太长、书写不便还可以在导入时通过as关键字为其设置别名调用时使用别名即可 输入m和n计算组合数C(m,n)的值 Version: 1.3 Author: 骆昊 from math import factorial as f m int(input(m )) n int(input(n )) print(f(m) // f(n) // f(m - n))仓库源码印证本仓库 Day31-35/code/example01.py 在分析算法时间复杂度时正是采用了from math import log2, factorial的导入方式随后直接使用factorial(y)计算阶乘序列用于绘制算法复杂度对比曲线example01.py。这验证了导入即用、不重复发明轮子正是实际工程中的常见做法。将来我们使用的函数要么是自定义函数要么是 Python 标准库或三方库提供的函数如果已有现成的可用函数就没有必要自己定义重复发明轮子是一件非常糟糕的事情。函数的参数体系从位置参数到可变参数位置参数与关键字参数以判断三条边的长度能否构成三角形为例可以定义如下函数def make_judgement(a, b, c): 判断三条边的长度能否构成三角形 return a b c and b c a and a c bmake_judgement的三个参数属于位置参数调用时通常按照从左到右的顺序依次传入且传入参数的数量必须与定义时的参数数量相同print(make_judgement(1, 2, 3)) # False print(make_judgement(4, 5, 6)) # True如果不想按照从左到右的顺序给出参数值也可以使用关键字参数通过参数名参数值的形式传参print(make_judgement(b2, c3, a1)) # False print(make_judgement(c6, b4, a5)) # True强制位置参数与命名关键字参数在定义函数时可以在参数列表中用/设置强制位置参数positional-only arguments用*设置命名关键字参数。强制位置参数只能按参数位置接收参数值命名关键字参数只能通过参数名参数值的方式传递。示例如下# /前面的参数是强制位置参数 def make_judgement(a, b, c, /): 判断三条边的长度能否构成三角形 return a b c and b c a and a c b # 下面的代码会产生TypeError错误错误信息提示强制位置参数是不允许给出参数名的 # TypeError: make_judgement() got some positional-only arguments passed as keyword arguments # print(make_judgement(b2, c3, a1))说明强制位置参数是 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 允许函数的参数拥有默认值。以第 07 课分支和循环结构实战中的 CRAPS 赌博游戏为例可以将摇色子获得点数的功能封装为函数from random import randrange # 定义摇色子的函数 # 函数的自变量参数n表示色子的个数默认值为2 # 函数的因变量返回值表示摇n颗色子得到的点数 def roll_dice(n2): total 0 for _ in range(n): total randrange(1, 7) return total # 如果没有指定参数那么n使用默认值2表示摇两颗色子 print(roll_dice()) # 传入参数3变量n被赋值为3表示摇三颗色子获得点数 print(roll_dice(3))再看一个更简单的例子def add(a0, b0, c0): 三个数相加求和 return a b c # 调用add函数没有传入参数那么a、b、c都使用默认值0 print(add()) # 0 # 调用add函数传入一个参数该参数赋值给变量a, 变量b和c使用默认值0 print(add(1)) # 1 # 调用add函数传入两个参数分别赋值给变量a和b变量c使用默认值0 print(add(1, 2)) # 3 # 调用add函数传入三个参数分别赋值给a、b、c三个变量 print(add(1, 2, 3)) # 6需要注意带默认值的参数必须放在不带默认值的参数之后否则会产生SyntaxError错误错误消息为non-default argument follows default argument没有默认值的参数放在了带默认值的参数后面。可变参数*args 与 **kwargsPython 语言中可以通过星号表达式语法让函数支持可变参数即调用函数时可以向函数传入 0 个或任意多个参数。在团队协作开发商业项目时设计供他人使用的函数往往无法预知调用者会传入多少个参数此时可变参数就能派上用场。下面的代码演示了用可变位置参数实现对任意多个数求和的add函数——调用时传入的参数会保存到一个元组中# 用星号表达式来表示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传入的关键字参数会被组装到一个字典中参数名是键参数值是值# 参数列表中的**kwargs可以接收0个或任意多个关键字参数 # 调用函数时传入的关键字参数会组装成一个字典参数名是字典中的键参数值是字典中的值 # 如果一个关键字参数都没有传入那么kwargs会是一个空字典 def foo(*args, **kwargs): print(args) print(kwargs) foo(3, 2.1, True, name骆昊, age43, gpa4.95)输出结果为(3, 2.1, True) {name: 骆昊, age: 43, gpa: 4.95}可见*args收集位置参数为元组、**kwargs收集关键字参数为字典二者可以同时出现这也解释了为什么 Python 生态中大量框架函数都以def func(*args, **kwargs)的形式定义——它兼容了任意调用方式。用模块管理函数解决命名冲突的关键手段不管用何种编程语言给变量、函数起名字都是一个让人头疼的问题因为会遇到命名冲突的尴尬情况。最简单的场景是在同一个.py文件中定义两个同名函数def foo(): print(hello, world!) def foo(): print(goodbye, world!) foo() # 大家猜猜调用foo函数会输出什么在单个文件中这种情况很容易避免但如果是团队协作、多人开发多个程序员都定义了名为foo的函数时该如何区分呢答案其实很简单Python 中每个文件就代表了一个模块module不同的模块中可以有同名的函数使用函数时通过import关键字导入指定模块再使用完全限定名模块名.函数名的方式调用即可区分不同模块中的同名函数。module1.pydef foo(): print(hello, world!)module2.pydef foo(): print(goodbye, world!)test.pyimport module1 import module2 # 用模块名.函数名的方式完全限定名调用函数 module1.foo() # hello, world! module2.foo() # goodbye, world!导入模块时同样可以使用as关键字为模块设置别名从而使用更简短的完全限定名test.pyimport module1 as m1 import module2 as m2 m1.foo() # hello, world! m2.foo() # goodbye, world!上面两段代码导入的是定义函数的模块也可以使用from...import...语法从模块中直接导入需要使用的函数test.pyfrom module1 import foo foo() # hello, world! from module2 import foo foo() # goodbye, world!但这里有一个陷阱如果从两个不同的模块导入了同名的函数后导入的函数会替换掉之前的导入。下面代码中调用foo会输出goodbye, world!因为先导入了module1的foo后导入了module2的foo如果两个from...import...反过来写结果就会相反test.pyfrom module1 import foo from module2 import foo foo() # goodbye, world!若想同时使用来自两个模块的同名函数办法依然是使用as关键字对导入的函数进行别名test.pyfrom module1 import foo as f1 from module2 import foo as f2 f1() # hello, world! f2() # goodbye, world!仓库源码印证模块化的导入函数并复用正是本仓库代码的组织方式。在 Day31-35/code/test_example01.py 中测试代码通过from example01 import seq_search, bin_search从example01模块导入查找函数再编写继承TestCase的测试用例对函数进行验证而 Day31-35/code/example01.py 本身就是以每个文件即一个模块的方式组织多个函数seq_search、bin_search、main这正是文档所述用模块管理函数思想的直接体现。标准库中的模块与内置函数Python 标准库提供了大量的模块和函数来简化开发工作random模块提供生成随机数和随机抽样的函数time模块提供与时间操作相关的函数math模块则包含计算正弦、余弦、指数、对数等一系列数学函数。随着学习的深入还会接触到更多模块和函数。标准库中还有一类不需要import就能直接使用的函数称之为内置函数built-in functions。下表列出了其中一部分常用内置函数函数说明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)会产生0到99的整数序列。round按照指定的精度对数值进行四舍五入例如round(1.23456, 4)会返回1.2346。sum对一个序列中的项从左到右进行求和运算例如sum(range(1, 101))会返回5050。type返回对象的类型例如type(10)会返回int而type(hello)会返回str。仓库源码印证从 Day31-35/code/example01.py 可以看到实际项目中对标准库的密集使用——from math import log2, factorial导入数学函数、from matplotlib import pyplot导入三方库、import numpy导入数值计算库三者混用互不冲突正是标准库模块 三方库模块 自定义模块协同工作的典型示例而 Day31-35/code/example11.py 则进一步展示了函数作用域相关的global、nonlocal关键字与 LEGBLocal → Enclosing → Global → Built-in变量查找顺序帮助读者理解函数内部变量与全局变量之间的边界。总结函数是对功能相对独立且会重复使用的代码的封装。学会定义和使用函数就能写出更为优质的代码。同时Python 标准库已经提供了大量的模块和常用函数用好这些模块和函数就能用更少的代码做更多的事情如果现有模块和函数无法满足需求则自定义函数再通过模块的概念来管理这些自定义函数。本课的实战建议可以归纳为三步第一步识别代码中的重复片段将其提炼为函数如fac第二步检查标准库是否已有现成实现如math.factorial避免重复发明轮子第三步将相关函数放入独立模块通过import、完全限定名或别名解决命名冲突为后续多文件、多人协作的项目开发打下基础。更深入的函数高级用法如递归、高阶函数、装饰器等可继续学习 Day01-20/17.函数高级应用.md 一课。赞分享教程文档【免费下载链接】Python-100-DaysPython - 100天从新手到大师项目地址https://gitcode.com/capable-ai/Python-100-Days点击查看免费下载相关推荐Python变量详解Python-100-Days第3课带你从数据载体到类型转换Python变量详解Python 100 Days第3课带你从数据载体到类型转换 本文是 Python 100 Days 系列课程的第 3 课围绕文档教程Python-100-Days 系列NumPy 数组运算、通用函数与广播机制深入解析基于 Day66-80 第 70 篇Python 100 Days 系列NumPy 数组运算、通用函数与广播机制深入解析基于 Day66 80 第 70 篇 本篇文章围绕《Python 10文档教程Python-100-Days 第一课从零运行你的第一个 Python 程序环境选择与 Hello World 实战Python 100 Days 第一课从零运行你的第一个 Python 程序环境选择与 Hello World 实战 本文是 Python 100 Day教程文档上一篇ISO 8583 支付报文处理中间件jPOS下一篇完整教程Android 手机一键提取 APK 文件快速完成应用备份创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑