第四十三篇:常用内建模块(上)—— datetime、collections、hashlib
前言
Python 的内建模块是日常开发中最频繁使用的工具库。datetime 处理日期时间,collections 提供高级容器类型,hashlib 负责消息摘要和加密哈希。本文将系统讲解这三大核心模块的用法与实战场景。
datetime —— 日期与时间处理
基本日期操作
from datetime import datetime, date, time, timedelta
# 获取当前日期和时间
now = datetime.now()
today = date.today()
current_time = time(14, 30, 0)
print(f"当前时间: {now}") # 2026-09-09 14:30:00.123456
print(f"当前日期: {today}") # 2026-09-09
print(f"星期: {today.weekday()}") # 0=Monday ... 6=Sunday
print(f"ISO 格式: {today.isoformat()}") # 2026-09-09
# 日期加减
tomorrow = today + timedelta(days=1)
yesterday = today - timedelta(days=1)
next_week = now + timedelta(weeks=1)
three_hours_ago = now - timedelta(hours=3)
print(f"明天: {tomorrow}")
print(f"一周后: {next_week}")
print(f"三小时前: {three_hours_ago}")
# 计算两个日期之间的差值
start_date = date(2026, 1, 1)
end_date = date(2026, 12, 31)
days_between = (end_date - start_date).days
print(f"全年天数: {days_between} 天") # 364
日期格式化与解析
from datetime import datetime
dt = datetime(2026, 9, 9, 14, 30, 45)
# 格式化:datetime → 字符串
formats = {
"标准格式": dt.strftime("%Y-%m-%d %H:%M:%S"),
"短日期": dt.strftime("%Y/%m/%d"),
"中文格式": dt.strftime("%Y年%m月%d日 %H时%M分%S秒"),
"ISO 8601": dt.isoformat(),
"RFC 2822": dt.strftime("%a, %d %b %Y %H:%M:%S +0000"),
}
for name, formatted in formats.items():
print(f"{name}: {formatted}")
# 标准格式: 2026-09-09 14:30:45
# 短日期: 2026/09/09
# 中文格式: 2026年09月09日 14时30分45秒
# ISO 8601: 2026-09-09T14:30:45
# RFC 2822: Wed, 09 Sep 2026 14:30:45 +0000
# 解析:字符串 → datetime
date_strings = [
"2026-09-09",
"2026/09/09 14:30",
"Sep 9, 2026",
]
parsers = [
("%Y-%m-%d", "%Y-%m-%d"),
("%Y/%m/%d %H:%M", "%Y/%m/%d %H:%M"),
("%b %d, %Y", "%b %d, %Y"),
]
for text, fmt in zip(date_strings, parsers):
parsed = datetime.strptime(text, fmt[0])
print(f"'{text}' → {parsed}")
strftime / strptime 格式化代码速查
| 代码 | 含义 | 示例 |
|---|---|---|
%Y |
四位年份 | 2026 |
%y |
两位年份 | 26 |
%m |
月份(01-12) | 09 |
%d |
日期(01-31) | 09 |
%H |
小时(00-23) | 14 |
%M |
分钟(00-59) | 30 |
%S |
秒(00-59) | 45 |
%A |
完整 weekday | Wednesday |
%a |
缩写 weekday | Wed |
%B |
完整 month | September |
%b |
缩写 month | Sep |
%p |
AM/PM | PM |
时区处理
from datetime import datetime, timezone, timedelta
# UTC 时间
utc_now = datetime.now(timezone.utc)
print(f"UTC 时间: {utc_now}") # 2026-09-09 06:30:00+00:00
# 北京时区(UTC+8)
beijing_tz = timezone(timedelta(hours=8))
beijing_now = datetime.now(beijing_tz)
print(f"北京时间: {beijing_now}") # 2026-09-09 14:30:00+08:00
# 时区转换
utc_dt = datetime(2026, 9, 9, 14, 30, tzinfo=timezone.utc)
beijing_dt = utc_dt.astimezone(beijing_tz)
print(f"UTC 14:30 → 北京时间: {beijing_dt}") # 2026-09-09 22:30:00+08:00
# 使用 zoneinfo(Python 3.9+,推荐)
try:
from zoneinfo import ZoneInfo
tokyo_tz = ZoneInfo("Asia/Tokyo")
ny_tz = ZoneInfo("America/New_York")
london_now = datetime.now(ZoneInfo("Europe/London"))
tokyo_now = london_now.astimezone(tokyo_tz)
ny_now = london_now.astimezone(ny_tz)
print(f"伦敦: {london_now}")
print(f"东京: {tokyo_now}")
print(f"纽约: {ny_now}")
except ImportError:
print("zoneinfo 需要 Python 3.9+")
实战:日历生成器
import calendar
from datetime import date
def generate_calendar(year, month):
"""生成指定年月日历"""
first_day = date(year, month, 1)
last_day = date(year, month, calendar.monthrange(year, month)[1])
month_label = f"{'='*30}\n{year}年 {month}月\n{'='*30}"
header = f"{'Sun':>6} {'Mon':>6} {'Tue':>6} {'Wed':>6} {'Thu':>6} {'Fri':>6} {'Sat':>6}"
lines = [month_label, header]
indent = " " * (first_day.weekday() * 7 + 2)
lines.append(indent)
for day in range(1, calendar.monthrange(year, month)[1] + 1):
current_date = date(year, month, day)
day_str = f"{day:>6}" if day != last_day.day else f"{day:<6}"
if current_date.weekday() >= 5:
lines[-1] += day_str
lines.append("")
else:
lines[-1] += day_str
if len(lines[-1].strip()) >= 42:
lines.append("")
return "\n".join(lines)
print(generate_calendar(2026, 9))
collections —— 高级容器
namedtuple —— 命名元组
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)
print(p.x, p.y) # 3 4
print(p[0], p[1]) # 3 4
print(p._asdict()) # OrderedDict([('x', 3), ('y', 4)])
print(p._replace(x=10)) # Point(x=10, y=4)
# 实用示例:表示坐标点
class Circle(namedtuple('CircleBase', ['center', 'radius'])):
@property
def area(self):
import math
return math.pi * self.radius ** 2
c = Circle(Point(0, 0), 5)
print(f"圆心: ({c.center.x}, {c.center.y}), 半径: {c.radius}")
print(f"面积: {c.area:.2f}") # 面积: 78.54
deque —— 双向队列
from collections import deque
import time
# 高效的双端插入和弹出
dq = deque(maxlen=5) # 固定大小的滑动窗口
for item in [1, 2, 3, 4, 5, 6, 7]:
dq.append(item)
print(f"添加 {item}: {list(dq)}")
# 添加 1: [1]
# 添加 2: [1, 2]
# ...
# 添加 5: [1, 2, 3, 4, 5]
# 添加 6: [2, 3, 4, 5, 6] ← maxlen 限制自动丢弃左侧元素
# 添加 7: [3, 4, 5, 6, 7]
# 性能对比:列表 vs 队列做 FIFO 队列
def list_queue(n):
q = []
for i in range(n):
q.append(i)
while q:
q.pop(0) # O(n) 复杂度!
def deque_queue(n):
q = deque(range(n))
while q:
q.popleft() # O(1) 复杂度
n = 100_000
start = time.perf_counter()
list_queue(n)
list_time = time.perf_counter() - start
start = time.perf_counter()
deque_queue(n)
deque_time = time.perf_counter() - start
print(f"list pop(0): {list_time:.2f}s")
print(f"deque popleft: {deque_time:.2f}s")
print(f"deque 比 list 快约 {list_time/deque_time:.0f} 倍!")
defaultdict —— 默认值字典
from collections import defaultdict
# 避免 KeyError — 自动初始化默认值
word_count = defaultdict(int)
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
for word in words:
word_count[word] += 1
print(dict(word_count))
# {'apple': 3, 'banana': 2, 'cherry': 1}
# 多级嵌套字典(免去繁琐的空值检查)
groups = defaultdict(list)
students = [
("Alice", "math"), ("Bob", "science"),
("Charlie", "math"), ("Diana", "art"),
("Eve", "science"), ("Frank", "math"),
]
for name, subject in students:
groups[subject].append(name)
for subject, members in sorted(groups.items()):
print(f"{subject}: {', '.join(members)}")
# art: Diana
# math: Alice, Charlie, Frank
# science: Bob, Eve
# 计数器统计
char_count = defaultdict(lambda: 0)
text = "hello world hello python"
for char in text:
char_count[char] += 1
print(dict(sorted(char_count.items())))
Counter —— 计数器
from collections import Counter
# 词频统计
text = "the quick brown fox jumps over the lazy dog the fox"
word_counter = Counter(text.split())
print(word_counter)
# Counter({'the': 3, 'fox': 2, 'quick': 1, 'brown': 1, 'jumps': 1, 'over': 1, 'lazy': 1, 'dog': 1})
# 最常见的 N 个元素
print(word_counter.most_common(3))
# [('the', 3), ('fox', 2), ('quick', 1)]
# 数学运算
c1 = Counter(a=3, b=1, c=2)
c2 = Counter(a=1, b=2, d=5)
print(c1 + c2) # Counter({'a': 4, 'b': 3, 'c': 2, 'd': 5}) 相加
print(c1 - c2) # Counter({'a': 2, 'c': 2}) 相减(只保留正值)
print(c1 & c2) # Counter({'a': 1, 'b': 1}) 交集(取最小值)
print(c1 | c2) # Counter({'a': 3, 'b': 2, 'c': 2, 'd': 5}) 并集(取最大值)
# 实际用例:分析日志级别分布
logs = ["ERROR", "INFO", "DEBUG", "ERROR", "WARNING", "INFO", "ERROR", "INFO"]
log_counter = Counter(logs)
total = sum(log_counter.values())
for level, count in log_counter.most_common():
percentage = count / total * 100
bar = "█" * int(percentage / 5)
print(f"{level:8s} {count:3d} ({percentage:5.1f}%) {bar}")
OrderedDict —— 有序字典
from collections import OrderedDict
menu = OrderedDict([
("starter", "沙拉"),
("main", "牛排"),
("dessert", "提拉米苏"),
("drink", "红酒"),
])
menu.move_to_end("starter")
print(menu)
# OrderedDict([('main', '牛排'), ('dessert', '提拉米苏'), ('drink', '红酒'), ('starter', '沙拉')])
# FIFODict 实现 — 缓存淘汰策略
class FIFODict(OrderedDict):
def __init__(self, max_size):
super().__init__()
self.max_size = max_size
def __setitem__(self, key, value):
if key in self:
self.move_to_end(key)
elif len(self) >= self.max_size:
self.popitem(last=False) # 删除最老的
super().__setitem__(key, value)
cache = FIFODict(max_size=3)
cache["a"] = 1
cache["b"] = 2
cache["c"] = 3
print(cache) # {'a': 1, 'b': 2, 'c': 3}
cache["d"] = 4 # 淘汰 'a'
print(cache) # {'b': 2, 'c': 3, 'd': 4}
ChainMap —— 多重映射组合
from collections import ChainMap
defaults = {"theme": "light", "language": "zh", "notifications": True}
user_prefs = {"theme": "dark"}
system_env = {"language": "en", "debug": False}
config = ChainMap(user_prefs, defaults, system_env)
print(config["theme"]) # dark(用户偏好优先)
print(config["language"]) # zh(用户偏好优先)
print(config["debug"]) # False(来自环境变量)
print(config["notifications"]) # True(来自默认值)
# 合并多个字典
dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3, "d": 4}
merged = ChainMap(dict1, dict2)
print(dict(merged)) # {'a': 1, 'b': 2, 'c': 3, 'd': 4}
hashlib —— 消息摘要与加密哈希
基本哈希算法
import hashlib
data = "Hello, World!"
# MD5(不推荐用于安全用途,仅用于校验和)
md5_hash = hashlib.md5(data.encode()).hexdigest()
print(f"MD5: {md5_hash}")
# 例: e59ff97941044f85df5297e1c302d260
# SHA-1(已不安全,不建议使用)
sha1_hash = hashlib.sha1(data.encode()).hexdigest()
print(f"SHA-1: {sha1_hash}")
# SHA-256(推荐的安全哈希)
sha256_hash = hashlib.sha256(data.encode()).hexdigest()
print(f"SHA-256: {sha256_hash}")
# SHA-512
sha512_hash = hashlib.sha512(data.encode()).hexdigest()
print(f"SHA-512: {sha512_hash}")
# 查看所有支持的算法
algorithms = hashlib.algorithms_available
print(f"可用算法: {sorted(algorithms)}")
HMAC — 消息认证码
import hmac
import hashlib
def sign_request(secret_key, message):
"""使用 HMAC-SHA256 签名请求"""
mac = hmac.new(
secret_key.encode(),
msg=message.encode(),
digestmod=hashlib.sha256
)
return mac.hexdigest()
api_key = "my-secret-key-12345"
request_body = '{"action":"login","user":"alice"}'
signature = sign_request(api_key, request_body)
print(f"签名: {signature}")
# 验证签名
is_valid = hmac.compare_digest(sign_request(api_key, request_body), signature)
print(f"签名验证结果: {is_valid}")
# ⚠️ 始终使用 hmac.compare_digest 而不是 == 比较!
# compare_digest 防时序攻击
文件校验和
import hashlib
import os
def compute_file_hash(filepath, algorithm="sha256", chunk_size=8192):
"""计算文件的哈希值"""
hash_func = hashlib.new(algorithm)
with open(filepath, "rb") as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
hash_func.update(chunk)
return hash_func.hexdigest()
# 演示用模拟数据
demo_data = b"This is a demo file for hashing purposes."
md5_demo = hashlib.md5(demo_data).hexdigest()
sha256_demo = hashlib.sha256(demo_data).hexdigest()
print(f"演示数据哈希:")
print(f"数据长度: {len(demo_data)} bytes")
print(f"MD5: {md5_demo}")
print(f"SHA256: {sha256_demo}")
密码存储最佳实践
import hashlib
import os
import time
def hash_password(password, salt=None, iterations=100_000):
"""
安全的密码哈希(PBKDF2-HMAC-SHA256)
Args:
password: 明文密码
salt: 随机盐值(如果为 None 则自动生成)
iterations: PBKDF2 迭代次数(越高越安全,但越慢)
Returns:
编码后的存储字符串格式: pbkdf2_sha256$salt$iterations$hash
"""
if salt is None:
salt = os.urandom(32) # 生成 256 位随机盐
dk = hashlib.pbkdf2_hmac(
'sha256',
password.encode('utf-8'),
salt,
iterations
)
stored = f"pbkdf2_sha256${salt.hex}${iterations}${dk.hex()}"
return stored
def verify_password(stored_password, provided_password):
"""验证密码"""
parts = stored_password.split('$')
if len(parts) != 4:
raise ValueError("无效的存储格式")
algorithm, salt_hex, iterations, expected_hash = parts
salt = bytes.fromhex(salt_hex)
iterations = int(iterations)
dk = hashlib.pbkdf2_hmac(
algorithm,
provided_password.encode('utf-8'),
salt,
iterations
)
computed_hash = dk.hex()
return hmac.compare_digest(computed_hash, expected_hash)
# 测试
password = "my_secure_password_123!"
print("正在哈希密码(可能需要几秒钟)...")
start = time.perf_counter()
hashed = hash_password(password)
elapsed = time.perf_counter() - start
print(f"哈希耗时: {elapsed:.2f}s")
print(f"存储格式: {hashed[:60]}...")
print(f"验证正确密码: {verify_password(hashed, password)}") # True
print(f"验证错误密码: {verify_password(hashed, 'wrong_password')}") # False
进度条可视化
def progress_bar(current, total, width=50, prefix="", suffix=""):
"""简易文本进度条"""
length = width
filled = round(length * current / max(total, 1))
bar = "█" * filled + "-" * (length - filled)
percent = 100 * current / max(total, 1)
elapsed_str = f"[{current}/{total}] [{percent:5.1f}%]"
print(f"\r{prefix} {elapsed_str} |{bar}| {suffix}", end="", flush=True)
if current >= total:
print()
# 模拟文件下载进度
import random
import time
total_size = 1_000_000 # 1MB
downloaded = 0
while downloaded < total_size:
chunk_size = random.randint(1000, 50000)
downloaded = min(downloaded + chunk_size, total_size)
progress_bar(downloaded, total_size, prefix="下载中", suffix=f"{downloaded//1024}KB")
time.sleep(0.05)
最佳实践总结
✅ 推荐做法
- 日期计算用 timedelta — 不要用手动加减秒数来计算天/周/月
- 字符串转 datetime 用 strptime — 不要直接用 eval 或正则解析日期字符串
- Counter 用于频率统计 — 比手写的 defaultdict 简洁得多
- defaultdict(int) — 替代
if key in dict: else的空值检查 - 密码哈希用 pbkdf2 — 不要用简单的 MD5/sha1 存密码
- HMAC 比较用 compare_digest — 防时序攻击
❌ 避免做法
import datetime
# ❌ 用时间戳换算天(容易出错)
days = timestamp // (24 * 60 * 60)
# ✅ 用 timedelta
from datetime import timedelta
delta = timedelta(seconds=timestamp)
days = delta.days
# ❌ 在循环中重复导入模块
for _ in range(1000000):
import json
data = json.loads(...)
# ✅ 在文件顶部统一导入
import json
for _ in range(1000000):
data = json.loads(...)
# ❌ 用 MD5 验证密码完整性
stored_md5 = hashlib.md5(password.encode()).hexdigest()
# ✅ 使用 PBKDF2/HMAC-SHA256
dk = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000)
总结
本文系统讲解了 Python 三大核心内建模块:
- datetime — 日期时间创建、timedelta 运算、strftime 格式化、strptime 解析、时区处理
- collections — namedtuple/deque/defaultdict/Counter/OrderedDict/ChainMap 的使用场景
- hashlib — MD5/SHA/SHA-256/SHA-512、HMAC 消息认证、文件校验和、PBKDF2 密码哈希
- 实战应用 — 日历生成器、FIFO 缓存、进度条、密码安全存储
下一篇文章将深入讲解常用内建模块(下):itertools、contextlib、urllib。
本文是 Python 教程系列的第四十三篇。
