很多人第一次接触Python,装好环境之后对着编辑器和命令行一脸懵:我到底该怎么开始写?写出来的东西算什么"程序"?我该按照什么顺序来学?这些问题,本质上都指向了一个问题——你还没搞清楚一个Python程序究竟是由什么构成的。
我见过太多初学者,一来就报班学爬虫、学数据分析,工具链装了一堆,最后连一个最基本的程序都写不完整。不是他们不够聪明,而是跳过了最关键的底层认知:程序不是神秘的暗号,它是由几种固定成分组合起来的东西,像搭积木一样有规律可循。这篇文章我就以"程序的构成"为核心,把Python程序从最小单元到工程组织一层层拆开,结合我实际教学和写代码过程中踩过的坑,带零基础的你把这块地基打扎实。全文偏实操,建议打开电脑跟着写,光看不动手,看完一定忘。
1. 先搞清楚一个Python程序到底是怎么跑起来的
1.1 从"一行代码"到"屏幕上出结果"的完整路径
在拆解程序的构成之前,我建议你先理解一个最基本的问题:你写的那几行文字,凭什么能被计算机执行?
计算机的CPU只认机器指令,也就是一串串二进制。而Python是一门解释型语言,它不会把你写的代码一次性变成整个可执行文件,而是需要一个"翻译"在运行时逐行把代码转成机器能懂的操作。这个"翻译"就是Python解释器,你在命令行里敲的python、在PyCharm里点击的"运行"按钮,本质都是在启动这个解释器。
举个例子,你写:
print("你好,Python")这一行文本保存在.py文件里。解释器读取它之后,先做词法分析和语法分析——你可以理解为"读句子、断句、看语法通不通",然后把print识别为一个内置函数,把"你好,Python"识别为一个字符串,最后调用底层的输出能力把它显示到屏幕上。
这个"读代码、理解代码、执行代码"的过程,Python内部有一套完整的机制,从字节码编译到虚拟机执行,一环扣一环。但对零基础来说,你只需要记住一个结论:你写的程序由多种成分组成,解释器负责把这些成分逐个解析、执行,谁先谁后都有章可循。理解了这一点,后面再学变量、函数、模块时,你就会知道每个东西在解释器眼里是什么样的"角色"。
1.2 很多新手根本没意识到"程序"是有层次的
我在带新手的时候,经常问一个问题:下面这段东西,算不算一个程序?
print(1) print(2) print(3)答案当然算。这是一个能运行、有输出的合法Python程序,虽然它没什么用。这件事很关键:程序的最小完整形态,就是一系列按顺序执行的指令。
但现实中的程序远比这复杂。你不可能什么都用一条条平铺的print写完,那样代码会膨胀到不可维护。于是就有了函数、类、模块,这些东西让程序从"一条条命令堆叠"变成"有结构、有层次的组织体"。
我给你一个直观的类比。一个程序就像一家餐厅:
- 一行行的基础语句是餐厅里每个服务员的具体动作——端菜、擦桌、收银;
- 函数是岗位职责——"传菜员负责把菜从厨房端到前厅"这个职责被定义好之后,每次需要传菜就喊一声,不用重复描述动作细节;
- 类像是部门制度——规定服务员有哪些属性(工号、姓名)、能执行哪些操作;
- 模块是整个餐厅的分区——后厨、前厅、仓库各司其职,互不干扰,但又能互相配合。
一个成熟的Python程序,一定是按照这个层次组织起来的。接下来我逐个拆解,让你真正看明白每一层是干什么的、怎么用。
2. 构成程序的四层核心成分:表达式、语句、函数、模块
2.1 表达式:程序的"原料",计算结果的东西
在Python里,表达式是被解析器计算并产生一个值的代码片段。听起来很绕,但你看完例子就懂了:
42 # 一个字面量,值就是42 3 + 5 # 一个运算,值就是8 "Hello" # 一个字符串字面量,值就是"Hello" len("abc") # 函数调用,值就是3这几行代码都有一个共同点:它们各自"算出"了一个值。表达式是构成程序的最小营养单位,就像面粉、鸡蛋、糖。你单独吃面粉不叫一顿饭,单独写一个表达式也不叫一个程序,但任何一段程序里都充满了表达式。
我实测下来,初学者最容易混淆的是"表达式"和"语句"的边界。你写x = 1 + 2,这里的1 + 2是表达式,它计算出了值3;而x = ...这个整体是赋值语句,它的作用是把计算出来的3存到变量x里。也就是说,表达式的核心职责是"算出值",语句的核心职责是"做事情"。
Python里有一些很经典的坑和表达式的求值顺序有关。比如3 + 5 * 2,结果是13而不是16,因为乘法的优先级高于加法。很多新手在这里栽跟头,不是不知道优先级规则,而是没意识到"表达式在按规则计算"这件事本身就是程序构成的一部分。遇到拿不准的优先级,我的建议就一条:显式加括号。(3 + 5) * 2和3 + (5 * 2)一眼就能看懂,不需要在脑内模拟解释器的优先级表。
2.2 语句:程序的"动作",做事情的单元
如果说表达式是原料,那语句就是"动作"。Python程序本质上就是一组按顺序执行的语句。常见的语句包括:
- 赋值语句:
x = 10 - 条件判断语句:
if ... elif ... else ... - 循环语句:
for ... in ...、while ... - 函数定义语句:
def ... - 类定义语句:
class ... - 导入语句:
import ...
这些语句共同构成了程序的骨架。解释器执行一个.py文件时,就是从第一行开始,一条语句一条语句往下执行,遇到函数定义就把函数记下来(但还不执行函数体内的代码),遇到if就判断条件决定走哪个分支,遇到循环就重复执行循环体。
很多人刚学Python时有个困惑:"为什么我定义了一个函数,运行程序却什么都没发生?"原因是定义函数的def语句只是"登记"了一个函数,并不会主动调用它。这就像你制定了一份工作流程文档,贴在公司墙上,但没人去执行它,流程就永远不会起作用。程序也一样,函数是蓄势待发的动作集合,只有真正调用它的那一刻,函数体里的语句才会逐一执行。
2.3 函数:程序的"积木块",复用与抽象的核心
函数是Python程序构成里最重要的一层,没有之一。它解决的根本问题是:把一段逻辑打包,给它起个名字,以后用这个名字就能反复触发这段逻辑。
看一个最简单的例子:
def greet(name): return "你好," + name print(greet("小明")) print(greet("小红"))def greet(name):是一个函数定义语句,name是参数,return是返回结果。函数体里的"你好," + name是一个表达式,它被计算出来后会作为返回值交给调用方。
我在教零基础学员时,会把函数比作"外卖订单"。你点一份"鱼香肉丝饭",后厨不会重新发明一遍菜谱,而是按照已经定义好的流程(函数体)来做。参数就是订单上写的"微辣""加饭"这些选项,返回值就是你最终拿到的那份饭。你不需要知道后厨怎么炒菜,只需要知道订单怎么写——这就是"抽象"。
从程序的构成角度看,函数带来两个巨大好处:
- 消除重复。同一段逻辑只需要写一次,需要时调用即可,程序总体积变小,修改时也只需要改一处。
- 隔离复杂度。复杂程序可以拆成多个小函数,每个函数只负责一件事,各自内部再复杂也不影响外部调用。
这里我特别想强调一个反直觉的点:很多新手觉得"我功能都实现了,干嘛要拆函数?写在一起还更连贯"。这想法短期没问题,但程序一旦超过200行,不拆函数的代码会变成一团乱麻。你改一个功能可能要牵连三个地方,调试时满屏变量看不到边界。我自己的经验是:一个函数最好控制在20-30行以内,超过这个长度,说明它干了不止一件事,该拆了。
2.4 模块:程序的"仓库",组织代码的工程化手段
当一个.py文件里的函数和类越来越多时,你又会遇到新的问题:文件太长,找东西费劲,写起来没头绪。模块就是用来解决这个问题的。
一个.py文件本身就是一个模块。你可以用import语句把另一个文件里的函数拿过来用:
# utils.py def add(a, b): return a + b def multiply(a, b): return a * b# main.py import utils print(utils.add(3, 5)) print(utils.multiply(3, 5))这里main.py和utils.py是两个模块。import utils语句做了三件事:找到utils.py、从头到尾执行它(把它内部的函数定义好)、在当前文件里建立一个名为utils的引用供你调用。
模块再往上,包(Package)就是装满模块的文件夹,通常带一个__init__.py文件。有了包和模块,你就能把一个大型程序拆成几十个文件,按功能分区存放。比如一个项目里可以有auth/、database/、api/等包,每个包再包含若干个模块,每个模块再包含若干个函数和类。
这就构成了一个完整的程序层次:表达式 → 语句 → 函数 → 类 → 模块 → 包 → 程序。你在写任何Python程序时,实际上都是在构建这个层次。哪怕你只写了一个20行的脚本,它也天然包含了表达式、语句、函数这些成分;等你写了5000行的项目,它就需要模块和包来维持秩序了。
3. 变量和数据:程序里流动的"血液"
3.1 变量的本质:一个带名字的储物格
刚才多次提到变量,但很多零基础的人对变量的理解是模糊的。有学员跟我说:"变量就像数学里的x,代表一个未知数。"这个理解有误导性。在Python里,变量更像是一个贴了标签的储物格:你把一个值放进格子,并在格子外面写上名字;以后你用这个名字,就能拿到这个值。
user_name = "张三" user_age = 25 is_vip = Trueuser_name这个变量指向字符串对象"张三"user_age指向整数对象25is_vip指向布尔对象True
Python的变量没有固定的类型标签,它可以随时指向另一种类型的对象。这在带来灵活性的同时,也埋了一些坑。
3.2 动态类型与隐式转换:方便,但也是坑源
Python是动态类型语言,意味着你不必声明变量的类型。下面这段代码是合法的:
data = 100 data = "字符串" data = [1, 2, 3]同一个变量名,先后指向了整数、字符串、列表。这在C++、Java里是不可想象的。我见过不少从静态语言转过来的朋友,一开始极其不适应,总是问"这个变量到底是什么类型"。我的经验就一句话:别用静态类型的习惯去定义Python变量,但要在心里清楚"这个变量此刻是什么类型"。
动态类型带来的典型坑是隐式类型转换。比如:
age = "25" print(age + 1)运行这行代码,Python会直接抛出TypeError: can only concatenate str (not "int") to str。原因是age是字符串,+号在字符串语境下是拼接的意思,而1是整数,Python不知道你想干什么。你想做的是数值相加,需要显式转换:
age = "25" print(int(age) + 1) # 输出 26你还可以反过来转:str(25)把整数转换成字符串"25"。在写代码的时候,我建议你在做运算之前,先确认操作数的类型,尤其是从文件、输入框、接口里拿到的数据——那些东西十有八九是字符串,直接拿去运算就会踩到上面那个坑。
3.3 定义变量时该遵守的实操规范
变量名看起来是小事,但我在review代码时最烦的就是a、b、temp、data1这类毫无信息的命名。三个月后你自己回来看,都不知道这些变量是什么。
Python社区有一套公认的命名规范(PEP 8),对零基础来说只需要记住几条核心:
- 变量名使用小写字母,多个单词用下划线分隔,比如
user_name,而不是userName或UserName; - 变量名要能表达含义,
user_name比un好一万倍; - 不要用Python关键字做变量名,比如
if、for、class; - 常量(约定不修改的值)用大写字母,比如
MAX_RETRY_COUNT = 3。
我个人的另外一个实操建议是:一个变量的生命周期越短越好。很多人习惯在程序开头把所有变量都声明了,用到后面代码里穿插使用。这种做法会让追踪变量值变得极其困难。更好的方式是:在真正需要这个值的地方附近创建变量,用完之后它就是会被自然"遗忘"。这样你在读代码时,看到某个变量的视线范围越小,越不容易出错。
4. 程序的执行走向:顺序、分支、循环
4.1 顺序执行:程序默认的"单行道"
Python程序默认从上往下逐行执行。这是理解程序执行机制的最基础模型。
print("第一步") print("第二步") print("第三步")输出必然是第一步、第二步、第三步。这个顺序不能用编程技巧改变,它是解释器的底层行为。你写任何程序,本质上都是在你决定这条"单行道"的路线。
那为什么真实程序看起来不像是一条平路往下走?因为里面出现了岔路口和回形走廊——分支和循环。
4.2 分支:让程序学会"看情况办事"
if语句让程序可以根据条件选择执行哪些语句块。看一个实际例子:
score = 87 if score >= 90: grade = "优秀" elif score >= 80: grade = "良好" elif score >= 60: grade = "及格" else: grade = "不及格" print(grade)这个程序从第一行开始,先给score赋值,然后逐个判断if、elif里的条件。条件是布尔表达式——即计算结果为True或False的表达式。score >= 90这个表达式对87求值为False,于是继续看下一个条件score >= 80,为True就执行grade = "良好",然后跳过剩下的所有elif和else。
这里面有个新手容易犯的错误:把if写成一堆独立的if,而不是用elif串起来。二者的区别很关键:
score = 87 # 写法一:多个独立if if score >= 60: print("及格") if score >= 80: print("良好") if score >= 90: print("优秀")这种写法,87会同时满足>=60和>=80两个条件,程序会打印两行。如果本意是只给一个评级,就必须用elif。写分支之前,先问自己:这些条件是要"匹配其中一个",还是"逐个独立判断"?这个问题想清楚,分支就不会写错。
4.3 循环:把重复劳动交给机器
循环有两种,for循环和while循环。零基础优先掌握for循环,它的语义更简单直白:遍历一个可迭代对象,取出每个元素执行一遍循环体。
students = ["小明", "小红", "小刚"] for student in students: print(student + "来签到")这段代码的本质是:for语句从students列表里依次取出元素,每次取一个放到变量student里,然后执行循环体。取完所有元素,循环结束。
range函数在for循环里太常用了:
for i in range(5): print(i)range(5)生成0, 1, 2, 3, 4这5个数。注意是从0开始,不包含5。这个细节每年坑掉无数新手——想打印5个数,以为range(5)是1到5,结果发现从0开始。
while循环则会在条件为真时一直执行循环体。最常见的坑是死循环:条件永远为真,程序出不来。例如:
i = 0 while i < 5: print(i)这段代码会无限打印0,因为i在循环体里从未变化,i < 5永远是True。正确做法是在循环体里改变条件变量:
i = 0 while i < 5: print(i) i += 1 # 每次加1,当i=5时条件为False,循环结束从程序的构成角度看,循环是整个程序里唯一能让"代码往回走"的结构。它本身很简单,但和分支、函数组合起来,就能实现非常复杂的行为。我建议你在初学阶段多观察程序的"执行轨迹"——在心里模拟一遍:代码走到哪一步、变量变成什么值、下一行走哪条路。很多难掌握的复杂程序,归根到底不过是顺序、分支、循环三种执行走向的排列组合。
5. 缩进和注释:看起来不像代码,但决定程序能不能活
5.1 缩进在Python里不是排版,是语法
这是Python和绝大多数编程语言的最大区别之一。C++、Java用花括号{}来表示代码块的边界,而Python用缩进表示。同一个缩进层级的代码属于同一个代码块。
if score >= 90: print("优秀") # 这一行缩进了4个空格,属于if内部 print("程序结束") # 这一行没有缩进,属于程序主流程如果缩进写错,轻则IndentationError异常直接报错,重则程序行为与预期不符。比如:
if score >= 90: print("优秀") print("恭喜你") # 这行也被缩进,只有条件满足才会执行 print("结束") # 这行无条件执行常见问题里,有些编辑器默认用Tab缩进,有些用空格,混用时Python 3会直接报错TabError: inconsistent use of tabs and spaces in indentation。我的建议很干脆:统一用4个空格,不要用Tab键(或者把编辑器配成Tab键自动转成4个空格)。主流编辑器如VS Code、PyCharm都支持这个配置。
另一个容易被忽略的点:缩进级别不要随便加深。新手嵌套多层级逻辑时(if里面套for、for里面套if),缩进很容易乱。对策就是之前说的:如果你发现自己缩进了四五层,赶紧把内层逻辑拆成函数。缩进的深度说明逻辑的复杂程度,缩进越深,代码越难读、越容易出错。
5.2 注释是写给"三个月后的自己"看的
注释不参与程序的执行,解释器会完全忽略它。但它是程序构成里不可缺少的一部分——因为你写的代码不仅是给计算机看,更是给人看。这个人包括三个月后的你自己,以及你的同事。
Python的注释有三种主要形式:
# 单行注释:用井号开头 """ 多行字符串注释: 三个引号包裹,可以写多行说明, 常用于文件开头的模块说明。 """ def add(a, b): """函数的文档字符串(docstring) 可以用 add.__doc__ 或 help(add) 查看。 """ return a + b我的注释经验有三个原则:
- 注释解释"为什么",而不是"是什么"。
# 把用户输入转成整数,因为input返回的一定是字符串这种注释有价值;# 定义变量x这种注释纯属噪音。 - 代码无法表达的约束,用注释写清楚。比如某个函数要求参数不能为负数,这个约束必须写明。
- 注释要保持更新。代码改了,注释没改,比没有注释还误导人。我看到过太多"注释和代码说的是两码事"的惨案。
5.3 命名规范:程序可读性的第一道关卡
变量、函数、类的命名,本质上是给程序里的各种构成成分"贴标签"。标签贴得好,读代码就像看目录;标签贴得烂,读代码就像看天书。
函数名用动词或动词短语,因为函数是动作:get_name、save_file、calculate_average。变量名用名词,因为变量是事物:user_age、total_price。类名用名词,且使用大驼峰(每个单词首字母大写):StudentInfo、OrderManager。
一个我多年总结的命名心法:如果给一个东西起名字要想三秒以上,说明你对它的概念还不清晰。这时候不要硬起一个凑合的名字,而是先停下来想一想这个东西到底代表什么、职责边界在哪。命名困难往往是抽象不足的信号,这时候该做的是继续拆分,直到你能轻松给它起一个准确的名字为止。
6. 从"能跑"到"会组织":程序构成的工程化思维
6.1 一个程序不要超过一个屏幕
零基础阶段,你可能觉得"我写的程序都在一个文件里,不超过100行,根本不需要什么工程化"。这个想法没错,但它会限制你写更大程序的能力。
工程化思维的第一条原则是:以小函数为单位组织代码。一个程序不管多复杂,写出来的正文都应该像一份层次清晰的文档。我们从一开始就养成习惯:把任务拆成多个函数,每个函数只做一件事。
看一个对比。假设要写一个程序:从命令行读取三个数字,计算它们的平均值,并输出结果。
粗糙写法:
a = input("请输入第一个数:") b = input("请输入第二个数:") c = input("请输入第三个数:") a = int(a) b = int(b) c = int(c) avg = (a + b + c) / 3 print("平均数是:", avg)这种写法不是不行,但它把所有逻辑平铺在主流程里。如果以后要改成"处理100个数字"或者"把平均数保存到文件",你得改一大坨。
函数化写法:
def get_number(prompt): """读取用户输入,并转换成整数""" return int(input(prompt)) def calculate_average(numbers): """计算一组数的平均值""" return sum(numbers) / len(numbers) def main(): nums = [] nums.append(get_number("请输入第一个数:")) nums.append(get_number("请输入第二个数:")) nums.append(get_number("请输入第三个数:")) avg = calculate_average(nums) print("平均数是:", avg) if __name__ == "__main__": main()功能完全一样,但程序的结构发生了变化。每个函数各司其职,main函数像一份"节目单",一眼就能看出程序的执行脉络。以后改造时,你可能只需要动main里的排列组合,或者给calculate_average加个去极值的逻辑,其他部分几乎不用动。
6.2 用if __name__ == "__main__":保护程序入口
上面代码最后一行有一个经典结构:
if __name__ == "__main__": main()很多初学者完全看不懂这行是干嘛的,甚至直接删掉。我必须把它讲透,这是从"脚本"走向"程序"的一道分水岭。
每个Python模块都有一个内置变量__name__。当模块被直接运行时,__name__的值是字符串"__main__";当模块被其他模块导入时,__name__的值是模块自己的名字(比如utils)。
所以if __name__ == "__main__":的意思是:只有当这个文件被直接运行时,才执行main();如果它被别人import,这行代码不会触发main()。
这个机制解决了什么问题?举个例子:假设你在utils.py里写了一个工具函数,又怕自己测试时忘了怎么用,于是在文件末尾写了几行调用代码。后来你在main.py里import utils,那几行测试代码就会被一并执行,把屏幕输出搞得一团糟。用if __name__ == "__main__":包住测试代码,就能避免这个尴尬。
从程序构成的视角看,这个结构把程序划分成了两个世界:一个是"模块的定义内容"(函数、类、变量),另一个是"程序的启动入口"(main函数的调用)。定义内容在被import时应当安静地被加载,而启动入口只在直接运行时才发挥作用。这两种身份混在一起,是程序出各种奇怪问题的根源之一。
6.3 拆模块的最佳时机:不早拆,也别晚拆
最后一个要聊的问题:什么时候把一个文件拆成多个模块?
我的经验是三条信号:
- 文件超过300行。这时你滚动翻页找函数已经不是效率问题了,而是容易出现遗漏和误改。
- 不同功能被多个文件复用。比如你写了两个项目文件都需要 AI 换算功能,那就应该抽到
utils.py里。 - 你想给某个部分单独做测试。独立的模块可以被单独
import到测试脚本里,不必把整个程序都跑起来。
拆模块和拆函数的逻辑完全一致:按职责划分,按变化频率划分,按复用度划分。没有绝对标准,但方向一定是从"一坨"走向"分门别类"。
我自己写一个小工具时,通常的构成形态是这样的:
project/ ├── main.py # 程序入口,调用各个模块 ├── config.py # 配置项,常量 ├── utils.py # 通用工具函数 ├── models.py # 数据结构和类定义 └── tests/ └── test_utils.py # 针对工具函数的测试这个结构不是唯一正确答案,但它体现了清晰的职责边界。main.py只管"引导流程",config.py只管"参数配置",utils.py只管"杂七杂八的通用能力",models.py只管"数据形态"。每个人都可以发展出适合自己项目的组织风格,但核心原则不变:让每个文件的职责单一而明确,让每个函数都短小精悍,让整个程序像一本目录清晰的书。
写到这里,我相信你对"Python程序由什么构成"已经有了一个完整的认知框架:从表达式到语句,从函数到模块,从变量到控制流,从缩进到入口保护。这套框架不只是语法知识的堆叠,更是一个合格Python开发者看待程序的方式。下次你再打开一个陌生的代码文件,不妨先按这个框架去"解剖"它:哪些是导入语句、哪些是变量定义、哪些是函数、哪些是主流程入口、哪些是分支和循环。每一层都看懂之后,复杂程序就不再是一团迷雾了。