文章目录
-
- 第一部分:为什么要把类放到独立文件?
-
- [1.1 一切都写在一个文件里的问题](#1.1 一切都写在一个文件里的问题)
- [1.2 把类分到独立文件,再导入使用](#1.2 把类分到独立文件,再导入使用)
- 第二部分:基础准备------创建一个包含类的模块文件
-
- [2.1 什么是模块?](#2.1 什么是模块?)
- [2.2 创建示例文件](#2.2 创建示例文件)
- 第三部分:五种导入方式
-
- [方式一:导入整个模块 `import 模块名`](#方式一:导入整个模块
import 模块名) - [方式二:从模块中导入指定的类 `from 模块名 import 类名`](#方式二:从模块中导入指定的类
from 模块名 import 类名) - 方式三:从模块中导入多个类
- 方式四:使用别名(as)
- [方式五:导入模块中的所有类 `from 模块名 import *`](#方式五:导入模块中的所有类
from 模块名 import *) - 五种方式对比总结
- [方式一:导入整个模块 `import 模块名`](#方式一:导入整个模块
- [第四部分:在模块文件里使用 `if name == "main"`](#第四部分:在模块文件里使用
if __name__ == "__main__") -
- [4.1 问题:直接在模块里写测试代码会干扰导入](#4.1 问题:直接在模块里写测试代码会干扰导入)
- [4.2 解决方案:`if name == "main":`](#4.2 解决方案:
if __name__ == "__main__":)
- 第五部分:把多个类分散到多个文件
-
- [5.1 文件结构](#5.1 文件结构)
- 第六部分:使用包(文件夹)组织多个模块
-
- [6.1 什么是包?](#6.1 什么是包?)
- [6.2 创建包结构](#6.2 创建包结构)
- [6.3 在主程序里从包中导入](#6.3 在主程序里从包中导入)
- [第七部分:`init.py` 的妙用](#第七部分:
__init__.py的妙用) -
- [7.1 在 `init.py` 里统一导出](#7.1 在
__init__.py里统一导出)
- [7.1 在 `init.py` 里统一导出](#7.1 在
- 第八部分:相对导入与绝对导入
-
- [8.1 绝对导入(推荐)](#8.1 绝对导入(推荐))
- [8.2 相对导入(仅在包内部使用)](#8.2 相对导入(仅在包内部使用))
- 第九部分:完整综合示例
-
- [9.1 项目结构](#9.1 项目结构)
- [9.2 各文件内容](#9.2 各文件内容)
- 第十部分:常见陷阱与注意事项
-
- [10.1 陷阱一:被导入的文件和当前文件不在同一目录](#10.1 陷阱一:被导入的文件和当前文件不在同一目录)
- [10.2 陷阱二:循环导入(Circular Import)](#10.2 陷阱二:循环导入(Circular Import))
- [10.3 陷阱三:导入的文件名和标准库冲突](#10.3 陷阱三:导入的文件名和标准库冲突)
- [10.4 陷阱四:修改了模块文件,但运行时还是旧版本](#10.4 陷阱四:修改了模块文件,但运行时还是旧版本)
- [10.5 陷阱五:`from module import *` 污染命名空间](#10.5 陷阱五:
from module import *污染命名空间)
- 第十一部分:查看模块信息的常用技巧
-
- [11.1 查看模块有哪些内容(dir())](#11.1 查看模块有哪些内容(dir()))
- [11.2 查看类在哪个模块里定义的(module)](#11.2 查看类在哪个模块里定义的(module))
- [11.3 查看模块的文档字符串(doc)](#11.3 查看模块的文档字符串(doc))
- [11.4 查看模块文件的位置(file)](#11.4 查看模块文件的位置(file))
- 第十二部分:小结
-
- [12.1 导入方式速查](#12.1 导入方式速查)
- [12.2 核心原则总结](#12.2 核心原则总结)
https://www.quanzhankaige.com/python18/
本文档面向零基础新手,目标是让你真正理解:
- 为什么要把类放到独立文件里
- 怎么把一个文件里的类导入到另一个文件使用
- 五种导入方式各自的写法、区别和适用场景
- 多个类如何组织到多个文件(模块)
- 包(文件夹)是什么,怎么用
- 常见陷阱与注意事项
配有大量可运行示例。
第一部分:为什么要把类放到独立文件?
1.1 一切都写在一个文件里的问题
假设你在写一个学生管理系统,所有代码都放在 main.py 里:
main.py
├── class Student: ...(100行)
├── class Teacher: ...(80行)
├── class Course: ...(60行)
└── 主程序逻辑 ...(50行)
随着项目变大,这个文件会越来越长,越来越难找、难改。
1.2 把类分到独立文件,再导入使用
更好的做法是:每个类(或一组相关的类)单独存放在一个 .py 文件里,需要用时再导入:
student.py ← 存放 Student 类
teacher.py ← 存放 Teacher 类
course.py ← 存放 Course 类
main.py ← 主程序,从上面的文件里导入需要的类
好处:
- 每个文件职责单一,容易查找
- 多人协作时互不干扰
- 类可以被多个项目复用
第二部分:基础准备------创建一个包含类的模块文件
2.1 什么是模块?
Python 里,一个 .py 文件就是一个模块(module) 。模块可以被其他 .py 文件导入和使用。
2.2 创建示例文件
我们先创建一个文件 car.py,里面定义一个 Car 类:
文件:car.py
python
"""汽车相关的类"""
class Car:
"""描述一辆汽车"""
def __init__(self, make, model, year):
"""初始化汽车属性"""
self.make = make # 品牌
self.model = model # 型号
self.year = year # 年份
self.odometer = 0 # 里程表,默认 0
def get_info(self):
"""返回汽车描述"""
return f"{self.year} {self.make} {self.model}"
def read_odometer(self):
"""读取里程表"""
print(f"里程表:{self.odometer} 公里")
def update_odometer(self, km):
"""更新里程表(不允许倒拨)"""
if km >= self.odometer:
self.odometer = km
else:
print("不能倒拨里程表!")
def increment_odometer(self, km):
"""增加里程"""
self.odometer += km
文件保存好之后,我们就可以在另一个文件里导入并使用这个 Car 类了。
注意:
car.py(被导入的文件)和main.py(你写代码的文件)必须放在同一个文件夹里,才能直接导入。
第三部分:五种导入方式
方式一:导入整个模块 import 模块名
语法:
python
import car # 导入整个 car.py 模块
使用:
python
import car
# 必须用"模块名.类名"来创建实例
my_car = car.Car("丰田", "凯美瑞", 2022)
print(my_car.get_info()) # 2022 丰田 凯美瑞
my_car.read_odometer() # 里程表:0 公里
特点:
- 导入的是整个模块,使用时前面要加
car. - 好处:一眼看出
Car来自哪个模块,代码可读性强 - 适合:模块内类较多,想保持命名空间清晰时
方式二:从模块中导入指定的类 from 模块名 import 类名
语法:
python
from car import Car # 只从 car.py 里导入 Car 这一个类
使用:
python
from car import Car
# 直接用类名,不需要加模块前缀
my_car = Car("本田", "雅阁", 2021)
print(my_car.get_info()) # 2021 本田 雅阁
特点:
- 使用时直接写
Car,不需要car.Car - 好处:代码更简洁
- 注意:如果你自己文件里也定义了一个叫
Car的东西,会发生命名冲突
方式三:从模块中导入多个类
语法:
python
from 模块名 import 类名1, 类名2, 类名3
假设 car.py 里现在有两个类:
文件:car.py(更新版,新增 ElectricCar)
python
"""汽车相关的类"""
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer = 0
def get_info(self):
return f"{self.year} {self.make} {self.model}"
def read_odometer(self):
print(f"里程表:{self.odometer} 公里")
def update_odometer(self, km):
if km >= self.odometer:
self.odometer = km
else:
print("不能倒拨里程表!")
def increment_odometer(self, km):
self.odometer += km
class ElectricCar(Car):
"""电动汽车,继承自 Car"""
def __init__(self, make, model, year, battery_size=75):
super().__init__(make, model, year)
self.battery_size = battery_size # 电池容量(度)
def describe_battery(self):
print(f"电池容量:{self.battery_size} 度电")
def get_info(self):
return super().get_info() + "(电动)"
导入多个类:
python
from car import Car, ElectricCar # 用逗号分隔,同时导入两个类
my_car = Car("大众", "帕萨特", 2020)
my_tesla = ElectricCar("特斯拉", "Model 3", 2023, battery_size=82)
print(my_car.get_info()) # 2020 大众 帕萨特
print(my_tesla.get_info()) # 2023 特斯拉 Model 3(电动)
my_tesla.describe_battery() # 电池容量:82 度电
方式四:使用别名(as)
4.1 给类起别名
当类名太长,或者和当前文件里的名字冲突时,可以起一个别名:
python
from car import ElectricCar as EC
car1 = EC("比亚迪", "海豹", 2024)
print(car1.get_info()) # 2024 比亚迪 海豹(电动)
4.2 给模块起别名
python
import car as c # 给整个模块起别名
my_car = c.Car("奥迪", "A6", 2023)
print(my_car.get_info()) # 2023 奥迪 A6
什么时候用别名?
- 类名很长,写起来繁琐
- 两个不同模块里有同名的类,需要区分
- 项目里有约定俗成的缩写(如
import numpy as np)
方式五:导入模块中的所有类 from 模块名 import *
语法:
python
from car import * # 导入 car.py 里的所有公开类和变量
使用:
python
from car import *
my_car = Car("雪佛兰", "迈锐宝", 2019)
my_tesla = ElectricCar("特斯拉", "Model Y", 2023)
print(my_car.get_info()) # 2019 雪佛兰 迈锐宝
print(my_tesla.get_info()) # 2023 特斯拉 Model Y(电动)
强烈不推荐 import *,原因:
- 你不知道从模块里导入了哪些名字
- 如果模块里有和你当前文件同名的变量/类,会悄悄覆盖,极难排查
- 代码阅读者无法判断某个名字来自哪里
建议: 绝大多数情况下,用方式二(
from car import Car)或方式一(import car)。
五种方式对比总结
| 方式 | 语法 | 使用时 | 推荐度 |
|---|---|---|---|
| 导入整个模块 | import car |
car.Car(...) |
⭐⭐⭐⭐⭐ |
| 导入指定类 | from car import Car |
Car(...) |
⭐⭐⭐⭐⭐ |
| 导入多个类 | from car import Car, ElectricCar |
Car(...) / ElectricCar(...) |
⭐⭐⭐⭐⭐ |
| 起别名 | import car as c / from car import Car as C |
c.Car(...) / C(...) |
⭐⭐⭐⭐(按需使用) |
| 导入全部 | from car import * |
Car(...) |
⭐(不推荐) |
第四部分:在模块文件里使用 if __name__ == "__main__"
4.1 问题:直接在模块里写测试代码会干扰导入
假设 car.py 底部有这样的测试代码:
python
# car.py 末尾
my_car = Car("测试", "测试车", 2023)
print(my_car.get_info()) # 这行在被其他文件导入时也会执行!
当别人 import car 时,这段测试代码会自动运行,打印出不该有的内容。
4.2 解决方案:if __name__ == "__main__":
Python 中每个文件都有一个特殊变量 __name__:
- 直接运行该文件时 :
__name__的值是"__main__" - 被其他文件导入时 :
__name__的值是模块名(如"car")
所以,把只想在直接运行时执行的代码放在这个判断里:
python
# car.py
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer = 0
def get_info(self):
return f"{self.year} {self.make} {self.model}"
def read_odometer(self):
print(f"里程表:{self.odometer} 公里")
def update_odometer(self, km):
if km >= self.odometer:
self.odometer = km
else:
print("不能倒拨里程表!")
def increment_odometer(self, km):
self.odometer += km
# 只有直接运行 car.py 时,下面的代码才会执行
# 被其他文件 import 时,下面的代码不会执行
if __name__ == "__main__":
my_car = Car("丰田", "凯美瑞", 2022)
print(my_car.get_info())
my_car.increment_odometer(500)
my_car.read_odometer()
这是编写模块文件的标准做法,请务必养成这个习惯!
第五部分:把多个类分散到多个文件
5.1 文件结构
随着项目变大,可以把不同的类放到不同文件:
my_project/
├── car.py ← 存放 Car 类
├── electric_car.py ← 存放 ElectricCar 类
└── main.py ← 主程序
文件:car.py
python
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer = 0
def get_info(self):
return f"{self.year} {self.make} {self.model}"
def read_odometer(self):
print(f"里程表:{self.odometer} 公里")
def update_odometer(self, km):
if km >= self.odometer:
self.odometer = km
else:
print("不能倒拨里程表!")
def increment_odometer(self, km):
self.odometer += km
文件:electric_car.py
python
from car import Car # ElectricCar 继承 Car,所以先导入 Car
class ElectricCar(Car):
def __init__(self, make, model, year, battery_size=75):
super().__init__(make, model, year)
self.battery_size = battery_size
def describe_battery(self):
print(f"电池容量:{self.battery_size} 度电")
def get_info(self):
return super().get_info() + "(电动)"
文件:main.py
python
from car import Car
from electric_car import ElectricCar
# 使用普通汽车
my_car = Car("大众", "帕萨特", 2020)
print(my_car.get_info()) # 2020 大众 帕萨特
my_car.increment_odometer(300)
my_car.read_odometer() # 里程表:300 公里
# 使用电动汽车
my_ev = ElectricCar("比亚迪", "汉", 2023, 100)
print(my_ev.get_info()) # 2023 比亚迪 汉(电动)
my_ev.describe_battery() # 电池容量:100 度电
第六部分:使用包(文件夹)组织多个模块
6.1 什么是包?
当你的模块越来越多,可以用文件夹 来分组。带有 __init__.py 文件的文件夹就叫做包(Package)。
6.2 创建包结构
my_project/
├── vehicles/ ← 这是一个包(文件夹)
│ ├── __init__.py ← 这个文件让 Python 把文件夹识别为包(可以为空)
│ ├── car.py ← Car 类
│ └── electric_car.py ← ElectricCar 类
└── main.py ← 主程序
文件:vehicles/__init__.py(可以为空,也可以写内容)
python
# 空文件,或者可以写:
# from .car import Car
# from .electric_car import ElectricCar
文件:vehicles/car.py(和前面一样)
python
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer = 0
def get_info(self):
return f"{self.year} {self.make} {self.model}"
def read_odometer(self):
print(f"里程表:{self.odometer} 公里")
def increment_odometer(self, km):
self.odometer += km
文件:vehicles/electric_car.py
python
from vehicles.car import Car # 注意:导入时要带包名
class ElectricCar(Car):
def __init__(self, make, model, year, battery_size=75):
super().__init__(make, model, year)
self.battery_size = battery_size
def describe_battery(self):
print(f"电池容量:{self.battery_size} 度电")
def get_info(self):
return super().get_info() + "(电动)"
6.3 在主程序里从包中导入
文件:main.py
python
# 写法一:从包里的模块导入类
from vehicles.car import Car
from vehicles.electric_car import ElectricCar
my_car = Car("奔驰", "C级", 2022)
print(my_car.get_info()) # 2022 奔驰 C级
my_ev = ElectricCar("特斯拉", "Model S", 2023, 100)
print(my_ev.get_info()) # 2023 特斯拉 Model S(电动)
python
# 写法二:导入整个子模块
import vehicles.car as car_module
my_car = car_module.Car("宝马", "5系", 2021)
print(my_car.get_info()) # 2021 宝马 5系
第七部分:__init__.py 的妙用
7.1 在 __init__.py 里统一导出
如果你希望用户从包导入时更方便,可以在 __init__.py 里提前把类导入进来:
文件:vehicles/__init__.py
python
from .car import Car # 相对导入:. 代表当前包
from .electric_car import ElectricCar
这样在主程序里就可以直接这样写:
python
from vehicles import Car, ElectricCar # 不用再指定具体的子模块名了
my_car = Car("奥迪", "A4", 2023)
my_ev = ElectricCar("小鹏", "P7", 2023)
print(my_car.get_info()) # 2023 奥迪 A4
print(my_ev.get_info()) # 2023 小鹏 P7(电动)
第八部分:相对导入与绝对导入
8.1 绝对导入(推荐)
从项目根目录开始写完整路径:
python
from vehicles.car import Car # 绝对导入
8.2 相对导入(仅在包内部使用)
在包内部的文件互相导入时,可以用 .(当前包)和 ..(上一级包):
python
# 在 vehicles/electric_car.py 里
from .car import Car # . 代表当前包(vehicles),等价于 from vehicles.car import Car
python
# 如果有更深的嵌套,.. 代表上一级包
from ..utils import helper # .. 上一级
新手建议: 在包内部文件互相导入时用相对导入(.),主程序里用绝对导入。
第九部分:完整综合示例
9.1 项目结构
school/ ← 项目文件夹
├── models/ ← 包
│ ├── __init__.py
│ ├── student.py ← Student 类
│ └── teacher.py ← Teacher 类
└── main.py ← 主程序
9.2 各文件内容
文件:models/student.py
python
"""学生类"""
class Student:
def __init__(self, name, student_id, grade):
self.name = name
self.student_id = student_id
self.grade = grade # 年级
self.scores = {} # 科目:分数
def add_score(self, subject, score):
self.scores[subject] = score
def average_score(self):
if not self.scores:
return 0
return sum(self.scores.values()) / len(self.scores)
def info(self):
avg = self.average_score()
return (f"学生:{self.name}(学号:{self.student_id})"
f" 年级:{self.grade} 平均分:{avg:.1f}")
if __name__ == "__main__":
s = Student("张三", "S001", "高二")
s.add_score("数学", 90)
s.add_score("英语", 85)
print(s.info())
文件:models/teacher.py
python
"""教师类"""
class Teacher:
def __init__(self, name, teacher_id, subject):
self.name = name
self.teacher_id = teacher_id
self.subject = subject # 任教科目
self.students = [] # 负责的学生列表
def add_student(self, student):
self.students.append(student)
print(f"{student.name} 已加入 {self.name} 老师的班级")
def class_average(self):
"""计算全班该科目的平均分"""
scores = []
for s in self.students:
if self.subject in s.scores:
scores.append(s.scores[self.subject])
if not scores:
return 0
return sum(scores) / len(scores)
def info(self):
avg = self.class_average()
return (f"教师:{self.name}(工号:{self.teacher_id})"
f" 科目:{self.subject} 班级平均分:{avg:.1f}")
if __name__ == "__main__":
t = Teacher("李老师", "T001", "数学")
print(t.info())
文件:models/__init__.py
python
from .student import Student
from .teacher import Teacher
文件:main.py
python
from models import Student, Teacher # 从包里导入,简洁明了
# 创建学生
s1 = Student("张三", "S001", "高二")
s2 = Student("李四", "S002", "高二")
s3 = Student("王五", "S003", "高二")
# 录入数学成绩
s1.add_score("数学", 92)
s2.add_score("数学", 78)
s3.add_score("数学", 85)
# 创建教师
t = Teacher("刘老师", "T001", "数学")
t.add_student(s1)
t.add_student(s2)
t.add_student(s3)
# 打印信息
print()
for s in [s1, s2, s3]:
print(s.info())
print()
print(t.info())
运行结果:
张三 已加入 刘老师 老师的班级
李四 已加入 刘老师 老师的班级
王五 已加入 刘老师 老师的班级
学生:张三(学号:S001) 年级:高二 平均分:92.0
学生:李四(学号:S002) 年级:高二 平均分:78.0
学生:王五(学号:S003) 年级:高二 平均分:85.0
教师:刘老师(工号:T001) 科目:数学 班级平均分:85.0
第十部分:常见陷阱与注意事项
10.1 陷阱一:被导入的文件和当前文件不在同一目录
报错: ModuleNotFoundError: No module named 'car'
原因: Python 默认只在当前运行脚本所在的目录里查找模块。
解决:
- 确保两个文件在同一文件夹里
- 或者把模块放到标准库目录、或用包的形式管理
10.2 陷阱二:循环导入(Circular Import)
a.py 导入 b.py,同时 b.py 又导入 a.py,就会产生循环导入,报 ImportError。
错误示例:
python
# a.py
from b import B
class A:
pass
python
# b.py
from a import A # 循环!a 导入 b,b 又导入 a
class B:
pass
解决方案:
- 把公共的类单独抽到第三个文件
common.py,让a.py和b.py都从common.py导入 - 或者在函数/方法内部做局部导入(进阶技巧,新手避免这种结构)
10.3 陷阱三:导入的文件名和标准库冲突
错误示例:
python
# 你创建了一个叫 random.py 的文件
# 然后在里面写 import random
# 此时导入的是你自己的 random.py,而不是标准库的 random 模块!
解决: 不要给自己的文件起和 Python 标准库相同的名字,如:
random.py、os.py、sys.py、math.py、string.py等都要避免
10.4 陷阱四:修改了模块文件,但运行时还是旧版本
原因: Python 会缓存已经导入的模块(.pyc 文件)。
解决:
- 重启 Python 解释器(关掉终端重新打开)
- 删除
__pycache__文件夹
10.5 陷阱五:from module import * 污染命名空间
python
# helper.py
def open(): # 定义了一个叫 open 的函数
print("自定义 open")
# main.py
from helper import * # 导入了 helper.open,覆盖了 Python 内置的 open()!
open("file.txt") # 调用的是 helper.open,而不是内置的文件打开函数,报错!
解决: 避免使用 import *,明确写出要导入的名字。
第十一部分:查看模块信息的常用技巧
11.1 查看模块有哪些内容(dir())
python
import car
print(dir(car)) # 列出 car 模块里所有的名字(包括类、函数、变量)
11.2 查看类在哪个模块里定义的(module)
python
from car import Car
print(Car.__module__) # car(告诉你 Car 来自 car 模块)
11.3 查看模块的文档字符串(doc)
python
import car
print(car.__doc__) # 汽车相关的类(car.py 文件顶部的注释)
11.4 查看模块文件的位置(file)
python
import car
print(car.__file__) # C:\Users\...\car.py(告诉你这个模块文件在哪里)
第十二部分:小结
12.1 导入方式速查
python
# 方式一:导入整个模块
import car
my_car = car.Car("丰田", "凯美瑞", 2022)
# 方式二:从模块导入指定类
from car import Car
my_car = Car("丰田", "凯美瑞", 2022)
# 方式三:从模块导入多个类
from car import Car, ElectricCar
# 方式四a:给类起别名
from car import ElectricCar as EC
my_ev = EC("特斯拉", "Model 3", 2023)
# 方式四b:给模块起别名
import car as c
my_car = c.Car("丰田", "凯美瑞", 2022)
# 方式五:导入全部(不推荐)
from car import *
12.2 核心原则总结
| 原则 | 说明 |
|---|---|
| 一个文件一个职责 | 相关的类放在同一个 .py 文件里 |
| 优先用明确导入 | 用 from car import Car,而非 from car import * |
写 if __name__ == "__main__": |
模块里的测试代码放在这个判断里,防止被导入时自动执行 |
| 避免文件名和标准库冲突 | 不要创建 random.py、os.py 等与标准库同名的文件 |
| 用包组织大型项目 | 多个模块放在带 __init__.py 的文件夹里 |
| 在包内部用相对导入 | . 表示当前包,.. 表示上级包 |
| 避免循环导入 | 公共类单独放在第三个文件里 |