ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python函数入门:从加法函数到环境配置与工程实践

Python函数入门:从加法函数到环境配置与工程实践 1. 简单加法函数开题为什么“最简示例”反而卡住一大片人1.1 热搜里的python函数学习困惑如果你有心翻一下python相关的搜索热度会发现一个很有意思的现象Python入门教程、python安装教程、函数声明这些词长期占据榜单与此同时还有大量形如“pip : 无法将‘pip’项识别为 cmdlet、函数、脚本文件或可运行程序的名称”、“vscode python环境配置”、“pycharm配置python环境”的报错搜索。也就是说大量的人其实是带着“我已经写完一个加法函数了为什么还是跑不起来”的问题点进教程的。标题里的“Python示例简单加法函数实现”听起来像是三分钟就能解决的问题。但实际上围绕这个简单函数背后牵扯到的环境配置、函数机制、调用约定、工程化组织方式才是真正让人从“能抄代码”升级到“能写代码”的那道坎。我用这个例子带新手入过很多次门也帮不少人排查过环境问题这篇文章就把完整的链路拆开讲一遍从第一行加法代码到def关键字背后的执行机制再到如何把“会写一个函数”变成“能组织一个小项目”。1.2 环境是函数的隐形前置很多新手拿到示例代码的第一个动作是新建一个.py文件把代码敲进去然后双击运行。这一步就把自己坑了。双击运行.py文件在某些平台上的默认行为可能是用文本编辑器打开也可能是用某个不匹配的解释器执行更常见的是根本没装Python直接报错。正确的做法是先确认解释器已经可用。在终端Windows下是CMD或PowerShellmacOS/Linux下是Terminal输入python --version如果能输出版本号例如Python 3.11.5说明解释器装好了。如果提示“python无法识别”先不要急着怀疑人生通常原因有三个一是Python没有真正安装二是安装时没有勾选“Add Python to PATH”三是你当前所在的终端进程是在安装完成之前打开的PATH没有刷新。这里有两个很实用的处理方式优先使用python -m这种写法来调用相关工具。比如安装第三方库时很多人直接输入pip install xxx一旦遇到“pip无法识别”就开始绕远路。其实用python -m pip install xxx可以避免绝大多数PATH问题因为这行命令的意思是“通过python解释器去运行pip模块”只要python本身能被找到pip就一定能被找到。重新安装Python时务必勾选“Add Python to PATH”选项。如果不想重装也可以手动把Python安装目录和Scripts目录加进系统环境变量但操作门槛反而更高不如重装一次来得快。1.3 第一个加法函数的最简形态环境就绪之后用任意编辑器新建一个文件比如add_demo.py写入下面这段代码def add(a, b): return a b print(add(2, 3))在终端执行python add_demo.py看到输出5你的第一个Python函数就算跑通了。这里有一个很关键的区分def add(a, b):只是声明了一个函数它不会在声明那一刻执行函数体真正让加法发生的是最后一行add(2, 3)这个调用表达式以及包裹它的print()函数。很多新手会在这个阶段产生一个疑问既然只是算2加3为什么不直接写print(2 3)问出这个问题其实是一件好事因为答案就是函数存在的意义直接写2 3只能算这一次而定义add函数之后你可以在任何需要加法的地方重复调用传入不同的参数获得不同的结果。这是从“计算”到“可复用的计算能力”的本质转变。2. def关键字的真实拆解加法函数为什么长这样2.1 def行与函数名声明与注册把def add(a, b):这一行拆开来看包含三个关键部分关键字def、函数名add、参数列表(a, b)。Python碰到这一行时会做一件很重要的事在当前作用域里登记一个名字叫add的对象这个名字指向一个函数对象同时记住它接收两个参数。但函数体内部的代码此刻并不会执行。这里可以打个比方def只是在厨房里贴了一张菜谱。你贴菜谱的时候并没有开始做菜只是告诉系统“以后凡是有人点‘add’这道菜就按这个流程做”。真正开始动手做菜是后面调用add(2, 3)、往厨房下单的时候。函数名的命名也有讲究。Python官方的风格建议是使用小写字母多个单词之间用下划线分隔例如add_two_numbers。虽然加法函数用add就够了但在真实项目里calculate_total_price这种名字远比calc、ctp这种缩写可读性好。写函数的人要时刻记住函数名是给别人看的包括未来的自己。2.2 函数体与return计算与结果出口函数体是return a b这一行。return关键字承担两个职责一是结束函数的执行二是把后面的表达式结果传给调用方。如果你去掉return只保留一个a b在函数体里Python计算完加法之后并不会把这个结果保存下来函数会隐式返回None。看一个对比def add_without_return(a, b): a b result add_without_return(2, 3) print(result) # 输出 None这个现象新手很容易踩。写函数的时候自以为“算完就完事了”结果调用方拿到的是None后面所有基于返回值继续运算的逻辑全线崩溃。排查这个问题的方法也很简单在调用函数之后打印一下返回值看看是不是None。如果是优先检查函数体里到底有没有return。还有一个细节return可以出现在函数体的任意位置一旦执行到函数立即终止后面的代码不再执行。这在处理提前返回的场景时非常有用。例如写一个只处理正数的加法def add_positive(a, b): if a 0 or b 0: return 参数不能为负数 return a b第一个return把异常情况挡在前面第二个return是主路径这种写法能显著减少嵌套层数让逻辑更清晰。2.3 调用时发生了什么参数绑定与栈区执行add(2, 3)时Python会做这样几件事把2和3依次绑定到函数定义里的形参a和b上然后为这次调用创建一个独立的命名空间也就是局部作用域在这个空间里执行a b把结果通过return传回外部接着销毁这个局部空间。这就是参数传递最朴素的理解方式函数拿到的是实参的值在函数内部改a和b不会影响外面的变量。再强调一次Python里形参绑定的是对象的引用所以如果传入的是可变对象比如列表函数内部修改这个列表确实会影响到外部。但对于加法函数这种传数字的最简场景按值理解完全够用不会产生偏差。栈区的概念不需要深挖但你需要知道一件事每一次函数调用都会占用一块独立的临时内存区域函数return之后这块区域会被回收。这意味着函数内部的临时变量不会“泄漏”到全局这也是函数能够安全被反复调用的底层原因。2.4 作用域边界函数内部的变量去了哪里看下面这段代码x 10 def add_and_bind(a, b): inner a b return inner print(add_and_bind(2, 3)) # 输出 5 print(x) # 输出 10inner是函数内部创建的变量外部访问不到。如果你试图在函数外打印inner会直接抛出NameError。反过来函数内部可以直接读取外部的全局变量x但如果在函数内部给x赋值情况就变了Python会认为你在创建一个新的局部变量x而不是修改全局的x。新手经常在这里困惑。我建议入门阶段养成一个习惯函数内部只使用参数和自己创建的局部变量不要直接操作全局变量。就拿加法函数来说如果想做一个“在全局总数上加一个数”的功能更稳妥的做法是total 0 def add_to_total(value): global total total value return total虽然这里用了global但说实话global在实际项目中能不用就尽量不用。后面讲到模块化组织时你就能体会到把状态通过参数传入、把结果通过返回值传出才是更容易测试、更容易复用的写法。3. 从新手加法到健壮加法一次函数的真实演化3.1 类型处理加法可能变成拼接return a b这行代码在数字参数下表现完美但一旦调用方传入字符串行为就会变成拼接。add(你好, 世界)得到的是你好世界而不是报错。这种行为有时是特性有时是灾难。入门阶段不需要写一堆防御性代码但你应该意识到Python是动态类型语言函数本身不限制参数类型。如果这个加法函数是给自己内部逻辑用的你可以选择信任调用方如果这个函数未来可能被其他同事调用那最好在函数里把预期说清楚。最简单的加固方式不是写复杂的类型判断而是先想清楚“这个函数到底允不允许字符串相加”。如果只允许数字可以这样def add(a, b): return a b先不做显式转换但配合类型注解和文档说明让调用方明确你的预期。如果你确实希望字符串数字也被正确处理可以在函数内部做转换def add(a, b): return float(a) float(b)这样“2”和3都能算出5.0不会出现字符串拼接的意外。当然这种做法会引入新的问题传进来的如果是无法转换成数字的对象会抛出ValueError。要不要捕获取决于函数的使用场景。3.2 默认参数让调用更简单Python函数支持默认参数即在定义时直接给参数一个初始值。默认参数的形态能让你的加法函数变得更加灵活。例如def add(a, b0): return a b print(add(3)) # 输出 3 print(add(3, 4)) # 输出 7b0让第二个参数变得可选。这种设计在真实项目中非常常见有的函数有大量配置项但绝大多数调用场景只需要用到其中一两个其余参数全部走默认值即可。不过默认参数有一个非常经典的坑我单独拿出来说一下默认参数不要使用可变对象。比如def add_to_list(value, lst[]): lst.append(value) return lst第一次调用add_to_list(1)返回[1]第二次调用add_to_list(2)返回[1, 2]因为默认列表[]在函数定义时就被创建了之后每次调用都在操作同一个列表对象。正确写法是def add_to_list(value, lstNone): if lst is None: lst [] lst.append(value) return lst在加法函数的语境下这个坑出现的概率不高但理解了它你就能避免在真实项目里遇到“为什么两次调用结果互相污染”的灵异问题。3.3 用*args扩展成可变数量加法如果需求从“两个数相加”变成“多个数相加”常规做法是定义很多参数def add(a, b, c, d, ...)这显然不是好方案。Python提供了*args语法用来接收任意数量的位置参数def add(*args): total 0 for num in args: total num return total print(add(1, 2, 3)) # 输出 6 print(add(1, 2, 3, 4, 5)) # 输出 15这时你会发现自己实现的add(*args)本质上是在重新造轮子。Python内置了sum函数print(sum([1, 2, 3, 4, 5])) # 输出 15所以真正实际工作里如果只是想把一个可迭代对象里的数字全部加起来优先用sum。但*args这个写法本身还是要理解它是理解装饰器、函数式编程里*解包的前置知识。另外提醒一点*args接到的参数在函数内部是一个元组你无法在函数内直接修改它。如果需要对参数做排序、过滤等操作先转换成列表。3.4 类型注解与文档字符串代码即文档前面说了这加法函数不强制类型但我们可以让代码自己“说话”。类型注解是Python 3.5以后引入的语法它不会影响运行主要是给人看也给IDE的静态检查工具看def add(a: int, b: int) - int: 返回两个整数的和。 Args: a: 第一个加数 b: 第二个加数 Returns: 两数之和 return a b加上类型注解以后在VS Code里悬停到add函数上会直接看到参数预期类型使用mypy这类工具也可以在编译前就发现传入字符串参数的问题。文档字符串则可以用help(add)在交互环境里查看到对团队协作尤其友好。不过类型注解不是万能的。它只是“说明”不是“断言”。调用方传入字符串时类型注解不会阻止运行加法该拼接还是会拼接。如果你需要真正的运行时校验可以搭配isinstance写断言def add(a: int, b: int) - int: assert isinstance(a, int) and isinstance(b, int), add函数仅支持整数参数 return a b断言在调试阶段很有用但正式环境如果启用了Python的优化模式-O参数assert会被消除。工程实践上更稳妥的做法是用异常处理来强制校验这点在真实项目中再展开。4. 加法函数如何走向真正项目封装、测试与复用4.1 把加法函数放进模块文件写一个函数很简单但如何组织函数才是真正拉开差距的地方。假设你的加法函数未来会在多个脚本里被用到把它放在一个独立模块文件里是一个最基础的工程化动作。创建一个my_math.py文件个人练习用的数学函数集合 def add(a: int, b: int) - int: 返回两个整数的和 return a b再创建一个main.py文件from my_math import add result add(10, 20) print(result)这样main.py只负责业务流程my_math.py只负责具体运算。将来如果你要修改加法逻辑只需要改一处。如果你又写了减法、乘法函数也可以都往my_math.py里放当这个文件变得越来越大时再考虑把相关函数拆成多个模块、甚至一个包。模块导入这块有新手会踩坑如果你在my_math.py里也写了测试代码比如print(add(1, 2))那么在main.py里导入时会把这行测试代码也执行一遍。正确做法是把测试代码放进if __name__ __main__:保护块里if __name__ __main__: print(add(1, 2))这个写法的意思是只有当my_math.py作为主程序直接运行时测试代码才执行被别的模块导入时这一段直接跳过。4.2 写第一个单元测试保护加法函数写单元测试是很多自学者跳过的一步也是我觉得少有的“越早接触越受益”的好习惯。就拿add函数来说肉眼确认它算对当然很简单但一旦函数逻辑变复杂回归测试的价值就会显现出来。用一个最轻量的方式不需要装任何测试框架Python自带的unittest就能承担这个任务。在test_my_math.py里写上import unittest from my_math import add class TestAdd(unittest.TestCase): def test_add_two_numbers(self): self.assertEqual(add(2, 3), 5) def test_add_negative_and_positive(self): self.assertEqual(add(-1, 1), 0) def test_add_returns_integer(self): self.assertIsInstance(add(2, 3), int) if __name__ __main__: unittest.main()然后在终端运行python -m unittest test_my_math.py看到OK就说明所有用例都通过了。这里我想特别解释一下为什么用python -m unittest而不是直接python test_my_math.py前者能让测试发现机制正常工作也符合项目里多个测试文件统一组织的习惯。测试用例的命名也建议遵循test_xxx格式这样可以被unittest自动用例发现机制收录。以后每次改动加法函数只要跑一遍测试就能马上知道有没有改坏原有的行为。4.3 从add到sum理解内置函数与标准库边界一个自然的问题是加两个数用add函数加一堆数用sum内置函数那我到底为什么要自己写函数答案很简单你写函数本质上是在为“领域逻辑”命名把一段操作抽象成一个有语义的单元。加法只是一个教学载体真实项目里你需要自定义的函数往往是类似calculate_order_total、format_user_name这种带有业务含义的操作。理解这个边界有一个实际好处当你准备写一个函数之前先花十秒钟想一想Python标准库和内置函数里是不是已经有现成实现。我见过不少人用几十行代码自己实现日期时间格式化结果就是用datetime库三行能搞定的事。建议没事翻一翻官方文档里的内置函数列表至少眼熟那些高频名字sum、len、max、min、sorted、map、filter等等。当然用内置函数不代表否定自定义函数。恰恰相反优秀代码往往是“自定义函数 内置函数”的组合。你把业务逻辑拆成有名字的函数在每个函数内部尽量复用标准库能力整体可读性和健壮性都会大幅上升。4.4 小函数所支撑的软件工程起点从一个简简单单的两数相加函数开始你会逐渐接触到“函数应该长什么样”这个工程话题。以我的经验来看让函数保持小而专注是最值得坚持的原则一个函数只做一件事参数尽量少返回值清晰。如果你发现一个函数里既要做加法又要打印日志又要修改全局变量又要格式化输出那这个函数已经超载了未来大概率会是维护痛点。拆成多个职责单一的函数每个函数都能单独测试组合起来又能覆盖完整业务这条路走起来一开始会觉得代码变多了但项目变大之后你会庆幸当初没把逻辑都堆在一个大函数里。以加法函数为例如果你需要“带日志的加法”可以拆成两个函数或者用装饰器来叠加日志逻辑而不是把打印语句塞进add里。装饰器的概念超出本文范围了但知道“函数可以被拆、可以被组合、可以被装饰”你就能在阅读高质量开源代码时更快找到感觉。我自己的习惯是每写一个函数先问自己三个问题这个函数的输入是什么输出是什么有没有可能让它更短如果三个问题的答案都清晰干脆说明这个函数大概率是健康的。加法函数虽然简单但它是这套思维方式的完美起点这也是为什么我坚持认为讨论“简单加法函数”绝不是在讨论一条O(1)的代码而是在讨论编程入门阶段最值得建立的良好直觉。
返回列表