Skip to content
当前页

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))  # 输出 8

2️⃣ 包(多模块)

如果模块比较多,可以组织成包。

目录结构示例:

mypackage/
├── __init__.py
├── math_ops.py
└── string_ops.py
  • __init__.py 可以为空,或者导入一些常用接口
  • math_ops.py
python
def add(a, b):
    return a + b
  • string_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, hello

3️⃣ 安装为可复用模块(发布或本地安装)

如果你想让模块在任何地方都能 import,需要打包安装。

方法一:本地安装

  1. 在项目根目录下创建 setup.py
python
from setuptools import setup, find_packages

setup(
    name="mypackage",
    version="0.1.0",
    packages=find_packages(),
)
  1. 安装:
bash
py -m pip install -e .   # 可编辑安装,修改代码即时生效

之后在任何地方都可以:

python
import mypackage

方法二:发布到 PyPI

  1. 打包:
bash
python setup.py sdist bdist_wheel
  1. 上传到 PyPI:
bash
py -m twine upload dist/*

其他人就可以:

bash
py -m pip install mypackage

4️⃣ 注意事项

  • 模块名和包名不要和标准库冲突(比如不要叫 os.py
  • 文件名遵循小写加下划线风格,例如 my_module.py
  • 包含 __init__.py 表示这是一个包
  • 如果是发布包,最好写 setup.pypyproject.toml 来声明元信息

✅ 总结:

类型文件结构导入方式
单文件模块mymodule.pyimport mymodule
包(多模块)mypackage/__init__.py + 多文件from mypackage import submodule
可复用模块打包 + 安装pip install mypackageimport 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.0

3️⃣ pip 安装时读取 METADATA

当你执行:

bash
py -m pip install mypackage.whl

pip 会:

  1. 读取 .dist-info/METADATA
  2. 解析 Requires-Dist 字段,知道需要先安装哪些依赖
  3. 递归解析依赖,生成依赖树
  4. 下载并安装所有缺失的依赖

✅ 总结流程

  1. 开发者在 setup.py / pyproject.toml 声明依赖
  2. 打包工具生成 .dist-info/METADATA
  3. 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, hello

3️⃣ 差异总结

特性没包有包
导入路径模块名直接导入包名.模块名from 包名 import 模块/函数
命名空间全局命名包创建命名空间,避免冲突
多模块管理不方便可以分层组织,子包和模块可清晰管理
对外接口控制可以用 __init__.py__all__ 控制导出

4️⃣ 补充

  • 包提供命名空间,类似前端的文件夹模块化
  • 模块直接暴露在全局,容易冲突和混乱
  • 包 + __init__.py 还可以统一导出 API,让外部只看到想让它看到的接口