☰
Python第4天:函数、模块与内置操作,告别复制粘贴的实战指南
2026/10/2 19:11:30 网站建设 项目流程

Python学到第4天,是一个特别有意思的分水岭。前面三天,你大概率已经把环境装好、变量和数据类型混了个脸熟,if判断和for循环也能写上几段了。但真上手写点像样的东西时,你会发现一个尴尬的局面:同一段逻辑想用第二遍,只能复制粘贴;代码一旦超过100行,改一个需求就要翻半天;想用别人现成的功能,又不知道该怎么"借"过来。day04要解决的,恰恰就是这三件卡住所有人的事:函数、模块,以及一组能让代码量骤减的内置操作。

这篇文章按"能直接落地"的标准来写,适合正在按自学路线往前推的入门者,也适合语法学过一遍、但对函数和模块缺乏体系感的人。全程基于Python 3.10+环境,代码都不长,但强烈建议每个例子都亲手敲一遍——纯看一遍的效果,大概只有动手敲的三分之一。

1. 正式动手前,先用三个自测确认自己配得上day04

讲函数之前,我通常会先扔出三个问题。答不上来别急着往下走,先回头补一补,不然今天的内容会学得飘。

1.1 循环与分支:从"背下来"到"敢上手"

很多人学循环是背代码:for i in range(10)记住了,缩进记住了,但一让他写"打印1到100里所有能被3整除的数",就卡住了。第4天开始,函数里随时会嵌套循环和分支,嵌套逻辑比单纯的循环复杂一截。如果这一步还停留在"照着写",接下来会非常吃力。

这里给一个最简单的自测题:写一个循环,从10打印到1。用range(10, 0, -1)还是range(1, 11)再反转?想清楚步长和边界,这不只是背语法,而是理解range的start、stop、step三个参数。如果答不上来,老老实实回到range继续练,今天的内容可以晚一天再看。

1.2 报错信息:Python给新手最好的学习材料

进入函数之后,报错频率会明显上升。作用域不对、参数个数不对、缩进不对,都会立刻报错。我见过太多人一看到红色报错就慌,直接把代码删了重写。其实Python的报错信息是出了名的友好,它会告诉你哪个文件、第几行、什么类型、大概因为什么。

举个例子,NameError: name 'x' is not defined,翻译过来就是"x这个名字还没定义过"。再看完整的Traceback,有个细节特别重要:报错堆栈要从最下面一行往上读,最末尾那行才是真正的出错位置,上面都是调用过程。这个习惯,从第4天开始就必须养成了——后面写函数、调用函数,报错堆栈会越来越长,会看报错就等于有了导航。

1.3 环境与终端:别让工具问题消耗学习意志力

搜"Python环境配置"的教程,大部分是在讲VS Code里怎么选解释器。VS Code确实是入门阶段综合体验比较好的选择:轻量、插件齐全、调试方便。但有两个细节一定要确认清楚:第一,随便敲一段代码,状态栏或命令面板里能看出当前用的是哪个Python解释器;第二,打开终端输入python --version,能正常输出版本号。

这两件事没搞定之前,不要开始今天的内容。工具不稳定的时候学什么都是飘的,你写一段代码,它换个解释器就报ModuleNotFoundError,你还以为是自己代码写错了,实际上连"解释器"都没选对。这种内耗,最消耗新手的学习意志力。

2. 函数从0到1:你需要的不是语法,而是"封装"这个思维

函数是第4天的主角,但理解函数千万别从"函数是对代码的封装"这种话开始。要从"我为什么要写它"开始。

比如你现在有三次机会要计算"价格加税"的结果,最直接的做法是复制三遍公式。但过两天税率改了,复制三次意味着改三处,漏改一处就出bug。把这个计算规则写进一个函数,就是把这个规则存进一个抽屉,每次要用,拉一次抽屉就行。这,才是函数真正的价值。

2.1 def到return的完整旅程

定义函数的关键字是def。一个最小的函数长这样:

def add_tax(price, rate=0.1): result = price * (1 + rate) return result

第一行def后面跟函数名add_tax,括号里是参数price和rate,冒号以下缩进的代码是函数体。函数体内部先算result,然后return把这个结果交出去。调用它时写add_tax(100, 0.2),得到120.0。

要注意,函数体里的result是局部变量,它和你在外面定义的任何result都没有关系。函数是一个独立的执行空间,return交出去的是"算出来的值",不是那个变量本身。喜欢类比的话,可以把函数想象成一台榨汁机:参数是放进去的橙子,函数体是榨汁机构,return是流出来的果汁。你把橙子递进去,果汁倒出来,机器内部发生了什么,外面不需要也不应该关心。

2.2 参数传递方式与默认值设计

add_tax(100, 0.2)这种按位置传参的方式,叫位置参数。参数多的时候容易记混顺序,建议用关键字参数:add_tax(price=100, rate=0.2),顺序就不重要了,可读性也更高。

rate=0.1这种写法叫默认参数,意思是调用时如果没传rate,就自动用0.1。这是写函数的一个重要设计思想:把"大多数场景下不用改"的选项做成默认值,把"每次调用都可能不同"的东西放在前面。比如"税率默认10%,偶尔按20%算"就很适合这种写法。

函数也可以没有参数,比如import random之后定义一个roll_dice,每次执行就返回random.randint(1, 6)。这种无参函数适合封装"不需要输入也有输出"的动作。

2.3 没有return的函数与print和return的区别

如果函数体里没有return,调用它会拿到None。None是Python里一个非常特殊的值,表示"什么都没有"。不要小看这种函数,很多时候我们只是想执行一个副作用:打印日志、写文件、发请求。比如一个函数专门负责把结果打印成整齐的报告,它就不需要返回任何数据。

但新手最容易犯的错是:写了一个函数,里面print出了结果,然后在外面直接拿变量接收调用结果,以为变量里存的就是打印的内容。实际上那个变量是None。记住一句话:print是给"人"看的,return是给"程序"用的。调试的时候用print,功能逻辑上用return。

3. 参数传递的暗坑:今天不解决,三个月后还得踩

第4天最容易劝退新手的点,不是函数难写,而是参数在传递过程中"莫名其妙"被改了。这一节必须讲清楚,否则后面写项目会反复栽在同一个坑里。

3.1 函数里改列表,外面为什么跟着变

先看这段代码:

def append_one(items): items.append(1) numbers = [1, 2, 3] append_one(numbers) print(numbers) # [1, 2, 3, 1]

很多新手看完会愣住:函数里不是局部作用域吗?为什么外面的numbers也变了?

原因在于:Python传给函数的是对象的引用,不是对象的副本。可以把变量想象成一张写着地址的便利贴,list对象是那栋房子。append_one拿到的便利贴,和外面贴的是同一个地址,函数里对房子做的改动,外面看到的就是改过的房子。

这不算bug,这是Python的设计。真正重要的是,写函数之前就要想清楚:我的函数会不会修改传入的list、dict、set?如果不想改,就要在函数内部复制一份再操作。

3.2 默认参数写[],是个流传多年的经典事故

再看一个几乎所有Python面试都会碰到的陷阱:

def add_item(item, items=[]): items.append(item) return items print(add_item(1)) # [1] print(add_item(2)) # [1, 2] ?

第二次调用时,直觉告诉我应该得到[2],结果却是[1, 2]。原因在于默认参数[]在函数定义那一刻只创建了一次,之后所有没传items的调用,共享的是这同一个列表对象。你往里头加东西,上一次的痕迹全留着。

正确写法是把默认参数设为None,函数内部再重新创建:

def add_item(item, items=None): if items is None: items = [] items.append(item) return items

这样每次调用,如果没有传items,都会生成一个新的空列表,互不干扰。

3.3 三条防御式习惯,提前绕开坑

我在带新人时,通常会让他们记住三条规则:

  • 函数尽量别修改外部传入的可变对象,非改不可就通过命名或注释明说。
  • 默认参数只允许用不可变类型:None、整数、字符串、元组都没问题,list、dict、set一概不用。
  • 想保护外部数据,在调用前主动复制一份:data.copy(),或者切片data[:]。

这三条不是语法强制的,而是让代码边界清晰的习惯。等你将来写项目,就会意识到"函数内外数据边界清晰"这件事有多重要——它直接决定了你的代码好不好查bug、好不好改需求。

4. 类型转换、切片和内置函数:今天练熟,代码量立刻减半

第4天除了函数,还有一批"让你写代码写得很痛快"的语法工具要掌握。很多看似复杂的操作,Python在语言层面已经配好了现成功能,只是很多人不知道。

4.1 四个类型转换函数里容易记错的边界情况

int()、float()、str()、bool()是高频操作,但它们的边界情况很容易踩。我把常见行为和易错点整理成一张表:

表达式结果说明
int("12")12字符串里必须是整数格式
int("12.5")报错字符串里带小数点不行
int(3.7)3直接截断小数,不是四舍五入
float("3.14")3.14字符串转浮点数
float(3)3.0整数转浮点数会补小数位
str(100)"100"数字转字符串
bool(0)False数值0为假
bool("")False空字符串为假
bool("False")True非空字符串就是真,内容不重要

最后一行的bool("False")是True,是每年都会有人问的经典迷惑点。另外,想四舍五入到整数,别用int(3.7),用round(3.7)才是4。这几种转换各有各的适用场景,别一招吃遍天。

4.2 切片[start:stop:step]:一行代码代替一堆循环

切片是Python里性价比极高的语法。基本格式是sequence[start:stop:step],start表示开始位置,stop表示结束位置(不包含),step是步长。

nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] print(nums[2:5]) # [2, 3, 4] print(nums[:4]) # [0, 1, 2, 3] print(nums[::2]) # [0, 2, 4, 6, 8] print(nums[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

第三行取了所有偶数下标位置的元素,第四行一行反转列表。这些用循环写也不是不行,但切片一行搞定,可读性还更高。

切片的实际价值远不止这点小花活。处理结构化数据时,想快速取出前N行、跳过表头、隔行抽样,都可以直接用切片。以后你做数据可视化遇到"横坐标太密集"的问题,有个常见的解决办法就是切片抽样——每隔几个点取一个,坐标轴立刻清爽。当然这个以后细说,今天先把切片本身练熟。

还有一个隐藏技巧:nums[:]会返回一个新的副本列表。上一节提到的"复制一份再操作",最简洁的写法就是这个。

4.3 len、sum、enumerate、zip:让循环更优雅

第4天值得认真记住的内置函数不多,但每一个都很能打:

  • len():拿到长度。len("hello")是5,len([1,2,3])是3。
  • sum():求和。sum([1,2,3])是6。
  • max()、min():取最大最小值。
  • sorted():返回排序后的新列表,sorted([3,1,2])是[1,2,3],而且不改变原列表。
  • enumerate():遍历时同时拿到索引和值。
  • zip():把多个序列按位置拼成一一对应的元组。

enumerate和zip是"让循环更优雅"的两个王牌,看个例子:

names = ["alice", "bob", "carol"] scores = [88, 95, 76] for name, score in zip(names, scores): print(name, score)

一次循环同时遍历两个列表,不用去管下标,可读性一下子就上来了。如果不用zip,就得靠索引,代码丑不说,还容易越界。

顺手再记个脸熟:列表推导式。它把循环和append压成一行,比如[x * x for x in range(10)]会生成0、1、4、9、……一直到81的平方列表。初看有点吓人,但本质就是"先想清楚要生成什么元素,再想清楚从哪里取数"。今天不用深入,先认识它。

5. import机制与自建模块:从第一天开始用"轮子"

第4天的另一个重要突破,是第一次正式理解"代码复用"。函数是你自己写的复用,模块是别人写好的复用。这一步打通之后,Python的世界才算真正打开——爬虫、数据处理、办公自动化、量化策略思路,本质上都是"导入合适的模块+调用合适的函数"。

5.1 三种import写法与隐藏习惯差异

  • import math:用math.sqrt(9)调用,所有函数前面都要带模块名。
  • from math import sqrt:直接用sqrt(9),但名字冲突的风险更高。
  • import math as m:用m.sqrt(9)调用,适合长模块名。

三种方式效果差不多,但习惯差异很大。新手最舒服的入门姿势是第一种,代码里能一眼看清函数来自哪个模块。等你也适应了from写法,再按自己的风格来。

有一个细节可以提前知道:from ... import某个名字之后,这个名字绑定的是导入那一刻模块里的对象,模块后续更新不会改变这个已绑定的引用。现在不用深究,知道有这么回事就够了。

5.2 自己写模块,并用__name__守卫测试代码

自建模块这件事,没有想象中复杂。任何一个.py文件,只要你愿意,都可以被其他脚本当作模块导入。比如写一个tools.py:

def say_hello(name): return f"hello, {name}"

然后在另一个文件里写from tools import say_hello,就能直接用了。这就是最简单的自建模块。

但这里有个新手必踩的坑:如果你在tools.py里顺手加了print(say_hello("test"))这类测试代码,那么别的文件导入tools.py时,这行也会跟着执行。解决办法是在tools.py底部加一道"守卫":

if __name__ == "__main__": print(say_hello("zhang"))

__name__这个魔法变量,在直接运行文件时是"main",在被导入时是文件名。所以这段测试代码只在直接运行tools.py时执行,被导入时自动跳过。这个写法在专业项目里也是常规操作,第4天养成这个习惯,后面写工具模块会顺手很多。

5.3 模块搜索路径与"库到底装在哪个目录"

很多人搜"python的库在哪个目录下",本质是想搞明白:import xx之后,Python到底去哪些地方找xx?答案就是sys.path——一个保存查找路径的列表。运行python -c "import sys; print(sys.path)"就能看到一长串目录。想让自己的模块能被导入,把它放在当前运行目录下,或者放到sys.path列出的任意目录里,都行。

第三方模块怎么装?就是网上教程里最常见的pip install。装完它会自动放到site-packages目录。遇到ModuleNotFoundError: No module named 'xx',十有八九是没装;偶尔是装到了另一个Python解释器里。理解了"每个解释器有自己独立的site-packages",这类报错的排查思路就清楚了。

5.4 连接外部程序的入门框架认知

热搜词里"python连接cmd"问得不少,这是一个理解Python位置的好切入点。用subprocess模块,Python可以在自己内部执行外部命令。比如:

import subprocess subprocess.run(["python", "--version"])

这条命令会拿到当前Python的版本输出。这个概念再往外扩,爬虫本质上是Python通过requests去访问某个网络服务,办公自动化是Python通过openpyxl读写Excel文件,量化策略的数据处理,则是通过数据库或API取行情。它们背后的共同框架都是:Python作为客户端,去连接某个外部程序或服务。把这个框架装进脑子里,后面学哪条线都不会懵。

提示:学习连接外部程序时,访问第三方网站要注意遵守对方服务条款和robots规范,本地练习尽量控制在自己的机器环境里,会更稳妥。

6. 实战:用前四天的语法做一款猜数字小游戏

每讲完一块理论,都得有一个能跑起来的项目收口,否则知识是散的。第4天最适合的实战项目,是一个带输入校验和轮数统计的猜数字游戏。它能覆盖变量、输入输出、循环、分支、函数、异常处理、模块导入——几乎把前四天的语法点全串起来了。

6.1 需求拆解:小项目也有四件事要做

规则很简单:程序随机生成一个1到100的整数,玩家每次猜一个数,程序提示"大了"或"小了",直到猜对为止,最后打印总共猜了多少次。

就这么个小东西,拆开也有四个功能点:生成随机数、循环接收用户输入、判断大小并给提示、记录并输出猜测次数。如果你能脱口说出这四个点,说明前几天的拆解意识还不错;说不出来也没关系,多拆几次就有感觉了。

6.2 直写版与函数化重构的对比

我故意先给一个把所有逻辑堆在主程序里的版本:

import random target = random.randint(1, 100) count = 0 while True: guess = int(input("请输入你猜的数字:")) count += 1 if guess > target: print("大了") elif guess < target: print("小了") else: print(f"猜对了,共用{count}次") break

能跑通,但两个问题马上露头:输入非数字会直接崩溃;猜数的逻辑和主流程完全耦合,想复用就得复制粘贴。下面做函数化重构:

import random def get_guess(): while True: raw = input("请输入你猜的数字:") try: return int(raw) except ValueError: print("输入无效,请输入整数。") def check(guess, target): if guess > target: return "大了" if guess < target: return "小了" return "中了" def main(): target = random.randint(1, 100) count = 0 while True: guess = get_guess() count += 1 result = check(guess, target) print(result) if result == "中了": print(f"猜对了,共用{count}次") break if __name__ == "__main__": main()

仔细对比两个版本。第二个版本把"获取输入"和"判断大小"拆成独立函数,main变成了一个调度流程。以后想改提示文案、把难度调成1到1000、增加猜测次数上限,只需要改对应的小函数,主流程不用动。这就是函数化的好处:逻辑拆分,各管各的。

6.3 跑通之后值得做的三件小事

三个功能做完了,千万别停。我建议再做三件小事,比单纯敲一遍案例有用得多。

第一件,把难度参数加进去。把def main()改成def main(low=1, high=100),里面的random.randint(low, high)就跟着参数走。这样下次想玩1到1000,直接main(1, 1000),连文件都不用改。

第二件,统计"历史最佳记录"。暂时不用写文件,可以用返回值把次数带出来,或用一个全局变量记录当前会话里的最少次数。这个小改动逼着你思考"函数之间怎么传数据"。

第三件,给get_guess加一个"连续输错三次自动退出"的能力。需要设计一个计数器,想清楚是继续循环还是提前退出、退出前要打印什么。这件小事会把异常处理和分支逻辑又练一遍,性价比很高。

6.4 这个"幼稚"项目通向的四个方向

猜数字小游戏看起来幼稚,但它已经是一个完整的程序循环:接收输入、处理、输出、重试。把它吃透了,后面的学习路线其实非常清晰。

把每次猜测的数值收集成一个列表,拿matplotlib画出来,就会碰到热搜里"画图横坐标太密集"的问题,这时候用切片抽样就很自然;把对局记录写入Excel,就会接触到openpyxl,也就是"python写入excel";把"从某个系统拉数据、清洗、处理、存档"这套流程串起来,正是办公自动化里"自动拉表"的核心场景;再往深走,把规则引擎和数据处理结合,量化策略里处理行情数据也长同样的骨架。网上那些爱心代码、李白打酒之类的趣味练习,本质上也还是函数加循环的组合,用来练手检验自己的基本功就是好去处。

第4天不需要一口气走到那里。今天搭好的函数骨架和模块意识,是走向所有这些方向时最通用的底座。把基础做扎实,比任何花里胡哨的进阶教程都更重要。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询