ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python py03进阶:函数参数、作用域与模块导入笔记

Python py03进阶:函数参数、作用域与模块导入笔记 最近整理自己的学习笔记翻出一个标着py03的文件夹里面是函数、模块、作用域相关的练习记录。如果你也在学Python或者正在某套课程里按编号往下走看到py03应该不陌生这是很多系列课程的第三阶段py01通常是环境搭建和基础语法py02是数据类型和流程控制到py03就切到了函数与模块化编程。这个阶段要解决的核心问题不是再记几个语法点而是开始学习“怎么组织代码”。这篇笔记就围绕py03阶段展开内容包括课程设计思路、函数参数的坑、作用域规则、模块导入的常用姿势以及我写成绩统计工具时踩过的报错和排查方法。适合刚啃完循环和列表、准备往进阶走的朋友也适合正在补函数这块知识缺口的学习者。1. py03这个阶段到底在训练什么1.1 课程编号里的隐性路径从语法到工程思维先说课程编号。我在不少Python课里看到的套路是py01/py02/py03这样的序号含义大同小异py01解决环境问题和基础输出py02把列表、字典、元组、条件分支、for/while循环这些“建筑材料”过一遍py03则开始讲函数、模块、作用域和异常处理。这其实是一条很清楚的成长路径——先有砖块再学砌墙最后才是盖房子。py03正好处在“砌墙”的位置。你发现没有到了py03学习难度突然上了一个台阶。不是因为函数本身难而是课程开始要求你同时控制多个变量、多个函数之间的数据传递甚至是多个文件。很多人在前两个阶段跟着教程抄代码觉得很顺到函数这块突然卡住原因就在这里学习目标变了从“把语法写对”变成了“把逻辑设计对”。这个阶段如果你能迈过去后面学面向对象、学网络请求、学Web框架都会轻松很多。因为那些内容本质上都是在更高维度上做“组织代码”这件事。函数的调用关系、参数的传值时机、模块之间的依赖这些基本功不扎实后面排错会非常痛苦。所以千万别觉得py03不过就是学个def这一章的厚度其实远超字面意思。1.2 为什么函数和模块总是被放在同一个阶段这是我复盘课程设计时想明白的一个点。函数解决的是“对一段逻辑的复用”模块解决的是“对一组逻辑的复用”。两者天然是连续的你先把一个功能写进函数再把几个相关的函数放进同一个.py文件这个文件就成了一个模块。所以课程把这两块内容放在同一个编号阶段逻辑上是顺的。放在一起还有一个好处可以尽早培养“按职责拆代码”的习惯。我见过不少自学了很久的人所有代码都堆在一个脚本里从头到尾几百行中间反复用CtrlF找变量改一处错了三处。这根本不是编码能力的问题是缺少模块化训练。py03阶段的函数加模块组合其实就是在强制你养成拆分的习惯先定义一个函数处理单一任务再通过import把它组装进更大的程序。如果你是在自学而不是上课我强烈建议别跳着学。哪怕你已经会用def写个简单函数也把py03阶段的练习完整过一遍。这个阶段看似简单但里面埋着默认参数、作用域、命名空间这些细节跳过的话后面会在莫名其妙的地方翻车。我见过太多人学到装饰器时彻底蒙圈回头一查根子就是函数作用域没吃透。2. 核心知识点函数参数、作用域与模块导入2.1 函数参数的五种形态以及最容易踩的默认参数坑先说说参数的形态。基础阶段你会接触到位置参数就是按顺序传进去的那种def greet(name, message): return f{name}, {message}然后是默认参数调用时可以省略def greet(name, message你好): return f{name}, {message}这里有一个经典的大坑默认值不要用可变对象。举个例子def add_item(item, container[]): container.append(item) return container第一次调用add_item(a)看起来很正常返回[a]。第二次再调用add_item(b)结果变成了[a, b]而不是[b]。原因是在Python里默认参数在函数定义的时候就被求值并且只创建一次后续调用共享的是同一个列表对象。正确的写法是def add_item(item, containerNone): if container is None: container [] container.append(item) return container这种“默认参数陷阱”是py03阶段几乎必考的一个细节值得专门记一笔。顺带说一句如果你想让多个调用方各自维护独立的容器这个写法就是标准答案千万别嫌多两行代码。接着是*args和**kwargs。它们允许函数接收任意数量的位置参数或关键字参数常用于写装饰器、封装接口或者做参数透传。核心规则是args是元组kwargs是字典且位置参数必须放在它们前面def log(level, *args, **kwargs): print(level, args, kwargs) log(INFO, 1, 2, 3, usertom)输出会是INFO (1, 2, 3) {user: tom}。你需要注意*args和**kwargs只是约定俗成的名字真正起作用的星星号语法。Python 3 还支持仅限关键字参数写法是在*args后面直接写参数名比如def func(a, *, b):这样b只能以关键字形式传参能有效防止调用顺序错乱。还有一个容易被忽略的小知识函数return可以返回多个值实际返回的是一个元组。比如def get_min_max(nums): return min(nums), max(nums)调用后用min_value, max_value get_min_max(scores)做解包这是py03阶段很常用的操作。记住这个细节后面学列表推导、学解包语法时会顺畅很多。2.2 作用域与命名空间LEGB规则到底在说什么作用域是py03阶段最容易让人头晕的部分。Python查找一个名字的时候遵循的是LEGB顺序Local当前函数内部的局部作用域、Enclosing外层嵌套函数的作用域、Global模块级别的全局作用域、Built-inPython内置名字。这个顺序决定了同名变量谁会生效。你可以把它理解成找东西的流程先在当前房间找找不到就打开门去外面客厅找再找不到就去小区门口问保安最后才去翻城市公共目录。公共目录就是Built-in那一层。Python内置的len、print都在这一层所以你能直接调用。一个很常见的问题是在函数内部给全局变量赋值看上去没报错但函数执行完全局变量还是原来的值。比如count 0 def increase(): count 1 increase() print(count) # 输出 0原因在于函数内部的count 1创建了一个新的局部变量和全局的count不是同一个。如果你确实想修改全局变量必须显式声明global count。py03的练习里很多同学在这里翻车最后发现是对名字查找规则理解不到位。更进阶的是nonlocal它作用在外层嵌套函数的局部作用域里。典型场景是闭包里的计数器def make_counter(): times 0 def count(): nonlocal times times 1 return times return count如果不写nonlocal内层函数对times的赋值同样会被当成局部变量运行时直接抛UnboundLocalError。这个报错很经典后面我会专门讲排查方法。理解了LEGB你基本就可以预判绝大多数“变量不按预期变化”的问题了。顺便提一句闭包这个概念在py03只做了解性要求但你把nonlocal玩明白了后面写装饰器会特别顺手。2.3 模块导入的姿势import、from import 与name机制模块化编程的第一步是把函数存进一个.py文件然后在另一个文件里导入。最常见的三种写法import statistics # 引入整个模块用 statistics.mean(...) 访问 from statistics import mean # 只引入名字直接用 mean(...) import statistics as st # 起别名减少输入三者没有绝对优劣但要遵守一条原则能限定就限定。如果只用到一两个函数from import更清晰如果模块名太长或者容易冲突as别名更顺手。无脑import整个模块的问题在于一旦项目变大命名空间里全是模块名反而看不清代码逻辑。我建议在py03阶段就养成在脚本里加__name__守卫的习惯def main(): # 主逻辑 pass if __name__ __main__: main()当你直接运行这个脚本时__name__的值是__main__main()会执行当它被其他脚本import的时候__name__是模块名main()不会自动执行。这个机制防止了“导入模块时误触发一堆代码”的问题是模块化编程的第一道保险。你可以在py03阶段就要求自己任何一个独立可运行脚本都写上这个守卫养成肌肉记忆。还有一个容易忽略的点不要循环导入。也就是a.py导入了b.pyb.py又回头导入a.py。一旦出现这种结构Python在加载过程中会因为名字还没定义而报ImportError。解决办法是重新设计模块依赖让依赖方向变成单向的或者把相互引用的代码移到第三个模块里。另外如果你遇到ModuleNotFoundError: No module named xxx可以先检查三件事模块文件所在目录是否在sys.path里、文件名是否拼错、以及模块名是否和标准库重名。py03阶段最常踩的就是自己写了random.py然后在另外的脚本里import random结果导入的根本不是标准库那个random。3. 实操过程用一套练习把知识点串起来3.1 写一个成绩统计模块从函数到模块完整走一遍这里我用自己的笔记记录来演示一个可复现的练习。目标是做一个成绩统计小工具输入一组分数输出平均分、最高分、最低分并且支持按条件筛选。第一步新建一个stats.py先写一个安全的平均值函数def mean(nums): if not nums: raise ValueError(分数列表不能为空) return sum(nums) / len(nums)注意我在函数入口加了空列表检查这是py03阶段该有的意识函数不能只对“正常输入”负责还要考虑异常情况。第二步写筛选函数比如过滤掉低于60分的成绩并计算剩余成绩的平均分def mean_above(nums, threshold60): filtered [n for n in nums if n threshold] return mean(filtered)这里顺便就用上了默认参数。第三步加一个排序需求用lambda表达式按自定义规则排序。假设分数存放在字典列表里每条记录是{name: 张三, score: 88}要求按成绩降序输出def sort_by_score(records): return sorted(records, keylambda r: r[score], reverseTrue)lambda在这里的好处是不用单独def一个命名函数逻辑短小的时候可读性反而更高。第四步把前面这些函数集中到一个模块里在另一个脚本里导入使用。调用方只需要from stats import mean, mean_above, sort_by_score scores [55, 78, 90, 63, 42] print(mean(scores)) # 65.6 print(mean_above(scores)) # 77.0 records [{name: 张三, score: 88}, {name: 李四, score: 61}] print(sort_by_score(records))输出会是[{name: 张三, score: 88}, {name: 李四, score: 61}]排序生效。这个练习的妙处在于它把函数定义、参数默认值、列表推导、lambda、模块导入全部串在了同一个真实场景里。你写一遍之后对py03阶段的知识点会有一个整体感受原来每个零散概念在项目里是这样配合工作的。我甚至可以再加一句这个成绩模块在后面学文件读写、学Pandas时还能继续扩展前期实现得越规范后面挂新功能越省事。3.2 真实脚本里怎么组织模块结构练习完上面的小模块再进一步看看真实项目的组织方式。我习惯把代码拆成三层输入层、计算层、输出层。计算层就是各种纯粹的运算函数输入层负责获取数据输出层负责展示结果。这样做的好处是以后想换数据来源或者改成命令行输出、文件输出都只动对应层不会牵连计算逻辑。说白了就是把“做什么”和“怎么做”分开把“数据怎么来”和“数据怎么用”分开。一个常见的初学者习惯是把所有功能全部写在main()函数里几十个操作顺序执行中间穿插print调试。这在练习阶段可以但程序一旦超过一两百行改起来就非常痛苦。更好的做法是main()只负责编排def main(): raw_data load_data() result analyze(raw_data) show_result(result) if __name__ __main__: main()每个步骤对应的函数单独放在自己的区域或模块里职责清晰。你在py03阶段就可以按这个思路去练哪怕只是几十行的小脚本也用这种“入口-处理-输出”的结构去搭。我以前写练习代码时喜欢全都堆在一个文件里觉得拆开麻烦后来有一次需要复用统计函数发现要从几百行里把函数抠出来特别费劲从那以后就老老实实拆文件了。养成习惯之后再去学类、学框架会觉得代码的组织是水到渠成的。4. 常见问题与排查技巧实录4.1 初学函数阶段最常踩的四个报错我整理了一份py03阶段出现频率最高的报错对照表基本都是我当年记在笔记里的真实案例报错信息触发场景解决思路NameError: name xxx is not defined拼写错误或变量在函数外定义检查名字拼写确认变量是否在可访问的作用域内TypeError: missing 1 required positional argument调用函数时少传了参数检查函数定义确认每个必填参数都传了值UnboundLocalError函数内修改外层变量却没声明nonlocal/global按LEGB分析名字归属补上global或nonlocal声明ImportError/ModuleNotFoundError模块名写错、未安装、循环导入检查模块路径、第三方库是否安装、拆解依赖关系每个报错背后都有“为什么”。比如UnboundLocalError根因是Python在编译函数时发现你对某个名字做了赋值于是把它标记为局部变量后续访问同一函数里的这个名字都会走局部作用域哪怕赋值语句在访问之后才出现。理解了这一点你看到UnboundLocalError时就不会瞎试而是直接去分析变量名归属。排查顺序我建议固定下来先看报错信息最后一行确定是什么类型的错误再往上看代码位置最后结合作用域规则判断。不要一上来就在网上搜整段代码搜一次两次还行每次都搜就没进步了。实际操作中把报错信息完整复制下来先自己读一遍很多时候问题就出在你以为“Python在A处报错所以是A的问题”其实真正的错误在B处只是到A才暴露。4.2 我自己的三个学习习惯专治“学完就忘”第一每个知识点配一个最小可运行脚本。学默认参数那天我就只写一个几行的文件故意踩一次可变默认参数的坑然后把正确写法贴在注释里。这个脚本文件名带日期和主题比如2025-05-06_default_arg.py。两个月后复习直接看这些带现场记录的小文件比看那种几百页的笔记效率高得多。第二用调试器代替print。很多人习惯到处print看值打印完还要记得删。我的做法是直接在IDE里打断点看变量在每一步的值。py03阶段练习函数时断点尤其好用你能清楚看到参数传入前后、return语句执行时变量是什么状态。这个习惯越早建立越好后面学递归、学闭包时断点就是你的眼睛。第三给函数写“测试脚本”。不用学什么测试框架就定义一个main里面放几组输入打印结果手动比对预期值。像上面那个成绩统计模块我会额外测空列表会怎么处理、只有一个分数时正不正常。这个动作能逼着你把边界条件想清楚而边界条件恰恰是新手代码最容易崩的地方。我自己到现在写工具函数依然保留这个“随手补几行测试”的习惯省下来的排错时间远比写测试的时间多。最后再分享一个我个人的体会。py03这个阶段最大的变化不是学会了def这个关键字而是开始有了“我在设计代码”的感觉。函数是抽象的边界模块是组织的边界当你第一次把一个功能封装进函数、再用import把它组装到另一个程序里时那种“代码可以被复用”的感觉是很妙的。建议你也把这段时间的练习脚本都留好命名规范一点两个月后回头看你会看到自己在代码组织能力上非常明显的进步。
返回列表