首页
/ 深入 CPython 的 email.message.Message 与 Compat32 策略:兼容 Python 3.2 的邮件对象模型全解析

深入 CPython 的 email.message.Message 与 Compat32 策略:兼容 Python 3.2 的邮件对象模型全解析

2026-09-07 22:04:04作者:温玫谨Lighthearted

本篇技术指南围绕 CPython 标准库 email 包中最核心的类 —— email.message.Message 展开,它是 Python 3.2 时代起就一直存在的"基础邮件消息类",其行为在默认策略 email.policy.Compat32 下与历史实现保持兼容。读者读完本文将掌握:Message 的 headers + payload 概念模型、构造与序列化(as_string / as_bytes)、类字典的头部访问接口、get_payload / attach / set_charset 等 payload 操作、Content-Type 与 RFC 2231 参数处理、walk() 深度遍历,以及 preamble / epilogue / defects 等实例属性;同时对照现代 EmailMessage 新 API,了解哪些方法是"legacy 方法"以及如何迁移,从而能够在维护旧代码与开发新代码之间做出正确选择。

Message 在 email 模块中的定位:与 EmailMessage 的分工

email.message.Message 被设计为表示一封 RFC 5322 邮件消息的基类。官方文档明确指出它与 EmailMessage(在现代 API 中推荐使用的类)非常相似,区别在于:

  • Message 没有 EmailMessage 额外加入的高层方法(如 set_contentmake_alternativeget_body 等);
  • Message 上某些方法的默认行为略有不同
  • 本文档还会讲解一些虽然在 EmailMessage 上也支持、但除非处理遗留代码否则不推荐使用的方法(这些方法在文档中被明确标注为 legacy)。

两者在哲学与结构上是一致的:EmailMessage 继承自 MIMEPart,而 MIMEPart 又继承自 Message。从 源码 Lib/email/message.py 的类定义顺序(Message 在第 141 行,EmailMessage 在第 1212 行,中间是作为消息树中各 MIME 部件基类的 MIMEPart,第 989 行)可以清楚看到这一继承关系。

Compat32 策略:为什么它决定本文的行为描述

本文描述的是 Message 在默认策略 compat32 下的行为。compat32Compat32 策略类的一个模块级单例,定义于 Lib/email/_policybase.py#L289-L392,其 docstring 说明它是"向后兼容策略,复刻 email 包 5.1(即 Python 3.2 时代)的行为"。因此:

  • 若你使用其他策略(如 policy.defaultpolicy.SMTP),应当改用 EmailMessage,因为新策略下 Message 的若干行为(尤其是头部存储与折叠)已不再被精确保留;
  • 若你写的是面向新项目的新代码,也请优先选择 EmailMessageMessage 的真正战场是解析存量邮件、维护老代码库。

概念模型:headers + payload

一封消息由**头部(headers)载荷(payload)**构成:

  • 头部是 RFC 5322 风格的 名字: 值,字段名与值之间以冒号分隔,冒号本身不属于任何一方;
  • payload 可以是一段简单的文本、一个二进制对象,也可以是"结构化的子消息序列"——每个子消息拥有自己的一组头部和自己的 payload。后者由消息具有 MIME 类型(如 multipart/*message/rfc822)来标识。

Message 对象提供的概念模型是:一个头部有序字典,外加若干方法用于:

  • 从头部中读取专门化信息(content type、charset、filename、boundary 等);
  • 访问 payload;
  • 生成消息的序列化表示;
  • 在对象树上递归遍历。

头部字典的特殊语义与 envelope header

Message 的伪字典以头部字段名(必须是 ASCII)为索引,值为"应当只包含 ASCII"的字符串(对非 ASCII 输入有特殊处理,但未必总能得到正确结果)。头部以保留大小写形式存储,但字段名匹配时大小写不敏感。此外还有几点与普通 dict 的关键差异,在 源码中 Message 的类注释 也有印证:

  1. 允许重复头部:普通字典不允许重复键,但一封邮件可以合法出现多个 Received 之类字段,需要 get_all 等专门方法访问;
  2. 顺序有保证keys() 等总是按头部在原始消息中出现(或被添加)的顺序返回,任何被删除后重新添加的头部会被追加到末尾;
  3. envelope header 不参与映射接口:每封消息还可携带一个独立的信封头部,又称 Unix-From 头部或 From_ 头部。

payload 则分两种情况:

  • 简单消息对象:payload 是一个 strbytes
  • MIME 容器文档(multipart/*message/rfc822):payload 是 Message 对象列表。

Message.__init__ 的源码(Lib/email/message.py#L156-L166)展示了这些字段在实例上的落地形式:self._headers = []self._unixfrom = Noneself._payload = Noneself._charset = Noneself.preamble = self.epilogue = Noneself.defects = [],以及默认内容类型 self._default_type = 'text/plain'

构造:Message(policy=compat32)

class email.message.Message(policy=compat32)
  • 若不指定 policy,使用 Compat32 策略(兼容 Python 3.2 的 email 包行为);
  • 若指定,则 policy 必须是某个 email.policy 类的实例,消息的更新与序列化将遵循该策略的规则。

policy 关键字参数于 Python 3.3 加入。

在实践中,你通常不会直接 Message() 构造对象,而是经由解析器产出——例如使用 email.parserParser/BytesParser,或其便捷函数 message_from_stringmessage_from_bytesmessage_from_filemessage_from_binary_file,它们默认即返回策略为 compat32Message 实例。若要收到 EmailMessage,则需传入 policy=policy.default

序列化:as_string、as_bytes 与 Generator

as_string(unixfrom=False, maxheaderlen=0, policy=None)

将整封消息"压平"(flatten)为一个字符串:

  • unixfrom 为真时,返回的字符串包含信封头部,默认 False
  • 向后兼容的关键默认值maxheaderlen 默认是 0(禁用头部换行折叠),若需折叠必须显式传入其他值;此方法会忽略策略中配置的 max_line_length
  • policy 参数(Python 3.4 加入)用于覆盖消息实例自带的策略,传入的策略会被交给 Generator,从而控制部分输出格式。

源码(Lib/email/message.py#L173-L195)显示它内部创建 StringIOGenerator(fp, mangle_from_=False, maxheaderlen=maxheaderlen, policy=policy) 后调用 flatten

需要留意两个易踩的坑:

  1. 压平可能改写消息:若为了完成向字符串的转换而需要填充默认值(例如自动生成或修改 MIME boundary),调用方持有的 Message 会被就地修改;
  2. 默认不处理 mbox 转义as_string 默认不做 Unix mbox 格式要求的"行首 From 转义(mangling)"。若你需要更精细的控制,应直接实例化 email.generator.Generator 并调用其 flatten
from io import StringIO
from email.generator import Generator
fp = StringIO()
g = Generator(fp, mangle_from_=True, maxheaderlen=60)
g.flatten(msg)
text = fp.getvalue()
  1. 二进制数据兜底替换:若消息包含未按 RFC 标准编码的二进制数据,这些不合规字节会被替换为 Unicode "未知字符"码点(U+FFFD 之类)。要无损序列化二进制,请改用 as_bytesBytesGenerator(见下)。

__str__() 等价于 as_string(),因此 str(msg) 即可得到格式化字符串。

as_bytes(unixfrom=False, policy=None)(3.4 新增)

as_string 对应,但返回 bytes 对象;policy(同样 3.4 加入)会被交给 BytesGenerator。源码(Lib/email/message.py#L202-L215)内部使用 BytesIOBytesGenerator(fp, mangle_from_=False, policy=policy)

对需要 mbox 转义或更细控制的二进制输出,同样建议绕过便捷方法而直接用 BytesGenerator

from io import BytesIO
from email.generator import BytesGenerator
fp = BytesIO()
g = BytesGenerator(fp, mangle_from_=True, maxheaderlen=60)
g.flatten(msg)
text = fp.getvalue()

__bytes__() 等价于 as_bytes(),即 bytes(msg)

email.generator源码 Lib/email/generator.py 中可以看到,Generator.flatten(msg, unixfrom=False, linesep=None)BytesGenerator.flatten 是真正递归写出消息树的地方;mangle_from_ 的默认行为也取决于是否显式传入策略(Lib/email/generator.py#L38-L68)。

结构与载荷操作

is_multipart()

若消息 payload 是子 Message 的列表则返回 True,否则 False。当它为 False 时,payload 应当是字符串对象(可能是 CTE 编码过的二进制载荷)。

一个值得注意的细节:is_multipart() 返回 True不一定意味着 msg.get_content_maintype() == 'multipart'。例如 message/rfc822 类型的 Message,其 is_multipart() 同样为 True(源码实现是判断 self._payload 是否为 list,Lib/email/message.py#L217-L219)。这一点在 walk() 一节会再次体现。

信封头部:set_unixfrom / get_unixfrom

set_unixfrom(unixfrom)   # unixfrom 应为字符串
get_unixfrom()           # 从未设置时返回 None

attach(payload)

把给定 payload 追加到当前载荷中。调用前当前载荷必须是 NoneMessage 列表;调用后 payload 始终变成 Message 列表。若想把载荷设置为标量(如字符串),请改用 set_payload。在非 multipart 的字符串载荷上调用 attach 会抛出 TypeError——源码(Lib/email/message.py#L233-L247)在 payload 已存在且不可 append 时抛出 TypeError("Attach is not valid on a message with a non-multipart payload")

这是 legacy 方法,在 EmailMessage 上由 set_content 及相关的 make_*add_* 方法取代。

get_payload(i=None, decode=False)

返回当前载荷:

  • is_multipart()True 时返回 Message 列表;为 False 时返回字符串。若返回的是列表而你直接修改该列表对象,将就地修改消息的载荷;
  • i:在 multipart 情况下返回第 i 个元素(从 0 计数),越界(i < 0i >= 数量)抛 IndexError;若载荷是字符串(非 multipart)却给了 i,抛 TypeError
  • decodeTrue 时:若消息非 multipart,依据 Content-Transfer-Encoding 头部解码载荷——值为 quoted-printablebase64 时解码,其他编码或缺少该头部时原样返回;任何情况下返回值都是二进制数据(bytes);若消息是 multipart 且 decodeTrue,返回 None。若 base64 载荷不完整(缺少 padding、出现 base64 字母表外字符),相应缺陷会被追加到消息的 defects 属性(分别是 email.errors 中的 InvalidBase64PaddingDefectInvalidBase64CharactersDefect);
  • decodeFalse(默认)时:正文以字符串形式返回、不对 CTE 解码。但对 Content-Transfer-Encoding8bit 的情况,会尝试用 Content-Type 头部指定的 charset 以及 replace 错误处理器解码原始字节;若未指定 charset,或指定了 email 包不识别的 charset,则按默认 ASCII 字符集解码。

源码 Lib/email/message.py#L249-L341 中保留了一张注释形式的逻辑表,清晰总结了 (i, decode, is_multipart) 三者的所有组合结果,例如:非 multipart 且 decode=True 时 payload 被解码为 bytes;base64 分支调用 decode_b 并将返回的缺陷逐一通过 self.policy.handle_defect(self, defect) 上报。

这是 legacy 方法,在 EmailMessage 上由 get_content()iter_parts() 取代。

set_payload(payload, charset=None)

将整个消息对象的载荷设为 payload;设置字符集的任务交给客户端,需自行保证载荷不变量。可选的 charset 设置消息的默认字符集,细节见 set_charset。这也是 legacy 方法,在 EmailMessage 上由 set_content 取代。

set_charset(charset)

设置载荷字符集,charset 可以是 email.charset.Charset 实例、命名字符集的字符串或 None

  • 字符串会被转换为 Charset 实例;
  • None:从 Content-Type 头移除 charset 参数(消息其余部分不改动);
  • 其他类型抛 TypeError

设置时的副作用(可从 源码 Lib/email/message.py#L363-L408 逐行印证):

  1. 若没有现有 MIME-Version 头,则添加一个;
  2. 若没有现有 Content-Type 头,则添加值为 text/plain 的一个;
  3. 无论 Content-Type 头是否已存在,其 charset 参数都会被设为 charset.output_charset
  4. charset.input_charsetcharset.output_charset 不同,payload 会被重新编码output_charset
  5. 若没有现有 Content-Transfer-Encoding 头,则按需使用指定 Charset 对 payload 做传输编码,并添加相应头部;若该头已存在,则假定 payload 已用该 CTE 正确编码,不做改动

EmailMessage 上,此能力由 set_contentcharset 参数取代。

get_charset()

返回与消息载荷关联的 Charset 实例。注意它是 legacy 方法——在 EmailMessage 上总是返回 None。它与 get_content_charset() 的区别:前者返回正文默认编码对应的 Charset 实例,后者返回 Content-Typecharset 参数的小写字符串。

映射接口:像字典一样读写头部

Message 实现了类字典(mapping-like)接口来读写 RFC 5322 头部,但有别于普通字典的若干语义差异(前面概念模型一节已述,其中最关键的是:允许重复头部、顺序有保证)。这些差异是有意设计的,取向是"最大程度的便捷"。

注意:在任何情况下,消息中存在的 envelope header 都不参与该映射接口;由 bytes 解析出的模型中,若某些头部值(违反 RFC)包含非 ASCII 字节,通过此接口取回时会以 charset="unknown-8bit"email.header.Header 对象表示。

方法与语义

方法 语义与差异
__len__() 返回头部总数,含重复项
__contains__(name) 字段名大小写不敏感匹配,返回消息是否有该字段;name 不应含冒号
__getitem__(name) 返回字段值;头缺失时返回 None从不抛 KeyError;重名时"返回哪一个"未定义,请用 get_all
__setitem__(name, val) 把字段追加到末尾;不会覆盖或删除同名已有头。想让新值成为唯一同名头,先删再加
__delitem__(name) 删除消息中该字段名的所有出现;字段不存在也不抛异常
keys() / values() / items() 按原始顺序返回字段名 / 值 / (名, 值) 二元组列表
get(name, failobj=None) __getitem__,但头缺失时返回 failobj(默认 None

文档给出的典型惯用法:

if 'message-id' in myMessage:
    print('Message-ID:', myMessage['message-id'])

以及"先删后设以保证唯一":

del msg['subject']
msg['subject'] = 'Python roolz!'

值得补充的源码细节:__setitem__ 会先查询 self.policy.header_max_count(name)——若策略规定某头最多出现 N 次(如 ReceivedFrom 等在新策略下有数量上限),超限会抛 ValueError;随后以 self.policy.header_store_parse(name, val) 存储(Lib/email/message.py#L433-L449)。而 values()/items()/get()/get_all() 取回值时都会经过 policy.header_fetch_parse,这正是 compat32 下非 ASCII 原始字节会被转换为 unknown-8bit Header 的钩子。在 Lib/email/_policybase.py#L329-L334header_fetch_parse 中可以看到它调用 _sanitize_header,凡检测到代理字符(surrogates)的值都会被包装为使用 unknown-8bit 字符集的 Header

附加常用方法

get_all(name, failobj=None)

返回指定 name 字段的全部取值列表(保持出现顺序、可含重复);若消息中不存在该字段,返回 failobj(默认 None)。这是访问 Received 等重复头部的正规途径。

add_header(_name, _value, **_params)

扩展版"设头"。类似 __setitem__,但可把附加参数以关键字形式一并写入,_name 是要添加的字段名、_value 是主值。规则:

  • 每个关键字参数的键作为参数名,下划线转换为连字符(因为连字符在 Python 标识符中非法);
  • 通常参数写作 key="value";若值为 None 则只写键;
  • 值含非 ASCII 字符时,可传三元组 (CHARSET, LANGUAGE, VALUE)(charset 命名编码用的字符集,LANGUAGE 通常为 None 或空串,见 RFC 2231);若传了含非 ASCII 的普通字符串而非三元组,则自动按 RFC 2231 以 utf-8 / 语言 None 编码。

示例与产出:

msg.add_header('Content-Disposition', 'attachment', filename='bud.gif')
Content-Disposition: attachment; filename="bud.gif"
msg.add_header('Content-Disposition', 'attachment',
               filename=('iso-8859-1', '', 'Fußballer.ppt'))
Content-Disposition: attachment; filename*="iso-8859-1''Fu%DFballer.ppt"

源码(Lib/email/message.py#L559-L587)逐项遍历 _params,值为 None 时仅输出 key(下划线转连字符),否则交给 _formatparam 格式化,最终经 self[_name] = '; '.join(parts) 走标准的 __setitem__ 通道。

replace_header(_name, _value)

替换头:替换消息中第一个匹配 _name 的头,同时保留头顺序与字段名原有大小写;若找不到匹配头,抛 KeyError。源码(Lib/email/message.py#L589-L602)在遍历中命中即改写对应槽位并 break,遍历完仍未命中才抛异常。

Content-Type 与内容类型 API

get_content_type() / get_content_maintype() / get_content_subtype()

  • get_content_type():返回小写形式的 maintype/subtype;无 Content-Type 头时返回 get_default_type() 给出的默认类型。依 RFC 2045,消息总有默认类型,因此本方法总会返回一个值——RFC 2045 规定默认类型是 text/plain,除非消息位于 multipart/digest 容器内(此时是 message/rfc822);若 Content-Type 头是无效类型说明,RFC 2045 强制回落到 text/plain
  • 后两者分别返回主类型与子类型部分。

源码(Lib/email/message.py#L608-L648)显示 get_content_type 在缺头时返回默认类型,在有头时取 = 前的主值转小写,并检查是否恰好含一个 /ctype.count('/') != 1 时回落 text/plain)。

get_default_type() / set_default_type(ctype)

  • 返回默认内容类型:大多数消息为 text/plain;作为 multipart/digest 容器子部件的消息为 message/rfc822
  • 设置默认内容类型:ctype 应为 text/plainmessage/rfc822(但不强制校验)。默认类型不存储在 Content-Type 头中(实例字段 _default_type 初值为 'text/plain')。

set_type(type, header='Content-Type', requote=True)

设置 Content-Type 头的主/子类型。type 必须是 maintype/subtype 形式的字符串,否则抛 ValueError。该方法替换 Content-Type 头,但保留原有参数;requoteFalse 时保留现有引号风格,否则重新引号(默认)。可经 header 指定替代头。设置 Content-Type 时同时会添加 MIME-Version 头。它是 legacy 方法,在 EmailMessage 上由 make_*add_* 方法取代。

MIME 参数(RFC 2045 / RFC 2231)API

get_params(failobj=None, header='content-type', unquote=True)

返回 Content-Type 头参数的列表,元素是按 = 切分的键值二元组;参数中没有 = 时值为空串;有 = 时取值规则同 get_param,且 unquoteTrue(默认)时解除引号。failobj 用于没有该头时返回;header 指定要搜索的替代头。这是 legacy 方法,在 EmailMessage 上由头部对象自带的 params 属性取代。

get_param(param, failobj=None, header='content-type', unquote=True)

返回 Content-Type 头参数 param 的字符串值;无该头或无该参数时返回 failobj(默认 None)。规则要点:

  • 参数键总是大小写不敏感比较;
  • 返回值可为字符串,也可为 RFC 2231 编码时的三元组 (CHARSET, LANGUAGE, VALUE)CHARSETLANGUAGE 都可能为 None,此时应认为 VALUEus-ascii 编码,LANGUAGE 通常可忽略;
  • 若应用不在乎是否 RFC 2231 编码,可用 email.utils.collapse_rfc2231_valueemail.utils)把返回值折叠为解码后的 Unicode 字符串:
rawparam = msg.get_param('foo')
param = email.utils.collapse_rfc2231_value(rawparam)
  • 无论哪种情况,参数值(返回的字符串或三元组的 VALUE)除非 unquoteFalse,否则总是解除引号的。

set_param(param, value, header='Content-Type', requote=True, charset=None, language='', replace=False)

Content-Type 头中设置参数:

  • 参数已存在则用 value 替换其值;若消息尚无 Content-Type 头,则先置为 text/plain,再依 RFC 2045 追加新参数;
  • header 可指定替代头;requoteFalse 时关闭必要的引号(默认 True);
  • 指定 charset 时按 RFC 2231 对参数编码,language 指定 RFC 2231 语言(默认空串),两者应为字符串;
  • replaceFalse(默认)时头部被移动到头部列表末尾;为 True 时原位更新(replace 关键字于 3.4 加入)。

del_param(param, header='content-type', requote=True)

Content-Type 头彻底移除指定参数(含其值),头被就地重写;默认在必要时给所有值加引号(requoteFalse 可关闭)。

文件名、边界与 charset 便捷方法

get_filename(failobj=None)

返回消息 Content-Dispositionfilename 参数的值;若该头没有 filename,回退查找 Content-Type 头的 name 参数;两者都无或头缺失则返回 failobj。返回值总是按 email.utils.unquote 解除引号的字符串。源码(Lib/email/message.py#L850-L864)进一步调用 collapse_rfc2231_value(...).strip() 处理 RFC 2231 编码名。

get_boundary(failobj=None)

返回 Content-Typeboundary 参数值;头缺失或无此参数返回 failobj;返回字符串总是解除引号(源码 Lib/email/message.py#L866-L877 还会对折叠结果做 rstrip(),因为 RFC 2046 说 boundary 可以以空白开头但不能以空白结尾)。

set_boundary(boundary)

Content-Type 头的 boundary 参数设为 boundary,必要时总会加引号;若消息没有 Content-Type 头,抛 email.errors.HeaderParseError(源码 Lib/email/message.py#L879-L923)。

值得注意:它与"先删除 Content-Type 头再用 add_header 加新 boundary"存在细微差别——set_boundary 保持 Content-Type 头在头部列表中的原有顺序,但不保留原始 Content-Type 头中可能存在的续行

get_content_charset(failobj=None)

返回 Content-Typecharset 参数、强制转为小写后的值;无该头或无 charset 参数时返回 failobj。注意此方法区别于 get_charset(后者返回正文默认编码的 Charset 实例)。

get_charsets(failobj=None)

返回消息中的字符集名列表:若为 multipart,对 payload 中每个子部件各有一个元素;否则列表长度为 1(只含消息自身)。列表元素是对应子部件 Content-Typecharset 参数的值(字符串);若子部件没有 Content-Type 头、没有 charset 参数、或主 MIME 类型不是 text,则该元素为 failobj。源码(Lib/email/message.py#L955-L971)实现为对 self.walk() 逐部件调用 get_content_charset 的列表推导。

get_content_disposition()(3.5 新增)

返回消息 Content-Disposition 头去掉参数后的小写值(若遵循 RFC 2183,取值为 inlineattachment),无该头时返回 None

深度遍历:walk()

walk() 是一个通用生成器,用于以深度优先的顺序遍历消息对象树的所有部件与子部件,典型用法是作为 for 循环的迭代器。

官方文档示例(使用 CPython 测试数据中的一份 multipart/report 报文)打印每个部件的 MIME 类型:

>>> for part in msg.walk():
...     print(part.get_content_type())
multipart/report
text/plain
message/delivery-status
text/plain
text/plain
message/rfc822
text/plain

walk 会进入任何 is_multipart()True 的部件的子部件,即便 msg.get_content_maintype() == 'multipart'False——这正是前面 is_multipart 一节强调的语义。示例用 _structure 调试辅助函数展示树形结构即可看明白:

>>> for part in msg.walk():
...     print(part.get_content_maintype() == 'multipart',
...           part.is_multipart())
True True
False False
False True
False False
False False
False True
False False
>>> _structure(msg)
multipart/report
    text/plain
    message/delivery-status
        text/plain
        text/plain
    message/rfc822
        text/plain

其中 message 部件不是 multipart,却包含子部件——is_multipart() 返回 Truewalk 因此继续下钻。

实现层面,Message.walk 是直接以 email.iteratorswalk 函数作为方法导入的(见 Lib/email/message.py#L985-L986from email.iterators import walk)。email.iterators 模块还提供 _structure(用于打印树形结构)、body_line_iteratortyped_subpart_iterator 等辅助迭代工具,实测报文可来自 CPython 测试套件 Lib/test/test_email/ 的数据文件。

三个实例属性:preamble、epilogue、defects

Message 对象可选携带两个用于生成 MIME 明文时使用的属性,外加一个记录解析问题的属性。

preamble

MIME 文档格式允许在"头部后的空行与第一个 multipart boundary 串之间"存在一段文本。正常情况下,MIME 感知的邮件阅读器看不到这段文本(它在标准 MIME 盔甲之外),但在查看原始文本或使用非 MIME 感知阅读器时会显现。preamble 属性存放这段"盔甲外的前导文本":

  • email.parser.Parser 解析到头部之后、第一个 boundary 串之前的文本时,将其赋给 preamble
  • email.generator.Generator 写出 MIME 消息明文时,若发现消息带有 preamble,会把它写在头部与第一个 boundary 之间的区域;
  • 若消息没有 preamble,该属性为 None

epilogue

preamble 行为相同,但内容是最后一个 boundary 之后到消息结束之间的文本。你不需要为了在文件末尾得到换行而把 epilogue 设为空串——Generator 会自行处理结尾换行。

defects

包含解析该消息时发现的所有问题(缺陷)列表,具体缺陷类型见 email.errors。前面 get_payload 一节提到的不合规 base64 也会在此登记。

Legacy 方法速查:Message → EmailMessage 迁移对照

文档在多个 legacy 方法上标注了替代关系,汇总如下,便于在新代码中快速迁移:

Message(compat32)legacy 方法 EmailMessage 上的替代
attach(payload) set_content + make_* / add_*
get_payload(i, decode) get_content()iter_parts()
set_payload(payload, charset) set_content()
set_charset(charset) set_content(..., charset=...)
get_charset() 总是返回 None(无对应物)
get_params / get_param 各头部对象返回的 params 属性
set_type make_* / add_* 方法

若确认只在 compat32 策略下工作(存量代码、老式脚本、测试夹具),继续使用 Message 与上述 legacy 方法完全可行;一旦切换到新策略或开始新项目,请直接使用 EmailMessage

综合实战:构建 → 序列化 → 解析 → 遍历

将上述 API 串成一个完整流程示例:先手工构造一个带附件的 multipart 消息,序列化为文本,再解析回来并遍历其结构。这演示了 Message 的典型使用闭环(注意构造/解析默认均处于 compat32 语义下):

from email.message import Message
from email.mime.text import MIMEText

# --- 构建:容器消息 + 两个子部分 ---
outer = Message()
outer['Subject'] = '示例:compat32 下的 Message 树'
outer['From'] = 'alice@example.org'
outer['To'] = 'bob@example.net'

text_part = MIMEText('你好,这是正文。', 'plain', 'utf-8')
text_part.add_header('Content-Disposition', 'inline')

attach_part = Message()
attach_part['Content-Type'] = 'application/octet-stream'
attach_part.set_payload(b'\x00\x01binary payload', charset=None)
attach_part.add_header('Content-Disposition', 'attachment',
                       filename=('utf-8', '', '示例.bin'))

outer.set_type('multipart/mixed')     # 自动补 MIME-Version
outer.attach(text_part)
outer.attach(attach_part)

# --- 序列化:字符串与字节两种途径 ---
print(outer.as_string(maxheaderlen=78))
raw_bytes = outer.as_bytes()          # 二进制载荷无损输出

# --- 解析回树 ---
from email import message_from_bytes
msg = message_from_bytes(raw_bytes)

# --- 遍历全部子部件并输出类型 ---
for part in msg.walk():
    print(part.get_content_type(),
          part.get_filename(),
          len(part.get_payload()) if part.is_multipart() else '-')

# --- 结构级调试输出(_structure 来自 email.iterators) ---
from email.iterators import _structure
_structure(msg)

几点说明:

  • set_type('multipart/mixed') 会同时补写 MIME-Version: 1.0,这是文档与源码都确认的行为;
  • Generator(..., mangle_from_=True, maxheaderlen=60) 手动压平可额外获得 mbox 行首 From 转义与头部折叠控制;as_string 默认不做这两件事;
  • as_string 默认 maxheaderlen=0(不折叠、忽略策略 max_line_length),想要折叠必须显式传值或改用 EmailMessageMIMEPart.as_stringLib/email/message.py#L998-L1012 中对 maxheaderlen=None 的情形回落到 policy.max_line_length,这是新旧两套默认值差异的典型缩影)。

延伸阅读

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388