| Day 10 | 模块与包:import、标准库与自定义模块 |

1 模块的本质 —— .py 文件就是模块

是什么?为什么学?

模块(module)就是一个 .py 文件,文件里写的函数、变量、类都可以被其他文件导入使用。它是 Python 组织代码的最小单位:复用(写一次到处用)、隔离(每个文件只管一件事)、协作(多人各写各的文件)。到昨天为止,所有代码都是「一个文件从头写到尾」;真实项目动辄几千上万行,全堆在一个文件里没法维护——模块就是把代码按功能拆成一个个文件,需要时导入使用。

底层原理

import math 背后做了三件事:① 找到 math.py 文件(按 sys.path 里的目录顺序查找);② 执行 整个文件(模块顶层的代码会真实运行一遍);③ 把模块对象绑定到名字 math 上。之后 math.sqrt 就是「从 math 这个模块对象里取 sqrt 属性」。每个模块只被完整执行一次,之后都从 sys.modules 缓存里直接取——所以导入多次不会重复执行。

生活类比

模块像零件仓库里的货架:每个货架(.py 文件)上整整齐齐摆着一类零件(函数、变量),货架上有标签(模块名)。你想用「平方根」这个零件,就去 math 货架取:math.sqrt。零件放仓库里,谁需要谁来取,不用每个车间(文件)都自己造一遍。

注意事项总结

1 模块是一个对象,用「模块名.名字」取里面的东西

import math
print(type(math))        # 模块的类型是 module
print(math.sqrt(81))     # 从 math 里取 sqrt 函数来用
print(math.pi)           # 从 math 里取变量 pi

运行结果:

<class 'module'>
9.0
3.141592653589793

模块里不仅有函数,还有 pi 这样的常量变量——它们统一叫模块的「名字」(name)。拿到不熟悉的模块时,可以用 dir(模块) 列出全部名字、用 hasattr(模块, "名字") 验证是否存在——这两个是「探索模块」的万能钥匙。

2 每个模块只执行一次——导入多次不会重复加载

import sys
import math
print("第一次导入后:", "math" in sys.modules)
import math                      # 再导入一次,不会重新执行 math 文件
print("math 还是同一个对象:", sys.modules["math"] is math)

运行结果:

第一次导入后: True
math 还是同一个对象: True

sys.modules 是「已导入模块的缓存字典」。任何地方再 import math,Python 直接从缓存取,不会重新执行——模块导入是「单例」的。

易错点

  • 错误写法:import mathh(拼写错误) → 问题:报 ModuleNotFoundError: No module named 'mathh' → 正确写法:检查模块名拼写。
  • 错误写法:只 import math 却直接写 sqrt(16) → 问题:报 NameError,sqrt 不在当前命名空间 → 正确写法:写 math.sqrt(16)(带模块名前缀)。
  • 错误写法:自己建了个叫 math.py 的文件 → 问题:你的文件会「顶掉」标准库 math,行为全乱 → 正确写法:自定义文件不要用标准库模块名命名。

记忆口诀:模块就是 .py,import 三连:找到、执行、绑名字;只跑一次进缓存,模块名前缀别忘写。

2 import 的两种姿势 —— 整模块导入 vs 精准导入

是什么?为什么学?

import math 导入整个模块(要用 math. 前缀);from math import sqrt 只把某个名字「复制」进当前文件(直接用)。加上 as 别名可以给长名字起短名。三种姿势各有适用场景。

底层原理

import math 是把模块对象绑定到 math 这个名字;from math import sqrt 则是「从 math 对象里取出 sqrt 属性,绑定到当前模块的 sqrt 名字」——相当于一次「复制引用」。所以之后直接用 sqrt 就行,但 math 这个名字本身没有绑定,写 math.sqrt 会 NameError。import math as m 只是把模块对象绑定到名字 m 上(as = alias,别名)。要注意:from 导入如果和你已有的变量同名,会覆盖你的变量——这是它最大的坑。

生活类比

两种姿势像超市购物import math 是把整个超市搬回家,家里多了「math 超市」,拿东西要写「去 math 超市拿 sqrt」;from math import sqrt 是只买走 sqrt 这一个商品,回家直接用,但家里并没有「math 超市」这个名字。as 则像给买回来的东西贴个自己的标签。

注意事项总结

1 from 导入会覆盖同名变量(重要坑!)

sqrt = 100                     # 1. 自己先定义了一个变量 sqrt
from math import sqrt          # 2. from 导入把 sqrt 覆盖成了 math 的函数

print(sqrt)                    # 现在 sqrt 是函数,不再是 100
print(sqrt(16))                # 可以当函数用

运行结果:

<built-in function sqrt>
4.0

from math import sqrt 把 sqrt 这个名字重新绑定到 math.sqrt 函数上,原来的 100 被覆盖丢失。这解释了为什么团队规范推荐多用 import math 形式——前缀能避免这种「名字被悄悄覆盖」的意外。

2 as 别名——给名字起短名

import random as r              # 模块起别名
from math import sqrt as s      # 函数起别名

print(s(144))                   # 用别名调用
print(r.randint(1, 3))          # 1~3 随机整数(每次运行可能不同)

运行结果:

12.0
2(每次运行结果可能不同)

长模块名(如 import numpy as np)或重名冲突时特别好用。

易错点

  • 错误写法:from math import sqrt 后又写 math.sqrt(9) → 问题:报 NameError: name 'math' is not defined → 正确写法:用 from 导入就用裸名字;想写 math. 前缀就用 import math。
  • 错误写法:from math import * → 问题:把 math 所有名字塞进来,可能覆盖你的变量,团队规范禁止 → 正确写法:明确导入需要的名字:from math import sqrt, pi

记忆口诀:import 整模块,前缀来访问;from 精准拿,裸名直接用;as 起别名,覆盖要提防。

3 标准库实战 —— math / random / os / sys 四大金刚

是什么?为什么学?

标准库是 Python 自带的「百宝箱」,装了几百个现成模块。今天先认识最常用的四个:math(数学计算)、random(随机数)、os(操作系统)、sys(系统参数)。学会用标准库,是「不重复造轮子」的第一步——你要的功能,大概率别人已经写好了。

底层原理

标准库模块都是 C 或 Python 写的、随 Python 一起安装的现成代码。它们与普通模块的导入机制完全一样:按 sys.path 查找、执行、绑定名字。math 里的函数(如 sqrt)用 C 实现,比 Python 手写快几十上百倍;random 用「梅森旋转」算法生成伪随机数(从种子出发,看起来随机但可复现);os 封装了操作系统的 API;sys 直接暴露解释器运行状态(版本、参数、路径)。

生活类比

标准库像宜家的样板间:你不需要从零设计家具,样板间里全是装好的方案——math 是计算器柜台,random 是抽奖转盘柜台,os 是物业办公室(管目录、管路径),sys 是前台服务台(告诉你本机信息)。需要什么功能,直接去对应柜台取。

注意事项总结

1 math——数学计算

import math
print(math.sqrt(144))       # 平方根:12.0
print(math.fabs(-7))        # 绝对值:7.0
print(math.pow(2, 10))      # 2 的 10 次方:1024.0
print(math.factorial(5))    # 阶乘:120

2 random——随机数

import random
print(random.randint(1, 6))     # 1~6 的随机整数
print(random.choice(["石头", "剪刀", "布"]))   # 从列表随机选一个
print(round(random.random(), 4))    # 0~1 之间的随机小数(保留 4 位)
nums = [1, 2, 3, 4, 5]
random.shuffle(nums)             # 原地打乱列表顺序

运行结果(每次运行不同,示例):

5
剪刀
0.6374
[3, 5, 1, 4, 2]

3 os——操作系统

import os
print(os.path.join("data", "notes", "hello.txt"))   # 拼接路径
print(os.path.exists("hello.txt"))                   # 文件是否存在
print(os.getcwd())                                   # 当前工作目录

运行结果(Windows 示例;getcwd 取决于运行目录):

data\notes\hello.txt
False
C:\Users\...\Temp

os.path.join 按当前系统的习惯拼接路径(Windows 用 \、Linux/macOS 用 /),跨平台不报错——文件相关的程序几乎都要用到 os。

4 sys——系统参数

import sys
print(sys.version)          # Python 版本号
print(sys.argv)             # 命令行参数列表(运行脚本时传入)

运行 python test.py a b 时,sys.argv['test.py', 'a', 'b']——下标 0 是脚本本身,参数从下标 1 开始。

易错点

  • 错误写法:random.random() 想要 16 的整数 → 问题:得到的是 01 的小数 → 正确写法:整数随机用 random.randint(1, 6)
  • 错误写法:math.sqrt("16") 传字符串 → 问题:报 TypeError,数学函数只接受数字 → 正确写法:先转数字:math.sqrt(int("16"))

记忆口诀:math 算数,random 随机,os 管系统,sys 查自己;标准库常查文档,不造轮子省力气。

4 自定义模块与 name 魔法 —— 写自己的 .py 文件

是什么?为什么学?

自己写的 .py 文件就是自定义模块,放在同一目录即可被导入。但直接运行和被导入时,文件的行为应该不同:被导入时不该执行「测试代码」。if __name__ == "__main__": 就是区分「直接运行 / 被导入」的标准写法——每个 Python 文件的标准结尾。

底层原理

Python 执行任何文件时都会给它设置一个内置变量 __name__直接运行时值是字符串 "__main__"被 import 导入时值是模块名(如 "my_tools")。所以 if __name__ == "__main__": 的意思是「只有当我被直接运行时才执行这段代码」。被导入时条件为假,测试代码自然跳过——同一个文件,两种身份,一套判断搞定。

生活类比

__name__员工工牌上的「值班身份」:白天上班(被导入)时,胸牌写的是「部门-岗位」(模块名),只干本职工作;下班回家(直接运行)时,身份是「主人」(__main__),可以做自我检查(运行测试代码)。

注意事项总结

1 自定义模块 my_tools.py(新建这个文件)

def add(a, b):
    """两数相加"""
    return a + b


def is_even(n):
    """判断 n 是否为偶数"""
    return n % 2 == 0


PI = 3.14159

if __name__ == "__main__":
    # 只有直接运行 my_tools.py 时才执行下面的自测
    print("自测 add(1, 2):", add(1, 2))
    print("自测 is_even(4):", is_even(4))

2 use_tools.py(与 my_tools.py 放同一目录)——导入并使用

import my_tools
from my_tools import is_even, PI

print(my_tools.add(3, 5))     # 用模块名前缀访问
print(is_even(10))            # 直接用导入的名字
print(PI)                     # 导入的常量

运行结果:

8
True
3.14159

import my_tools 时,Python 会执行 my_tools.py 文件——但 __name__ 此时是 "my_tools" 而不是 "__main__",所以自测代码不会执行。两个文件必须放在同一个目录下。

易错点

  • 错误写法:测试代码直接写在文件顶层 → 问题:被 import 时测试代码也执行,屏幕上乱打东西 → 正确写法:测试代码全部放进 if __name__ == "__main__":
  • 错误写法:if __name__ == "main":(少了双下划线) → 问题:条件永远为假,直接运行时自测不执行 → 正确写法:写全:if __name__ == "__main__":
  • 错误写法:自定义模块没放在当前目录 → 问题:报 ModuleNotFoundError → 正确写法:被导入文件与使用文件放同一目录。

记忆口诀:直接运行 __main__,被导模块名;测试放进口判断,一个文件两种身份。

5 包 —— 把模块装进文件夹

是什么?为什么学?

(package)是__init__.py 的文件夹,用来组织多个相关模块。模块一多,全平铺在目录里会乱;包让你用「包名.模块名」分层管理,就像文件夹套文件夹。真实项目(Django、Flask 都是几十上百个模块)全靠包来组织,今天先建立概念。

底层原理

Python 把「含 __init__.py 的文件夹」当作包:导入 mypkg.tools 时,先执行 mypkg/__init__.py(可放包级初始化代码,也可为空),再执行 mypkg/tools.py__init__.py 是包的「身份证」——有了它,文件夹才能被当成包导入。Python 3 还支持「命名空间包」(没有 init.py 也能导),但规范做法仍然是显式创建 __init__.py。点号 . 在导入语句里就是「进入下一层」的意思。

生活类比

包像办公室的文件柜:模块是柜子里的文件夹,包是带标签的柜子(标签就是 __init__.py)。找文件时先找到柜子(包名),再找文件夹(模块名):「财务柜.报销单」= mypkg.tools

注意事项总结

1 标准库里的「包」——xml 就是一个包

import xml
print(type(xml))                       # xml 是包,本质也是模块
print("未导入时有没有 etree:", hasattr(xml, "etree"))   # 子模块不会自动加载

import xml.etree.ElementTree as ET     # 包名.模块名.类名,分层导入
root = ET.fromstring("<name>Python</name>")
print(root.tag, root.text)

运行结果:

<class 'module'>
未导入时有没有 etree: False
name Python

import xml 只加载包本身,子模块 etree 不会自动加载(所以 hasattr 是 False);import xml.etree.ElementTree as ET 用点号一层层进到具体模块。

2 建包的目录结构

mypkg/                # 包:一个文件夹
  __init__.py         # 包的标志文件(可以是空文件)
  tools.py            # 模块 1
  data.py             # 模块 2
import mypkg.tools          # 用 包名.模块名 导入
from mypkg import data      # 也可以这样
from mypkg.tools import add # 或直接导入模块里的函数

易错点

  • 错误写法:忘了建 __init__.py → 问题:老版本 Python 无法导入该文件夹(3.3+ 有兼容但不符合规范) → 正确写法:每个包文件夹里都放一个 __init__.py(可空)。
  • 错误写法:import mypkg.tools 后直接写 tools.add(1, 2) → 问题:报 NameError,tools 没绑定到当前命名空间 → 正确写法:用全名 mypkg.tools.add(1, 2),或 from mypkg import tools

记忆口诀:包是文件夹,带证 __init__;点号进层级,包名点模块;子模块不自动,导入才加载。

6 动手实践:随机密码生成器

把今天学的 random、自定义模块、函数组合起来:做一个密码生成器,可以指定长度和是否包含数字、大小写、特殊符号。

# 随机密码生成器
import random
import string


def generate_password(length=12, use_digits=True, use_special=True):
    """生成长度为 length 的随机密码

    参数:
        length:     密码长度
        use_digits: 是否包含数字
        use_special: 是否包含特殊符号
    """
    chars = string.ascii_letters          # 所有大小写字母
    if use_digits:
        chars = chars + string.digits     # 加上数字
    if use_special:
        chars = chars + string.punctuation  # 加上特殊符号

    password = ""
    for i in range(length):
        password = password + random.choice(chars)
    return password


if __name__ == "__main__":
    print("默认密码(12位,含数字和符号):", generate_password())
    print("纯字母密码(8位):", generate_password(8, False, False))
    print("含数字不含符号(16位):", generate_password(16, True, False))
    print("很长的密码(20位):", generate_password(20))

运行结果(每次不同,示例):

默认密码(12位,含数字和符号): G7&kL#q2@xWp
纯字母密码(8位): QwErTyUi
含数字不含符号(16位): A1b2C3d4E5f6G7h8
很长的密码(20位): !xZ9&vB3@kL5#nP7*qW2

完成标准:运行后能看到 4 种不同规则的密码;修改 generate_password 的默认参数,再生成「只含小写字母、长度 6」的密码。想一想:string 是标准库,string.ascii_lettersstring.digitsstring.punctuation 分别是什么,用 print 看一看。

今日总结

模块是什么:.py 文件就是模块;import 三件事——找到、执行、绑定名字,且只执行一次(sys.modules 缓存) import 模块模块.名字 访问;from 模块 import 名字 直接使用;as 可起别名;from ... import * 要避免 from ... import ... 会覆盖同名变量,这是它最大的坑,多用 import 模块 更安全 标准库四大金刚:math 算数学、random 造随机、os 管系统、sys 查参数 自定义模块放同一目录即可导入;测试代码放 if __name__ == "__main__": 里,直接运行才执行、被导入不执行 包 = 带 __init__.py 的文件夹,用点号分层导入;子模块不会自动加载 完成随机密码生成器,并尝试改参数生成不同规则的密码

报错原因修复
ModuleNotFoundError: No module named 'xxx'模块不存在、没安装,或自定义模块不在同一目录检查名字拼写;自定义模块放在当前目录;第三方库用 pip 安装
AttributeError: module 'xxx' has no attribute 'yyy'模块里没有这个函数/属性检查函数名拼写;用 dir(模块) 看模块里有什么
ImportError: cannot import name 'xxx'from 导入的名字在模块里不存在确认模块里确实定义了该名字
NameError: name 'math' is not defined用了 math.xxx 但只 from math import sqrtimport math 才能写 math.xxx,或直接导入你要的函数
RecursionError 或行为异常(导入时打印了测试输出)自定义模块里测试代码没放在 if __name__ == "__main__":把测试代码放进 if __name__ == "__main__":
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇