python模块入门
1️⃣ 单文件模块
最简单的模块就是一个 .py 文件。
例如,你有一个文件 mymodule.py:
python
# mymodule.py
def add(a, b):
return a + b
def sub(a, b):
return a - b在同一个目录下的另一个文件里可以直接导入:
python
# main.py
import mymodule
print(mymodule.add(3, 5)) # 输出 82️⃣ 包(多模块)
如果模块比较多,可以组织成包。
目录结构示例:
mypackage/
├── __init__.py
├── math_ops.py
└── string_ops.py__init__.py可以为空,或者导入一些常用接口math_ops.py:
python
def add(a, b):
return a + bstring_ops.py:
python
def hello(name):
return f"Hello, {name}"使用方法:
python
from mypackage import math_ops, string_ops
print(math_ops.add(2,3))
print(string_ops.hello("Tom"))或者在 __init__.py 里统一导出:
python
from .math_ops import add
from .string_ops import hello然后可以直接:
python
from mypackage import add, hello3️⃣ 安装为可复用模块(发布或本地安装)
如果你想让模块在任何地方都能 import,需要打包安装。
方法一:本地安装
- 在项目根目录下创建
setup.py:
python
from setuptools import setup, find_packages
setup(
name="mypackage",
version="0.1.0",
packages=find_packages(),
)- 安装:
bash
py -m pip install -e . # 可编辑安装,修改代码即时生效之后在任何地方都可以:
python
import mypackage方法二:发布到 PyPI
- 打包:
bash
python setup.py sdist bdist_wheel- 上传到 PyPI:
bash
py -m twine upload dist/*其他人就可以:
bash
py -m pip install mypackage4️⃣ 注意事项
- 模块名和包名不要和标准库冲突(比如不要叫
os.py) - 文件名遵循小写加下划线风格,例如
my_module.py - 包含
__init__.py表示这是一个包 - 如果是发布包,最好写
setup.py或pyproject.toml来声明元信息
✅ 总结:
| 类型 | 文件结构 | 导入方式 |
|---|---|---|
| 单文件模块 | mymodule.py | import mymodule |
| 包(多模块) | mypackage/__init__.py + 多文件 | from mypackage import submodule |
| 可复用模块 | 打包 + 安装 | pip install mypackage → import mypackage |
1️⃣ 声明依赖
(1) setup.py(传统方式,setuptools)
python
from setuptools import setup
setup(
name="mypackage",
version="0.1.0",
install_requires=[
"numpy>=1.26",
"pandas>=2.0"
],
)install_requires就是这个包的依赖列表- 还可以指定版本范围:
numpy>=1.26,<2.0
(2) pyproject.toml(现代方式,Poetry / Flit)
toml
[tool.poetry.dependencies]
python = "^3.10"
numpy = "^1.26"
pandas = "^2.0"- 现代工具推荐这种方式,更规范
2️⃣ 打包时生成 METADATA
- 使用工具打包:
bash
python setup.py sdist bdist_wheel # setuptools
poetry build # poetry- 打包工具会自动在生成的
.whl或.tar.gz包里创建.dist-info/METADATA - METADATA 内容示例:
Name: mypackage
Version: 0.1.0
Requires-Dist: numpy >=1.26
Requires-Dist: pandas >=2.03️⃣ pip 安装时读取 METADATA
当你执行:
bash
py -m pip install mypackage.whlpip 会:
- 读取
.dist-info/METADATA - 解析
Requires-Dist字段,知道需要先安装哪些依赖 - 递归解析依赖,生成依赖树
- 下载并安装所有缺失的依赖
✅ 总结流程
- 开发者在 setup.py / pyproject.toml 声明依赖
- 打包工具生成 .dist-info/METADATA
- pip 安装时读取 METADATA → 解析依赖 → 安装依赖
运行文件
__name__是内置变量,如果是直接运行py文件,name__会等于__main,否则等于文件名
if name == 'main': 直接文件时运行的代码
在 Python 中,区分“需要导出给外部使用的函数”和“内部使用函数”主要依赖命名约定和模块设计理念,而不是像一些语言需要显式声明 public/private。我帮你整理一下方法:
1️⃣ 单下划线 _ 表示内部函数
- 在模块或包里,如果函数或变量前加 单下划线,表示这是内部实现,不希望被外部直接使用。
- Python 并不会强制阻止导入,只是 约定俗成。
python
# mymodule.py
def public_func():
print("这是对外的函数")
def _internal_func():
print("这是内部函数")在外部:
python
from mymodule import *
public_func() # 可以使用
_internal_func() # 也可以访问,但不推荐from module import *时,默认不会导入_internal_func。
2️⃣ 双下划线 __ 用于类的内部函数
- 如果函数在 类里,前加双下划线会触发 名称重整(name mangling),避免被外部轻易访问。
python
class MyClass:
def public_method(self):
print("外部调用")
def __private_method(self):
print("内部调用")python
obj = MyClass()
obj.public_method() # 正常
obj.__private_method() # 报错
obj._MyClass__private_method() # 可以访问,但不推荐双下划线主要用于类的封装,不常用于模块级函数。
3️⃣ __all__ 显式声明导出函数
- 模块可以通过
__all__列表显式声明“对外导出的函数/类”。 - 其他函数即使不加
_,也不会被from module import *导入。
python
# mymodule.py
__all__ = ["public_func"]
def public_func():
print("对外函数")
def internal_func():
print("内部函数")python
from mymodule import *
public_func() # 可以使用
internal_func() # 报错:未导入4️⃣ 总结约定
| 情况 | 约定方式 | 作用 |
|---|---|---|
| 模块级内部函数 | _func() | 外部可以访问,但不推荐 |
| 类内部私有方法 | __method() | 名称重整,避免外部轻易访问 |
控制 import * | __all__ = [...] | 显式声明导出接口 |
Python 核心思想是 “we are all consenting adults”,依赖约定而非强制封装。
好的,我们可以把“没包”和“有包”的导入区别整理清楚,并举例说明。核心区别是命名空间层级和导入路径。
1️⃣ 没包的情况(单模块)
假设你有一个文件:
math_ops.py内容:
python
def add(a, b):
return a + b导入方式:
python
import math_ops
print(math_ops.add(2, 3))特点:
- 模块就在当前目录下,直接
import 模块名 - 没有层级概念,模块名直接暴露在全局命名空间
- 如果多个模块同名,会冲突
2️⃣ 有包的情况(目录 + __init__.py)
目录结构:
mypackage/
├── __init__.py
├── math_ops.py
└── string_ops.py内容示例:
python
# math_ops.py
def add(a, b):
return a + b
# string_ops.py
def hello(name):
return f"Hello, {name}"导入方式:
python
from mypackage import math_ops, string_ops
print(math_ops.add(2, 3))
print(string_ops.hello("Tom"))或者在 __init__.py 里统一导出:
python
# mypackage/__init__.py
from .math_ops import add
from .string_ops import hello然后可以直接:
python
from mypackage import add, hello3️⃣ 差异总结
| 特性 | 没包 | 有包 |
|---|---|---|
| 导入路径 | 模块名直接导入 | 包名.模块名 或 from 包名 import 模块/函数 |
| 命名空间 | 全局命名 | 包创建命名空间,避免冲突 |
| 多模块管理 | 不方便 | 可以分层组织,子包和模块可清晰管理 |
| 对外接口控制 | 无 | 可以用 __init__.py 或 __all__ 控制导出 |
4️⃣ 补充
- 包提供命名空间,类似前端的文件夹模块化
- 模块直接暴露在全局,容易冲突和混乱
- 包 +
__init__.py还可以统一导出 API,让外部只看到想让它看到的接口