今天你 AI 赋能了吗?打开浏览器,在地址栏输入 xuefulu.ai ,按下 Enter 即可访问学府路1号!
2026年10月14日 星期三 10:40:28
常用网址: | | 学府路1号 https://xuefulu.ai | 下载 Python https://xuefulu.ai/Python.html | 下载 TRAE https://xuefulu.ai/Trae.html

一、为什么要「拆模块」

写到几十行、几百行之后,一个 .py 文件会越来越长。所有代码挤在一起,有三个很现实的麻烦:

  1. 找不到东西。想改「算平均分」那段,得从头往下翻半天;
  2. 不能复用。下个项目还要用同样的「算平均分」,只能复制粘贴,改一处要改两处;
  3. 没法分工。两个人同时改一个文件,很容易互相覆盖。

模块(module)解决的正是这件事:把相关的功能放进一个独立文件,需要时导入进来。包(package)则是把一堆相关的模块装进一个文件夹,再往上套一层。

其实你已经一直在用模块了——import math 里的 math 就是一个模块,只不过那是别人写好的。本篇讲的是自己拆、自己建、自己导。

名字是什么例子
模块 module一个 .py 文件math、你的 mytools.py
包 package一个装着模块的文件夹numpy、你的 myshop/
函数 function文件里的一段可复用代码math.sqrt()

第 11 篇《导入库的方法》讲的是「怎么把别人的东西拿来用」,本篇往前一步,讲的是「怎么把自己的东西拆好、放好、再用起来」。

课程思政|诚信与职业操守 · 分工,不是各扫门前雪

把功能拆进不同模块,好处之一是两个人可以各写各的、互不覆盖。但拆开只是开头——接口怎么定、变量叫什么,都要事先说好。协作的底线是不给别人添乱:你改的这一处,别人还要接着用。将来进团队、进项目组,先守约定、再谈效率,这份分寸感就是职业操守。

二、模块就是一个 .py 文件

这是最容易被想复杂的一点:模块没有任何特殊格式,就是一个普通的 .py 文件。

下面这个文件叫 mytools.py,内容里只有两个函数和一个变量:

Python
# 文件:mytools.py
def add(a, b):
    return a + b

def average(nums):
    return sum(nums) / len(nums)

PI2 = 6.28

就这么简单。它现在既是「能直接运行的程序」,也是「能被别的文件导入的模块」。保存到你的项目文件夹里,模块就诞生了。

接下来在同一个文件夹里新建另一个文件,比如 main.py,写上:

Python
# 示意:文件 main.py(和 mytools.py 放在同一个文件夹里才能跑)
import mytools

print(mytools.add(2, 3))
print(mytools.average([12, 15, 9]))    # 三杯奶茶的价格
print(mytools.PI2)

运行 main.py,会看到:

输出
5
12.0
6.28
导入名字 = 文件名去掉 .py。mytools.py 就写 import mytools,不要写成 import mytools.py。这是新手最常犯的错之一。

为了让下面每一段代码你都能直接复制粘贴就跑,这里用一个临时代替:先用几行代码把 mytools.py 造在一个临时文件夹里,再导入它。真实项目里不需要这一堆准备代码——把文件放好、直接 import 就行。

Python
import pathlib, sys, tempfile

work = pathlib.Path(tempfile.mkdtemp())      # 造一个临时文件夹
(work / 'mytools.py').write_text('''
def add(a, b):
    return a + b

def average(nums):
    return sum(nums) / len(nums)

PI2 = 6.28
''', encoding='utf-8')

sys.path.insert(0, str(work))               # 告诉 Python 去这个文件夹找模块
import mytools                              # 导入自己写的模块,写法和导入 math 一样

print(mytools.add(2, 3))
print(mytools.average([12, 15, 9]))    # 三杯奶茶的价格
print(mytools.PI2)

输出:

输出
5
12.0
6.28

注意第四行那几个单引号——它在造文件内容。真实的项目里你是在编辑器里手敲这个文件,用不着这行。

2.1 找不到模块会怎样

如果 mytools.py 没放在能被找到的地方(比如放错文件夹),导入就会失败:

Python
import mytools

运行后会报错(逐字照抄解释器):

报错
ModuleNotFoundError: No module named 'mytools'

ModuleNotFoundError 是导入环节最常见的一个错。它的意思很直白:「我按规矩找了一圈,没找到叫这个名字的模块」。第七节会讲它到底去哪儿找了。

三、导入的写法:四种,各有场合

第 11 篇《导入库的方法》里已经讲过四种写法。这里用标准库再快速过一遍,重点放在什么时候用哪种。为了让你能直接跑,下面都用 math 当例子——换成你自己写的模块,写法一模一样。

3.1 import 模块名:最稳,也最清楚

Python
import math
print(math.sqrt(16))
print(math.pi)

输出:

输出
4.0
3.141592653589793

用时前面要带 math.。啰嗦一点,但一眼就能看出这个函数是从哪来的——文件长了以后这个优点很重要。

3.2 from 模块名 import 名字:只拿要用的

Python
from math import sqrt, pi
print(sqrt(16))
print(pi)

输出:

输出
4.0
3.141592653589793

用的时候不用写前缀,代码更短。代价是看不出它来自哪个模块,而且名字可能和你自己定义的撞车。

3.3 import 模块名 as 别名:名字太长时的救星

Python
import math as m
print(m.ceil(3.2))
print(m.floor(3.8))

输出:

输出
4
3

3.4 from 模块名 import *:别用

from math import * 会把模块里所有公开名字一股脑倒进你的文件,包括你不认识的。后果是:你自己的变量被悄悄覆盖、dir() 列出来几百个名字、出错时根本查不出这个名字哪儿来的。养成习惯:一律不写星号导入。
写法什么时候用要不要写前缀
import math默认就用这个,最不容易乱要,math.sqrt()
from math import sqrt只用一个函数,且名字不会撞不要,sqrt()
import math as m模块名很长(如 import numpy as np)要,但短,m.sqrt()
from math import *任何情况下都不建议不要,且会污染命名

3.5 自己写的模块也是这么导

把上面的 math 换成你的模块名,其他一个字都不用改:

  1. import mytools → 用 mytools.add(2, 3);
  2. from mytools import add, average → 直接用 add(2, 3);
  3. import mytools as mt → 用 mt.add(2, 3)。

四、看看模块里有什么:dir() 与 help()

拿到一个不认识的模块(不管别人写的还是自己写的),先问两个问题:它有什么?每个东西怎么用? 两个内置工具就能回答。

4.1 dir(模块):列出它的全部名字

Python
import math
print(dir(math)[:6])          # 只印前 6 个,不然太长
print('一共', len(dir(math)), '个名字')

输出:

输出
['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'acos']
一共 67 个名字

列表里那些带双下划线的(__name__、__doc__)是 Python 自动加的,先忽略;剩下没下划线的是真正给你用的函数和常量。

4.2 help(模块):看说明书

help(math) 会打印一整页说明,在 IDLE 或终端里翻着看最方便。它读的就是模块里的文档字符串——这也是为什么第五节要讲「给自己的模块写说明」。

Python
import math
print(math.__doc__.strip().splitlines()[0])

输出:

输出
This module provides access to the mathematical functions

4.3 三个自动带上的属性

Python
import math, json
print('math 叫什么:', math.__name__)
print('math 有 __file__ 吗:', hasattr(math, '__file__'))
print('json 叫什么:', json.__name__)
print('json 从哪来:', json.__file__.split('/')[-1])
print('json 有说明吗:', bool(json.__doc__))

输出:

Python
math 叫什么: math
math 有 __file__ 吗: False
json 叫什么: json
json 从哪来: __init__.py
json 有说明吗: True

注意 math 那一行是 False——它没有 __file__。原因是 math 属于内置模块,直接编进了 Python 解释器里,没有单独的 .py 文件;而 json 是用 Python 写的,实实在在地躺在一个文件里。

属性含义举例
模块.__name__模块的名字'json'
模块.__file__模块文件放在哪儿(内置模块没有)…/json/__init__.py
模块.__doc__模块开头的说明文字help() 显示的就是它
换到自己写的模块上完全一样:dir(mytools) 会列出你的函数,mytools.__file__ 会告诉你那个文件到底在哪。「找不到自己写的模块」时,先印一下 __file__,十有八九是文件位置不对。

五、__name__ 是什么、if __name__ == '__main__' 有什么用

上一节看到 math.__name__ 是字符串 'math'。那么正在运行的那个文件,它的 __name__ 是什么?

Python
print('当前文件的 __name__ 是:', __name__)

import math
print('被导入的 math,__name__ 是:', math.__name__)

输出:

输出
当前文件的 __name__ 是: __main__
被导入的 math,__name__ 是: math

结论:直接被运行的那个文件,__name__ 是 '__main__';被导入的模块,__name__ 是自己模块名。

这个区别有什么用?看下面这个文件:

Python
# 文件:greet.py
def hello(name):
    print('你好,' + name)

if __name__ == '__main__':
    print('这一行只在本文件被直接运行时才出现')
    hello('同学')

把它跑两遍,分别是「当模块导入」和「直接运行」,看看差别:

Python
import pathlib, sys, tempfile, runpy

work = pathlib.Path(tempfile.mkdtemp())
f = work / 'greet.py'
f.write_text('''
def hello(name):
    print('你好,' + name)

if __name__ == '__main__':
    print('这一行只在本文件被直接运行时才出现')
    hello('同学')
''', encoding='utf-8')
sys.path.insert(0, str(work))

print('--- 情况一:被别的文件 import 时 ---')
import greet
greet.hello('同学')

print('--- 情况二:自己直接运行时 ---')
runpy.run_path(str(f), run_name='__main__')

输出:

输出
--- 情况一:被别的文件 import 时 ---
你好,同学
--- 情况二:自己直接运行时 ---
这一行只在本文件被直接运行时才出现
你好,同学

差别就在这里:导入时那句 print 没出现,因为它被 if 挡住了。

运行方式此时 __name__if 里面会执行吗
python greet.py(直接运行)'__main__'会
import greet(被别人导入)'greet'不会
给每个「既能单独运行、又能被导入」的模块都加上这行 if __name__ == '__main__':,把「测试用的打印」和「演示用的调用」写在里面。好处是:别人导入你的模块时,不会有莫名其妙的输出冒出来。

AI 助手小贴士|两种 import 的差别在「谁进了当前文件的名字空间」。

打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:import my_utils 和 from my_utils import fmt_price 有什么区别?各举一个会发生命名冲突的例子

课程思政|工匠精神与严谨求实 · 别打扰用你代码的人

别人 import 你的模块,是为了用里面的函数,不是想看你在自测时随手打印的东西。把测试代码放进 if __name__ == '__main__': 里,导入时它就安静地待着。写代码时多想一步使用者的感受,这个小小的判断里,藏着的正是替别人着想的工匠心。

六、包:把模块装进文件夹

模块多了怎么办?按用途分类,放进文件夹——这个文件夹就是包(package)。

一个包的样子长这样:

输出
myshop/
├── __init__.py      ← 这个文件的「有无」决定它是不是包
├── goods.py         ← 模块:饮品
└── orders.py        ← 模块:订单

注意那个 __init__.py:它是包的门牌。有个约定要记住——

Python 版本__init__.py 是必须的吗说明
Python 3(现代用法)不是必须没有它也能当包用(叫「命名空间包」)
老版本 / 老教程必须很多教程还这么写,看到别奇怪
建议还是写上内容可以是空的,也可以放包的说明;有它更明确

用起来是这样的:

Python
# 示意:文件 main.py(需先按上面的结构建好 myshop 包)
from myshop import goods        # 方式一:从包里导入某个模块
import myshop.orders            # 方式二:导入包里的模块,用全名调

print(goods.price('珍珠奶茶'))
print(myshop.orders.count())

下面这段把「建包 → 导入 → 用」整个流程真跑一遍:

Python
import pathlib, sys, tempfile

work = pathlib.Path(tempfile.mkdtemp())
pkg = work / 'myshop'
pkg.mkdir()
(pkg / '__init__.py').write_text('# 包的门牌,内容可以为空\n', encoding='utf-8')
(pkg / 'goods.py').write_text(
    'def price(name):\n'
    '    return {"珍珠奶茶": 12, "柠檬茶": 8}[name]\n',
    encoding='utf-8')
(pkg / 'orders.py').write_text(
    'def count():\n'
    '    return 3\n',
    encoding='utf-8')

sys.path.insert(0, str(work))
from myshop import goods
import myshop.orders

print(goods.price('珍珠奶茶'))
print(myshop.orders.count())

输出:

输出
12
3

6.1 绝对导入与相对导入

在包里面的模块之间互相导入时,有两种写法:

写法长什么样什么时候用
绝对导入from myshop import goods推荐,从顶层包名写起,清楚
相对导入from . import goods只在包内部用,. 表示当前包
相对导入(上一级)from .. import other少用,层级深了容易绕晕
相对导入有个硬规矩:它只能在「被当作包导入」的模块里用。如果你直接 python orders.py 运行包里的某个文件,那句 from . import goods 会报 ImportError: attempted relative import with no known parent package。
起步阶段配一条省心规则:包内部的一律写绝对导入,不碰相对导入。

七、Python 去哪儿找模块:sys.path

写 import mytools 时,Python 会按一个固定顺序去一串文件夹里翻。这串文件夹列在 sys.path 里:

Python
import sys
print('搜索清单第 1 项:', repr(sys.path[0]))
print('清单里一共几处:', len(sys.path))

输出:

输出
搜索清单第 1 项: ''
清单里一共几处: 6

sys.path[0] 那一项是空字符串 '',表示「当前正在运行的那个脚本所在的文件夹」。这就是为什么「把两个文件放在同一个文件夹」就能直接导入——第一个被翻的就是那里。

空字符串 '' 只在交互模式(或 python -c)里出现;把这两行存成 find.py 再运行,第 1 项打印的就是 find.py 所在文件夹的路径。「清单里一共几处」也因电脑和装的库而异,不用对数。

完整顺序大致是:

顺序找哪里说明
1脚本所在文件夹最常见的情况,也是你放自己的模块最省事的位置
2标准库目录装 Python 时自带的,math、os 都在这儿
3第三方库目录用 pip install 装进来的库,如 numpy

7.1 找不到时怎么办

先别急着改代码,按这个顺序排查:

  1. 确认文件名没写错:import mytool 找不到 mytools.py,就是差一个字母;
  2. 确认在同一个文件夹:脚本旁边有没有那个 .py;
  3. 确认没写 .py:import mytools.py 是错的写法;
  4. 确认文件名不叫 math.py 这类标准库名:同名会「遮住」标准库,非常难查;
  5. 实在不在同一层,才考虑 sys.path.append() 临时加路径(写法见下面)。

临时加一个搜索位置,可以这样(只对本次运行有效):

Python
import sys
before = len(sys.path)
sys.path.append('/tmp')
print('加之前有几处:', before)
print('加之后有几处:', len(sys.path))
print('最后一项是:', sys.path[-1])

输出:

输出
加之前有几处: 6
加之后有几处: 7
最后一项是: /tmp
「把模块和脚本放在同一个文件夹」能解决九成的找不到问题。sys.path 是排查工具,不是首选方案——很多新手学会了 sys.path.append 之后到处乱加,反而搞出更难查的问题。

八、虚拟环境:给项目一个独立的小房间

pip install 装的库,默认落在整个电脑共用的那一处。这会带来两个真实麻烦:

  1. A 项目要 pandas 1.x,B 项目要 pandas 2.x——全局只能留一个版本,必有一个跑不起来;
  2. 装了几十个库之后,你根本记不清哪个项目用了哪些,换台电脑就装不回同样的环境。

虚拟环境(virtual environment)就是给每个项目单独开一个小房间,房间里的库只属于这个项目。

8.1 三步走

输出
# 第一步:在项目文件夹里创建虚拟环境(文件夹名通常叫 venv)
python -m venv venv

# 第二步:激活它(Windows)
venv\Scripts\activate

# 第二步:激活它(macOS / Linux)
source venv/bin/activate

# 第三步:这时再装库,就只装在这个项目里
pip install requests

激活成功的标志很清楚:命令行提示符前面多了一对括号,写着环境名,像 (venv)。

8.2 那三个高频命令

命令
pip list                        # 看看这个环境里已经装了什么
pip freeze > requirements.txt   # 把当前清单导出成一个文件
pip install -r requirements.txt # 换台电脑时,按清单一次装回来

requirements.txt 是项目交接的关键:别人拿到你的项目,一条命令就能装出一样的环境。

命令作用什么时候用
python -m venv venv新建虚拟环境每个新项目开工时
venv\Scripts\activate激活(Windows)每次打开终端要开工时
source venv/bin/activate激活(macOS / Linux)每次打开终端要开工时
deactivate退出虚拟环境切到别的项目时
pip install 包名装库需要新库时
pip freeze > requirements.txt导出清单项目要交给别人 / 备份时
pip install -r requirements.txt按清单装拿到别人的项目时
虚拟环境文件夹(venv/)不要拷给别人、也不要用 git 提交。它体积大且和本机绑定,换台电脑就失效。要交付的是 requirements.txt,别人自己按清单重建。

8.3 让 TRAE 帮你打理虚拟环境

8.1 和 8.2 的那些命令不需要背——TRAE 的侧边 AI 对话框可以替你敲。按 Ctrl + U 打开(Mac 上是 Cmd + U),这个对话框的完整介绍见《4-Trae》。

新建并激活:刚建好项目文件夹,在对话框里说一句——

「在当前项目文件夹里新建一个名叫 venv 的虚拟环境,激活它,再装上 requests。」

AI 会把这句话拆成 8.1 的那几步,在底部终端里逐条执行:python -m venv venv、激活命令、pip install requests。完成后可以再补一句「跑一下 pip list」,看到 requests 出现在清单里,就说明库确实装进了这个项目自己的小房间。

更新已有环境:项目做了一半,环境要动,也照样一句话——

「按 requirements.txt 把虚拟环境的依赖更新一遍」「把 pandas 升级到最新版」,对应的正是 8.2 里的 pip install -r requirements.txt 和升级命令。换电脑拿到别人的项目时,一句「先建虚拟环境,再按 requirements.txt 装齐依赖」就能把整套环境复原出来。

想让 AI 做得又快又准,说话时记住三点:

AI 在终端执行命令前通常会先征求你的同意,看清命令再放行。它跑的就是 8.1、8.2 里那几条命令,只是替你敲——正因为命令你都学过,才看得懂它做得对不对。

课程思政|数据安全与法治素养 · 各归各的房间

虚拟环境给每个项目单独开一间房,库只装在自己屋里,不跟别的项目混在一起。边界清楚,才不会互相污染。把依赖写进 requirements.txt,交接时别人一条命令就能装出一样的环境——这既是工程规矩,也是一种数据素养:谁的数据、放在哪儿、谁有权动,心里都要有一道清楚的线。

九、写模块的几个好习惯

9.1 一个模块只干一类事

scores.py 就放和成绩有关的函数,files.py 就放和文件有关的。名字起得越具体,以后越好找。

9.2 给模块和函数写说明

在文件最上面写一段说明,它会自动成为 help(你的模块) 显示的内容。函数里第一行也可以写:

Python
# 文件:scores.py
'''成绩相关的工具函数。'''

def average(nums):
    '''算平均分:把一组分数的和除以个数。'''
    return sum(nums) / len(nums)

9.3 用 if __name__ == '__main__': 隔开「自测」

第五节讲过。把「随手试一下」的代码放进这个分支,导入你模块的人就不会被莫名其妙的输出打扰。

9.4 别用标准库的名字

把自己的文件命名为 csv.py、random.py、string.py、json.py、datetime.py 这类名字,会遮住同名的标准库。表现是:import csv 之后 csv.reader 不见了,报 AttributeError,而且换个文件夹又好了——极难排查。
避开办法很简单:自己的文件名加个前缀或用途词,比如 my_csv_tools.py、data_io.py。

9.5 别在两个模块里互相导入

Python
# a.py 里写了 from b import func_b
# b.py 里又写了 from a import func_a     ← 这就是「循环导入」
# 结果是导入报错,或者某个名字变成 None,问题很难定位

真需要共享的东西,抽到第三个模块(比如 common.py)里,两边都去导入它。

AI 助手小贴士|拆模块的目录结构有最佳实践,让 AI 给个起点。

打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:我要把课程项目拆成几个 .py 文件,请给一个目录结构建议:工具函数放哪、主流程放哪、什么时候需要 __init__.py

十、常见坑

坑 1:写成了 import mytools.py

Python
import pathlib, sys, tempfile
work = pathlib.Path(tempfile.mkdtemp())
(work / 'mytools.py').write_text('def add(a, b): return a + b\n', encoding='utf-8')
sys.path.insert(0, str(work))   # 文件明明就在这儿,还是导不进来

import mytools.py

运行后会报错(逐字照抄解释器):

报错
ModuleNotFoundError: No module named 'mytools.py'; 'mytools' is not a package

报错信息里那句话很值得读一遍:'mytools' is not a package——Python 把 mytools.py 理解成了「包 mytools 下面的模块 py」,于是去找一个根本不存在的包。

处理办法:导入写文件名去掉后缀,import mytools。

坑 2:自己的文件名和标准库撞名

假设你手边有个自己的文件叫 csv.py。Python 找模块时先翻你的文件夹,于是 import csv 拿到的是你的那个,标准库的 csv 被挡住了:

Python
import pathlib, sys, tempfile
work = pathlib.Path(tempfile.mkdtemp())
(work / 'csv.py').write_text('MARK = 1\n', encoding='utf-8')
sys.path.insert(0, str(work))

import csv
print('导入到的 csv 是:', csv.__file__.split('/')[-1])
print('它有 reader 吗:', hasattr(csv, 'reader'))

输出:

Python
导入到的 csv 是: csv.py
它有 reader 吗: False

接着用它,就会冒出这种「标准库明明有、我这里却没有」的报错:

Python
import pathlib, sys, tempfile
work = pathlib.Path(tempfile.mkdtemp())
(work / 'csv.py').write_text('MARK = 1\n', encoding='utf-8')
sys.path.insert(0, str(work))

import csv
print(csv.reader(['a,b']))

运行后会报错(逐字照抄解释器):

报错
AttributeError: module 'csv' has no attribute 'reader'

处理办法:给自己的文件换个名字(my_scores.py、data_io.py),别用 csv、random、string、json、datetime 这些标准库名。

有个例外值得知道:math 这类内置模块是直接编进解释器的,同名文件挡不住它。但像 csv、random、string 这些从文件加载的模块就会被挡住。不用去记哪些挡得住——统一避开标准库名就对了。

坑 3:模块不在同一个文件夹

Python
import mytools

运行后会报错(逐字照抄解释器):

报错
ModuleNotFoundError: No module named 'mytools'

处理办法:先确认 .py 和脚本在同一个文件夹;确实要跨层,再用第六节的办法处理路径。

坑 4:导入整个模块时,模块里的顶层代码会执行一遍

Python
import pathlib, sys, tempfile
work = pathlib.Path(tempfile.mkdtemp())
(work / 'noisy.py').write_text('print("我被导入了!")\n', encoding='utf-8')
sys.path.insert(0, str(work))
print('导入之前')
import noisy
print('导入之后')

输出:

输出
导入之前
我被导入了!
导入之后

处理办法:模块顶层只放「定义」(def / class / 常量),把会执行、会打印的代码放进 if __name__ == '__main__': 里。

坑 5:改了模块代码,但运行结果没变

Python 导入一个模块后会把它缓存起来,同一次运行里再导入不会重新读文件。在 IDLE 里改完 mytools.py 却看不到变化,就是这个原因。处理办法:重新运行一次程序(多数编辑器里是重新点运行),而不是在同一个会话里反复 import。

坑 6:相对导入只能在包内部用

Python
from . import something

运行后会报错(逐字照抄解释器):

报错
ImportError: attempted relative import with no known parent package

处理办法:别直接运行包里的单个文件;或者在包内部统一改成绝对导入(第六节 6.1)。

十一、报错手册

报错原文都是从本机解释器逐字抄下来的,对照着找就行。

报错原文(最后一行)什么情况怎么改
ModuleNotFoundError: No module named 'mytools'找不到这个模块:文件名错、不在同一文件夹、或没装核对文件名与位置;第三方库先 pip install
ModuleNotFoundError: No module named 'mytools.py'; 'mytools' is not a package写成 import mytools.py 了去掉 .py,写 import mytools
ImportError: attempted relative import with no known parent package在包外面(或直接运行包内文件)用了相对导入改成绝对导入,或从包的外层运行
ImportError: cannot import name 'add' from 'mytools'模块找到了,但里面没有这个名字检查名字拼写;确认函数真的定义在模块里
AttributeError: module 'csv' has no attribute 'reader'标准库被同名的自己文件遮住了给自己的文件改名,别用标准库名
SyntaxError: invalid syntax(出现在 import 行)漏了逗号、括号,或路径写法不对对照本节前面的正确写法逐字核

下面这几条是「不报错,但结果不对」的,比报错更值得当心:

现象原因怎么改
导入时冒出一堆打印模块顶层有会执行的代码挪进 if __name__ == '__main__':
改了模块,运行结果还是老样子模块被缓存了重新运行整个程序
from mytools import * 之后名字乱撞星号导入倒进了太多名字改成 import mytools 或点名导入
自己定义的变量把标准库函数覆盖了import * 或重合命名去掉星号导入,变量别起成 list、sum

十二、常见问题

Q:模块和包到底怎么区分?
模块是一个 .py 文件;包是一个装着模块的文件夹。用起来都是 import,区别只在「导入的名词前面有没有文件夹那一层」,比如 import goods 是模块,from myshop import goods 里的 myshop 是包。

Q:自己的模块要不要加 __init__.py?
现代 Python 不加也能当包用。但建议加上(可以是空文件),一是更明确,二是很多工具和老教程按这个约定来。

Q:if __name__ == '__main__': 一定要写吗?
不是「一定」,但写上是好习惯。判断标准很简单:这个文件会不会被别人导入? 会,就写;如果它只是个一次性的小脚本,不写也无所谓。

Q:虚拟环境非用不可吗?
只装一两个库、只做练习时可以不用。但只要开始「同时做两个项目」或者「要交给别人」,就该用——否则版本冲突迟早找上门。

Q:为什么我 import 自己的模块总是报错,用别人的库就没事?
因为第三方库装在了 Python 的搜索目录里,而你的文件在哪儿,Python 并不知道。记住那条最省事的规则:把模块和用它的脚本放进同一个文件夹。

Q:一个项目里文件怎么组织比较好?
起步阶段的够用版本:项目文件夹里放「入口脚本」(如 main.py)+ 几个功能模块(scores.py、files.py)+ 一个放数据的文件夹 + 一个 venv/。功能变多了,再把同类模块收进一个包。

AI 助手小贴士|模块、包、库三个词总被混着用,让 AI 辨析清楚;pip 的报错也能直接贴给它(虚拟环境的打理见第八节 8.3)。

打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:初学者常混淆 Python 的模块、包、库这三个词。请用文件夹类比讲清它们的关系,再说明 pip、import、requirements.txt 各负责哪一环

十三、随堂自测(12 题)

点击选项即可判分,答错的题会立刻显示解析。

已答 0 / 12 · 正确 0 —

1自己写了一个 mytools.py,正确的导入写法是?

  • A. import mytools.py as mytools
  • B. import mytools.py
  • C. import mytools
  • D. from mytools.py import add
答案:C。导入名 = 文件名去掉 .py。写成 import mytools.py 会报 ModuleNotFoundError: No module named 'mytools.py',因为 Python 把 mytools.py 当成「包 mytools 下面的 py」,而不是文件名。

2自己的模块写好了,但一导入就报 ModuleNotFoundError,最先该检查的是?

  • A. Python 是不是装错了
  • B. 文件是不是和脚本放在同一个文件夹、名字有没有拼错
  • C. 电脑要不要重启
  • D. 是不是该用 from 而不是 import
答案:B。Python 搜索模块时第一站就是「当前脚本所在文件夹」,所以「同目录 + 名字拼对」能解决九成的找不到问题。四条排查顺序是:文件名拼写 → 是否同目录 → 有没有多写 .py → 是否和标准库撞名。

3关于 if __name__ == '__main__':,下面说法正确的是?

  • A. 这个文件被直接运行时,__name__ 的值是 '__main__'
  • B. 这个文件被 import 时,if 里面的代码也会执行
  • C. __name__ 只能在主文件里用
  • D. 写不写对程序没有任何影响
答案:A。直接运行的入口文件 __name__ 是 '__main__';被导入时是模块自己的名字(比如 'greet')。所以 if 里的代码只有在直接运行时才执行——这正是用它隔开「自测代码」的原理。

4下面哪种导入写法被建议不要用?

  • A. import math
  • B. from math import sqrt
  • C. import math as m
  • D. from math import *
答案:D。星号导入会把模块里所有公开名字倒进你的文件,容易覆盖你自己的变量、也让 dir() 变得无法阅读。前三种写法各有场合:import 模块名 最不容易乱,from ... import 短但要小心重名,as 适合名字很长的库。

5文件夹 myshop/ 里有一个 goods.py,想用里面的 price() 函数,写法正确的是?

  • A. import myshop/goods
  • B. from myshop import goods,然后 goods.price()
  • C. from myshop.goods import *,这是唯一正确写法
  • D. import myshop-goods
答案:B。包名和模块名之间用点号连接:from myshop import goods 之后写 goods.price();也可以 import myshop.goods,之后写 myshop.goods.price()。斜杠、连字符都不是 Python 的写法。

6包里的模块想导入同包的另一个模块,下面哪种写法被推荐?

  • A. 绝对导入 from myshop import goods
  • B. 相对导入 from . import goods,且只能这么写
  • C. 写文件路径 from myshop/goods import price
  • D. 用 include 语句
答案:A。包内部的一律推荐绝对导入,清楚又不容易出错。相对导入 from . import goods 虽然合法,但它有个硬规矩:只能在该模块被当作包的一部分导入时使用——直接运行包里的单个文件就会报 ImportError: attempted relative import with no known parent package。

7dir(math) 能告诉你什么?

  • A. math 模块里一共有多少个函数被调用过
  • B. math 模块装在哪台电脑上
  • C. math 模块里可用的名字有哪些
  • D. math 模块每个函数怎么用
答案:C。dir() 列出模块里的全部名字(函数、常量、类型),带双下划线的可以先忽略。想看「每个东西怎么用」要用 help(),它读的是模块里的文档字符串。

8在包内部写 from . import goods,如果直接运行这个包内的文件,会怎样?

  • A. 完全正常,和导入时一样
  • B. 只会警告一下,继续运行
  • C. 自动改成绝对导入
  • D. 报 ImportError: attempted relative import with no known parent package
答案:D。相对导入要靠「包」这个上下文才知道 . 指的是谁。直接运行包内的单个文件时没有这个上下文,就报错。处理办法:在包内部统一改成绝对导入,或者不要直接运行包内的文件。

9下面哪个文件名最容易被建议改掉?

  • A. math.py
  • B. my_scores.py
  • C. score_utils.py
  • D. class_report.py
答案:A。math.py 和标准库同名,会把它「遮住」——之后 import math 拿到的是你的文件,math.sqrt() 就消失了,报 AttributeError。同类要避开的还有 random.py、json.py、csv.py、string.py、datetime.py。

10虚拟环境(venv)主要解决什么问题?

  • A. 让 Python 跑得更快
  • B. 让不同项目用各自的库版本,互不干扰
  • C. 自动帮你写代码注释
  • D. 把代码打包成 exe
答案:B。全局装库时两个项目抢同一个库的版本,必有一个跑不起来。虚拟环境给每个项目一个独立的小房间。要交给别人的是 pip freeze > requirements.txt 导出的清单,不是 venv/ 文件夹本身。

11改完 mytools.py 里的代码,重新导入却没变化,最可能的原因是?

  • A. 必须把模块放进包里才能生效
  • B. Python 坏了,要重装
  • C. 文件名写错了
  • D. 模块已经被缓存,需要重新运行整个程序
答案:D。Python 导入过的模块会缓存在 sys.modules 里,同一次运行中再 import 不会重读文件。正确做法是重新运行整个程序,而不是在同一个会话里反复写 import。

12一个文件既能被直接运行、又能被别人导入,下面哪个做法最合理?

  • A. 把测试用的打印和演示调用直接写在文件末尾
  • B. 禁止别人导入这个文件
  • C. 把「定义」放在顶层,把「演示/测试」放进 if __name__ == '__main__': 里
  • D. 把演示代码写进 __init__.py
答案:C。顶层只放 def / class / 常量,别人导入时不会有输出冒出来;演示和测试放进 if __name__ == '__main__':,只有自己直接运行时才执行。这是模块「既能当工具、又能当脚本」的标准写法。