前提知识:类与对象:Python 的面向对象初体验,像搭积木一样写代码
适合读者:掌握了类与对象基础、想了解 Python 代码组织和标准库的开发者


想象一家餐厅的后厨。

厨房里有很多厨师,每个厨师各有专长:有的专做前菜,有的负责主菜,有的专精甜点。客人不需要知道厨师叫什么名字,只需要看菜单点菜,后厨自然会安排对应的厨师出品。

在 Python 的世界里,模块(module)就是一位厨师,包(package)就是整个厨房import 关键字是点菜单——告诉 Python:“我需要某个厨师的那道菜。”

本篇用一个餐厅每日特价菜生成器作为贯穿场景:基于菜单数据,随机选出一道今日特价菜,并标注推荐理由和时间戳。这个小工具会把 randomdatetimeos 三个标准库模块串在一起,让读者感受到标准库的实用价值。


一、模块、包与 import 的本质

1.1 三个概念一次理清

概念 餐厅类比 实际例子 本质
模块 一位厨师 random.pymath.py 一个 .py 文件
整个厨房 oscollections 一个文件夹,内含 __init__.py
内置函数 厨房自带的万能工具 print()len()type() Python 解释器直接提供,无需导入
# 内置函数,直接可用
print("Hello")        # 不需要 import

# math 是 Python 自带的标准库模块
import math
math.sqrt(16)          # → 4.0

math 这个文件在哪里?它是 Python 解释器安装时自带的,不需要额外安装。如果想知道模块的具体路径:

import math
print(math.__file__)
# Linux/macOS: /usr/lib/python3.12/lib-dynload/math.cpython-312-x86_64-linux-gnu.so
# Windows:      C:\Users\xxx\AppData\Local\Programs\Python\Python312\lib\math.py

1.2 import 的三种写法

# 写法一:导入整个模块(推荐,明确知道函数来自哪里)
import math
print(math.sqrt(16))      # → 4.0

# 写法二:只导入需要的函数或变量(使用更简洁,但可能有命名冲突)
from math import sqrt, pi
print(sqrt(16) * pi)     # → 12.566...

# 写法三:给模块或函数起别名(避免名字太长,或绕开命名冲突)
import numpy as np
from datetime import datetime as dt
np.array([1, 2, 3])
dt.now()

三种写法的权衡:

import 写法对比

import math
优点:命名空间清晰
缺点:每次调用要加 math.

from math import sqrt
优点:直接用 sqrt()
缺点:同名变量会覆盖,可读性差

import xxx as y
优点:省打字 + 避免冲突
缺点:新名字需要记忆

经验之谈:代码库中 import xxx 的写法是最稳妥的——即使名字长一点,但任何人读代码时都能一眼看出 sqrt 来自 math 模块,而不是自己定义的函数或第三方库。

1.3 __name__ == "__main__" 的原理

这是 Python 中最容易让人困惑又最重要的知识点之一。先看代码:

文件:greet.py

def say_hello():
    print("Hello!")

# 直接运行此文件时执行这里
if __name__ == "__main__":
    print("正在作为主程序运行")
    say_hello()
$ python greet.py
正在作为主程序运行
Hello!

如果这个文件被其他文件导入,if __name__ == "__main__": 下的代码不会执行

文件:main.py

import greet      # 导入 greet 模块

print("main.py 正在运行")
greet.say_hello()
$ python main.py
main.py 正在运行
Hello!

原因在于 __name__ 变量的值:

Python 启动文件

`__name__` 的值

`__name__ == '__main__'`
→ 直接运行此文件

`__name__ == '模块名'`
→ 被其他文件导入

执行 `if __name__` 下的代码

跳过 `if __name__` 下的代码

场景 __name__ 的值 if __name__ == "__main__"
直接运行 python xxx.py "__main__" 执行
被其他文件 import "模块名"(如 "greet" 跳过

这个机制有什么用?让同一个 .py 文件既能作为主程序运行,又能作为模块被其他文件导入。几乎所有 Python 项目的入口文件都有这一行:

# 几乎所有项目的入口文件结构
def main():
    # 程序的主要逻辑写在这里
    ...

if __name__ == "__main__":
    main()

二、标准库宝藏库:5 个最常用的模块

Python 自带的标准库极其丰富——不需要 pip install,不需要联网,开箱即用。以下 5 个模块覆盖了日常开发的绝大多数场景。

2.1 math —— 数学运算

import math

# 常用函数
math.sqrt(16)       # → 4.0,平方根
math.pow(2, 10)     # → 1024.0,幂运算(返回 float)
math.floor(3.7)     # → 3,向下取整
math.ceil(3.2)      # → 4,向上取整
math.fabs(-5)       # → 5.0,绝对值(返回 float)

# 常量
math.pi             # → 3.141592653589793
math.e              # → 2.718281828459045

# 三角函数
math.sin(math.pi / 2)   # → 1.0
math.cos(0)             # → 1.0
math.radians(180)       # → 3.14159...,角度转弧度

注意math.pow(2, 10) 返回 float(1024.0),如果需要整数结果,用 2 ** 10

2.2 random —— 随机数生成

这是日常 Python 开发中使用频率非常高的模块——随机选菜、洗牌、打乱顺序这些场景都靠它。

import random

# 基本随机
random.randint(1, 6)        # → 随机整数 [1, 6](两端都包含)
random.random()             # → [0.0, 1.0) 之间的随机浮点数
random.uniform(1.5, 9.3)    # → [1.5, 9.3] 之间的随机浮点数

# 从序列中随机选择
menu = ["红烧肉", "清蒸鱼", "宫保鸡丁", "麻婆豆腐"]
random.choice(menu)         # → 随机返回一道菜
random.sample(menu, 2)      # → 随机返回 2 道菜(不重复)

# 打乱顺序(洗牌)
cards = ["A", "2", "3", "4", "5"]
random.shuffle(cards)       # cards 顺序被随机打乱
print(cards)                # → 类似 ['3', 'A', '5', '2', '4']

2.3 datetime —— 日期与时间

import datetime

# 获取当前时间
now = datetime.datetime.now()
print(now)
# → 2026-04-27 22:30:15.123456

# 格式化输出(最常用)
print(now.strftime("%Y-%m-%d %H:%M"))
# → 2026-04-27 22:30

print(now.strftime("%A, %B %d"))      # 英文全称
# → Monday, April 27

# 时间差计算
import datetime
tomorrow = now + datetime.timedelta(days=1)
print(tomorrow.strftime("%Y-%m-%d"))
# → 2026-04-28

one_hour_ago = now - datetime.timedelta(hours=1)
print(one_hour_ago.strftime("%H:%M"))
# → 21:30

# 字符串转时间对象
time_str = "2026-04-27 18:00:00"
dt = datetime.datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S")
print(dt)                        # → 2026-04-27 18:00:00
占位符 含义 示例
%Y 四位年份 2026
%m 两位月份 04
%d 两位日期 27
%H 24小时制小时 22
%M 分钟 30
%S 15
%A 星期几(英文) Monday

2.4 os —— 操作系统接口

import os

# 文件路径(跨平台拼接,避免手动加斜杠)
path = os.path.join("data", "menu", "dishes.txt")
print(path)
# Linux/macOS: data/menu/dishes.txt
# Windows:     data\menu\dishes.txt

# 检查文件/目录是否存在
os.path.exists("menu.py")           # → True / False
os.path.isfile("menu.py")           # → True / False(是文件吗)
os.path.isdir("data")               # → True / False(是目录吗)

# 获取当前工作目录
print(os.getcwd())
# → /home/user/project 或 C:\Users\xxx\project

# 创建目录
os.makedirs("output", exist_ok=True)  # exist_ok=True 表示已存在也不报错

# 列目录文件
files = os.listdir(".")
print([f for f in files if f.endswith(".py")])

# 环境变量
home = os.environ.get("HOME") or os.environ.get("USERPROFILE")
print(f"用户主目录: {home}")

2.5 sys —— 系统与解释器

import sys

# 退出程序(慎用,强制终止整个进程)
# sys.exit(0)   # 0 表示正常退出,非 0 表示异常退出

# 命令行参数(从 argv[1] 开始是传入的参数)
# python main.py arg1 arg2
# sys.argv = ["main.py", "arg1", "arg2"]
print(sys.argv)

# Python 解释器路径
print(sys.executable)
# → /usr/bin/python3 或 C:\Users\xxx\AppData\Local\Programs\Python\Python312\python.exe

# 查看已加载的模块
print(sys.modules.keys())

三、用 dir()help() 自主探索模块

大多数教程只讲已经被讲烂的那几个函数——但 Python 标准库有 200+ 个模块,没人能全部记住。真正重要的能力是自己发现模块里有什么,而不是依赖别人告诉你。

3.1 dir() —— 列出模块的所有属性

import math

# 不带参数:列出当前作用域的所有名字
print(dir())
# → ['__annotations__', '__builtins__', '__doc__', 'math', ...]

# 带参数:列出对象的所有属性和方法(双下划线开头的是 Python 内部使用的)
attrs = dir(math)
print(attrs)
# → ['__doc__', '__file__', '__loader__', '__name__', '__package__',
#     'acos', 'acosh', 'asin', ... 'sqrt', 'tan', 'tanh', 'trunc']

过滤出以字母开头的"实用"成员(去掉双下划线开头的内部属性):

import math
practical = [a for a in dir(math) if not a.startswith("_")]
print(practical)
# → ['acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', ... 'sqrt', 'tan', 'tanh', 'trunc']

3.2 help() —— 查看函数的文档

import random

# 查看某个函数的用法
help(random.choice)

输出:

Help on method choice in module random:

choice(self, seq) method of random.Random instance
    Choose a random element from a non-empty sequence.

简洁用法:只读 docstring 字符串本身:

print(random.choice.__doc__)
# → Choose a random element from a non-empty sequence.

经验之谈:在实际写代码时,如果知道大概要做什么但不确定用哪个函数,先 import xxx,然后 print(dir(xxx)) 扫一遍成员名字,往往能找到惊喜——比如 os.path.splitext() 这个函数,名字本身就说明了它的用途。


四、综合实战:餐厅每日特价生成器

4.1 需求分析

一个餐厅老板希望每天早上自动生成一道"今日特价菜",包含:

  • 从固定菜单中随机选一道菜
  • 给出推荐理由
  • 标注生成时间
  • 如果当日菜品已经生成过(文件存在),直接读取而不是重复生成

4.2 完整代码

# daily_special.py

import os
import random
import datetime
import json


MENU_FILE = "menu.json"
SPECIAL_FILE = "today_special.json"


def load_menu():
    """从 JSON 文件加载菜单数据"""
    if not os.path.exists(MENU_FILE):
        # 如果菜单文件不存在,创建默认菜单
        default_menu = {
            "appetizers": ["凉拌黄瓜", "老醋花生", "拍黄瓜"],
            "main_courses": ["红烧肉", "清蒸鲈鱼", "宫保鸡丁", "麻婆豆腐", "鱼香肉丝"],
            "desserts": ["红糖糍粑", "冰粉", "芝麻汤圆"]
        }
        with open(MENU_FILE, "w", encoding="utf-8") as f:
            json.dump(default_menu, f, ensure_ascii=False, indent=2)
        return default_menu

    with open(MENU_FILE, "r", encoding="utf-8") as f:
        return json.load(f)


def generate_reason(dish, category):
    """根据菜品和类别生成推荐理由"""
    reasons = {
        "appetizers": [
            f"{dish} 开胃解腻,是整顿饭的好开头",
            f"{dish} 清爽可口,唤醒味蕾",
        ],
        "main_courses": [
            f"{dish} 精选食材,当日主推",
            f"{dish} 大厨拿手菜,不容错过",
            f"{dish} 香气扑鼻,回头客必点",
        ],
        "desserts": [
            f"{dish} 甜蜜收尾,一整天的好心情",
            f"{dish} 手工制作,限量供应",
        ],
    }
    return random.choice(reasons.get(category, [f"{dish} 今日特价"]))


def pick_special(menu):
    """从菜单中随机选取一道今日特价菜"""
    category = random.choice(list(menu.keys()))
    dish = random.choice(menu[category])
    reason = generate_reason(dish, category)

    return {
        "dish": dish,
        "category": category,
        "reason": reason,
        "generated_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M"),
    }


def save_special(special):
    """将今日特价保存到文件"""
    with open(SPECIAL_FILE, "w", encoding="utf-8") as f:
        json.dump(special, f, ensure_ascii=False, indent=2)


def load_special():
    """从文件读取今日特价(如果已存在)"""
    if os.path.exists(SPECIAL_FILE):
        with open(SPECIAL_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    return None


def main():
    # 先检查今日特价是否已生成过
    today_special = load_special()
    today_date = datetime.datetime.now().strftime("%Y-%m-%d")

    if today_special and today_special.get("generated_at", "").startswith(today_date):
        print(f"今日特价已生成({today_special['generated_at']}),直接读取:")
    else:
        menu = load_menu()
        today_special = pick_special(menu)
        save_special(today_special)
        print(f"今日特价已生成({today_special['generated_at']}):")

    # 输出格式化的今日特价
    category_names = {
        "appetizers": "前菜",
        "main_courses": "主菜",
        "desserts": "甜点",
    }
    cat_name = category_names.get(today_special["category"], today_special["category"])

    print("=" * 30)
    print(f"  🍽️  今日特价 · {cat_name}")
    print(f"  菜名:{today_special['dish']}")
    print(f"  推荐理由:{today_special['reason']}")
    print(f"  生成时间:{today_special['generated_at']}")
    print("=" * 30)


if __name__ == "__main__":
    main()

4.3 运行效果

第一次运行(生成特价菜):

今日特价已生成(2026-04-27 08:05):
==============================================================
  🍽️  今日特价 · 主菜
  菜名:宫保鸡丁
  推荐理由:宫保鸡丁 大厨拿手菜,不容错过
  生成时间:2026-04-27 08:05
==============================================================

当日第二次运行(读取缓存):

今日特价已生成(2026-04-27 08:05),直接读取:
==============================================================
  🍽️  今日特价 · 主菜
  菜名:宫保鸡丁
  推荐理由:宫保鸡丁 大厨拿手菜,不容错过
  生成时间:2026-04-27 08:05
==============================================================

4.4 流程图

开始:main()

today_special.json 存在
且日期是今天?

读取缓存的今日特价

加载 menu.json
(不存在则创建默认菜单)

从菜单随机选取
一道菜 + 推荐理由

记录当前时间戳

保存到 today_special.json

输出格式化结果


五、常见易错点

5.1 导入顺序错误

Python 会根据 sys.path 中的路径顺序查找模块——当前脚本所在目录在最前面,然后是 Python 标准库,最后是第三方库。

# 错误示例:当前目录下有一个名为 random.py 的文件
# 会覆盖标准库的 random!
import random

print(random.randint(1, 6))  # 报错:AttributeError(如果自定义的 random.py 没有 randint)

解决方法:永远不要用标准库的名字(randomossysmath 等)命名自己的文件。

5.2 模块只导入一次

同一个模块被多次 import,Python 只会在第一次执行初始化,后续 import 直接返回已加载的对象。

import math
import math        # 这次 import 什么都不做,math 模块对象不变

print(math is math)   # → True,同一个对象

如果修改了模块文件,需要重启 Python 解释器才能生效——Python 不支持热重载自己。

5.3 相对导入的限制

包内部可以使用相对导入(如 from . import utils),但不能用相对导入运行独立文件

restaurant/
├── __init__.py
├── daily_special.py   # 如果直接运行 python daily_special.py
└── menu_utils.py      # 这里的 from . import menu_utils 会报错!

解决方法:始终用 python -m 包名.模块名python 入口文件.py 的方式运行,确保 Python 路径正确。


六、知识结构图

模块与包

import 机制

标准库宝藏

模块探索工具

项目结构实践

import xxx

from xxx import yyy

import xxx as z

name == 'main'

`math`:数学运算

`random`:随机数

`datetime`:日期时间

`os`:文件系统

`sys`:解释器

`json`:序列化

`collections`:数据结构

`dir()`:列出属性

`help()`:查看文档

`__doc__`:docstring

单文件脚本

包结构 + `__init__.py`

`python -m` 入口运行

避免命名冲突


七、工程原则

  1. 标准库优先:遇到常见需求(随机数、日期时间、文件操作、JSON 序列化)先查标准库,不需要 pip install 就能用,稳定性和兼容性都有保障。

  2. import xxx 的命名空间写法优先于 from xxx import yyy:显式写出模块名虽然多敲几个字,但代码可读性大幅提升——读者始终知道 sqrt 来自 math 而不是自己定义的函数。

  3. dir()help() 做自主探索:没有人能记住标准库的全部内容,遇到问题时的第一反应应该是"这个模块里有没有现成的函数",而不是"去 pip 一个第三方库"。

  4. 不要用标准库名字命名自己的文件random.pytest.pystring.py 这些文件名会悄悄覆盖 Python 内置模块,产生难以排查的 Bug。

  5. __name__ == "__main__" 是 Python 项目的标准入口约定:所有需要直接运行的文件都应该有这个结构,所有只需要被导入的代码都不应该出现在这个分支下。


如果觉得这篇文章有帮助,欢迎点赞、评论和关注。往期的函数与异常处理、类与对象等文章中也有类似的实战项目,可以结合阅读。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐