# 19.1.1. `email.message`: 表示一封电子邮件信息

源代码: Lib/email/message.py

3.6 新版功能: 1

位于 `email` 包的中心的类就是 `EmailMessage` 类。这个类导入自 `email.message` 模块。它是 `email` 对象模型的基类。`EmailMessage` 为设置和查询头字段内容、访问信息体的内容、以及创建和修改结构化信息提供了核心功能。

一份电子邮件信息由*头*和*负载*（又被称为*内容*）组成。头遵循 RFC 5322 或者 RFC 6532 风格的字段名和值，字段名和字段值之间由一个冒号隔开。这个冒号既不属于字段名，也不属于字段值。信息的负载可能是一段简单的文字消息，也可能是一个二进制的对象，更可能是由多个拥有各自头和负载的子信息组成的结构化子信息序列。对于后者类型的负载，信息的 MIME 类型将会被指明为诸如 multipart/* 或 message/rfc822 的类型。

`EmailMessage` 对象所提供的抽象概念模型是一个头字段组成的有序字典加一个代表 RFC 5322 标准的信息体的*负载*。负载有可能是一系列子``EmailMessage``对象的列表。你除了可以通过一般的字典方法来访问头字段名和值，还可以使用特制方法来访问头的特定字段（比如说 MIME 内容类型字段）、操纵负载、生成信息的序列化版本、递归遍历对象树。

`EmailMessage` 的类字典接口的字典索引是头字段名，头字段名必须是ASCII值。字典值是带有一些附加方法的字符串。虽然头字段的存储和获取都是保留其原始大小写的，但是字段名的匹配是大小写不敏感的。与真正的字典不同，键与键之间不但存在顺序关系，还可以重复。我们提供了额外的方法来处理含有重复键的头。

payload 是多样的。 对于简单的消息对象，它是字符串或字节串对象；对于诸如 multipart/* 和 message/rfc822 消息对象的 MIME 容器文档，它是一个 `EmailMessage` 对象列表。

class `email.message.``EmailMessage`(policy=default)

  如果指定了*policy*，消息将由这个*policy*所指定的规则来更新和序列化信息的表达。如果没有指定*policy*，其将默认使用 `default` 策略。这个策略遵循电子邮件的RFC标准，除了行终止符号（RFC要求使用``rn``，此策略使用Python标准的``n``行终止符）。请前往 `policy` 的文档获取更多信息。

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

    以一段字符串的形式返回整个消息对象。 若可选的 unixform 参数为真，返回的字符串会包含信封头。 unixform 的默认值是 `False`。 为了保持与基类 `Message` 的兼容性，maxheaderlen 是被接受的，但是其默认值是 `None`。 这个默认值表示行长度由策略的 `max_line_length` 属性所控制。从信息实例所获取到的策略可以通过 policy 参数重写。 这样可以对该方法所产生的输出进行略微的控制，因为指定的 policy 会被传递到 `Generator` 当中。

    扁平化信息可能会对 `EmailMessage` 做出修改。这是因为为了完成向字符串的转换，一些内容需要使用默认值填入（举个例子，MIME 边界字段可能会被生成或被修改）。

    请注意，这个方法是为了便利而提供，不一定是适合你的应用程序的最理想的序列化信息的方法。这在你处理多封信息的时候尤甚。如果你需要使用更加灵活的API来序列化信息，请参见 `email.generator.Generator` 。同时请注意，当 `utf8` 属性为``False``的时候（这是默认值），本方法将限制其行为为生成以“7 bit clean”方式序列化的信息。

    在 3.6 版更改: maxheaderlen*没有被指定时的默认行为从默认为0修改为默认为策略的*max_line_length。

  `__str__`()

    与``as_string(policy=self.policy.clone(utf8=True))``等价。这将让``str(msg)``产生的字符串包含人类可读的的序列化信息内容。

    在 3.4 版更改: 本方法开始使用``utf8=True``，而非 `as_string()` 的直接替身。使用``utf8=True``会产生类似于 RFC 6531 的信息表达。

  `as_bytes`(unixfrom=False, policy=None)

    以字节串对象的形式返回整个扁平化后的消息。 当可选的 unixfrom 为真值时，返回的字符串会包含信封标头。 unixfrom 的默认值为 `False`。 policy 参数可被用于重载从消息实例获取的默认 policy。 这可被用来控制该方法所产生的部分格式效果，因为指定的 policy 将被传递给 `BytesGenerator`。

    扁平化信息可能会对 `EmailMessage` 做出修改。这是因为为了完成向字符串的转换，一些内容需要使用默认值填入（举个例子，MIME 边界字段可能会被生成或被修改）。

    请注意，这个方法是为了便利而提供，不一定是适合你的应用程序的最理想的序列化信息的方法。这在你处理多封信息的时候尤甚。如果你需要使用更加灵活的API来序列化信息，请参见 `email.generator.BytesGenerator` 。

  `__bytes__`()

    与 `as_bytes()` 等价。这将让``bytes(msg)``产生一个包含序列化信息内容的字节序列对象。

  `is_multipart`()

    如果该信息的负载是一个子 `EmailMessage` 对象列表，返回 `True` ；否则返回 `False` 。在 `is_multipart()` 返回 `True` 的场合下，负载应当是一个字符串对象（有可能是一个使用了内容传输编码进行编码的二进制负载）。请注意， `is_multipart()` 返回 `True` 不意味着 `msg.get_content_maintype() == 'multipart'` 也会返回 `True` 。举个例子， `is_multipart` 在 `EmailMessage` 是 `message/rfc822` 类型的信息的情况下，其返回值也是 `True` 。

  `set_unixfrom`(unixfrom)

    将信息的信封头设置为 unixform ，这应当是一个字符串。（在 `mboxMessage` 中有关于这个头的一段简短介绍。）

  `get_unixfrom`()

    返回消息的信封头。如果信封头从未被设置过，默认返回 `None` 。

  以下方法实现了对信息的头字段进行访问的类映射接口。请留意，只是类映射接口，这与平常的映射接口（比如说字典映射）有一些语义上的不同。举个例子，在一个字典当中，键之间不可重复，但是信息头字段是可以重复的。不光如此，在字典当中调用 `keys()` 方法返回的结果，其顺序没有保证；但是在一个 `EmailMessage` 对象当中，返回的头字段永远以其在原信息当中出现的顺序，或以其加入信息的顺序为序。任何删了后又重新加回去的头字段总是添加在当时列表的末尾。

  这些语义上的不同是刻意而为之的，是出于在绝大多数常见使用情景中都方便的初衷下设计的。

  还请留意，无论在什么情况下，消息当中的任何信封头字段都不会包含在映射接口当中。

  `__len__`()

    返回头字段的总数，重复的也计算在内。

  `__contains__`(name)

    Return true if the message object has a field named name. Matching is done without regard to case and name does not include the trailing colon. Used for the `in` operator. For example:

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

  `__getitem__`(name)

    返回头字段名对应的字段值。 name 不含冒号分隔符。如果字段未找到，返回 `None` 。 `KeyError` 异常永不抛出。

    请注意，如果对应名字的字段找到了多个，具体返回哪个字段值是未定义的。请使用 `get_all()` 方法获取匹配字段名的所有字段值。

    使用标准策略（非 `compat32`）时，返回值是 `email.headerregistry.BaseHeader` 的某个子类的一个实例。

  `__setitem__`(name, val)

    在信息头中添加名为 name 值为 val 的字段。这个字段会被添加在已有字段列表的结尾处。

    请注意，这个方法 既不会 覆盖 也不会 删除任何字段名重名的已有字段。如果你确实想保证新字段是整个信息头当中唯一拥有 name 字段名的字段，你需要先把旧字段删除。例如：

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

    如果 `policy` 明确要求某些字段是唯一的（至少标准策略就有这么做），对这些字段在已有同名字段的情况下仍然尝试为字段名赋值会引发 `ValueError` 异常。这是为了一致性而刻意设计出的行为，不过我们随时可能会突然觉得“还是在这种情况下自动把旧字段删除比较好吧”而把这个行为改掉，所以不要以为这是特性而依赖这个行为。

  `__delitem__`(name)

    删除信息头当中字段名匹配 name 的所有字段。如果匹配指定名称的字段没有找到，也不会抛出任何异常。

  `keys`()

    以列表形式返回消息头中所有的字段名。

  `values`()

    以列表形式返回消息头中所有的字段值。

  `items`()

    以二元元组的列表形式返回消息头中所有的字段名和字段值。

  `get`(name, failobj=None)

    返回对应字段名的字段值。这个方法与 `__getitem__()` 是一样的，只不过如果对应字段名的字段没有找到，该方法会返回 failobj 。这个参数是可选的（默认值为 `None` ）。

  以下是一些与头有关的更多有用方法：

  `get_all`(name, failobj=None)

    返回字段名为 name 的所有字段值的列表。如果信息内不存在匹配的字段，返回 failobj （其默认值为 `None` ）。

  `add_header`(_name, _value, **_params)

    高级头字段设定。这个方法与 `__setitem__()` 类似，不过你可以使用关键字参数为字段提供附加参数。 _name 是字段名， _value 是字段 主 值。

    对于关键字参数字典 _params 的每个键值对而言，它的键被用作参数的名字，其中下划线被替换为短横杠（毕竟短横杠不是合法的Python标识符）。一般来讲，参数以 `键="值"` 的方式添加，除非值是 `None` 。要真的是这样的话，只有键会被添加。

    如果值含有非ASCII字符，你可以将值写成 `(CHARSET, LANGUAGE, VALUE)` 形式的三元组，这样你可以人为控制字符的字符集和语言。 `CHARSET` 是一个字符串，它为你的值的编码命名； `LANGUAGE` 一般可以直接设为 `None` ，也可以直接设为空字符串（其他可能取值参见 :rfc`2231` ）； ``VALUE` 是一个字符串值，其包含非ASCII的码点。如果你没有使用三元组，你的字符串又含有非ASCII字符，那么它就会使用 RFC 2231 中， `CHARSET` 为 `utf-8` ， `LANGUAGE` 为 `None` 的格式编码。

    例如：

        msg.add_header('Content-Disposition', 'attachment', filename='bud.gif')

    会添加一个形如下文的头字段：

        Content-Disposition: attachment; filename="bud.gif"

    带有非ASCII字符的拓展接口：

        msg.add_header('Content-Disposition', 'attachment',
                       filename=('iso-8859-1', '', 'Fußballer.ppt'))

  `replace_header`(_name, _value)

    替换头字段。只会替换掉信息内找到的第一个字段名匹配 _name 的字段值。字段的顺序不变，原字段名的大小写也不变。如果没有找到匹配的字段，抛出 `KeyError` 异常。

  `get_content_type`()

    返回信息的内容类型，其形如 maintype/subtype ，强制全小写。如果信息的 Content-Type 头字段不存在则返回 `get_default_type()` 的返回值；如果信息的 Content-Type 头字段无效则返回 `text/plain` 。

    （根据 RFC 2045 所述，信息永远都有一个默认类型，所以 `get_content_type()` 一定会返回一个值。 RFC 2045 定义信息的默认类型为 text/plain 或 message/rfc822 ，其中后者仅出现在消息头位于一个 multipart/digest 容器中的场合中。如果消息头的 Content-Type 字段所指定的类型是无效的， RFC 2045 令其默认类型为 text/plain 。）

  `get_content_maintype`()

    返回信息的主要内容类型。准确来说，此方法返回的是 `get_content_type()` 方法所返回的形如 maintype/subtype 的字符串当中的 maintype 部分。

  `get_content_subtype`()

    返回信息的子内容类型。准确来说，此方法返回的是 `get_content_type()` 方法所返回的形如 maintype/subtype 的字符串当中的 subtype 部分。

  `get_default_type`()

    返回默认的内容类型。绝大多数的信息，其默认内容类型都是 text/plain 。作为 multipart/digest 容器内子部分的信息除外，它们的默认内容类型是 message/rfc822 。

  `set_default_type`(ctype)

    设置默认的内容类型。 尽管并非强制，但是 ctype 仍应当是 text/plain 或 message/rfc822 二者取一。默认内容类型并不存储在 Content-Type 头字段当中，所以设置此项的唯一作用就是决定当 Content-Type 头字段在信息中不存在时，`get_content_type` 方法的返回值。

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

    在 Content-Type 头字段当中设置一个参数。如果该参数已于字段中存在，将其旧值替换为 value 。如果 header 是 `Content-Type` （默认值），并且该头字段于信息中尚未存在，则会先添加该字段，将其值设置为 text/plain ，并附加参数值。可选的 header 可以让你指定 Content-Type 之外的另一个头字段。

    如果值包含非ASCII字符，其字符集和语言可以通过可选参数 charset 和 language 显式指定。可选参数 language 指定 RFC 2231 当中的语言，其默认值是空字符串。 charset 和 language 都应当字符串。默认使用的是 `utf8` charset ，language 为 `None` 。

    如果 replace 为 `False` （默认值），该头字段会被移动到所有头字段的末尾。如果 replace 为 `True` ，字段会被原地更新。

    于 `EmailMessage` 对象而言， requote 参数已被弃用。

    请注意，头字段已有的参数值可以通过头字段的 `params` 属性来访问（举例： `msg['Content-Type'].params['charset']` ）。

    在 3.4 版更改: 添加了 `replace` 关键字。

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

    从 Content-Type 头字段中完全移去给定的参数。头字段会被原地重写，重写后的字段不含参数和值。可选的 header 可以让你指定 Content-Type 之外的另一个字段。

    于 `EmailMessage` 对象而言， requote 参数已被弃用。

  `get_filename`(failobj=None)

    返回信息头当中 Content-Disposition 字段当中名为 `filename` 的参数值。如果该字段当中没有此参数，该方法会退而寻找 Content-Type 字段当中的 `name` 参数值。如果这个也没有找到，或者这些个字段压根就不存在，返回 failobj 。返回的字符串永远按照 `email.utils.unquote()` 方法去除引号。

  `get_boundary`(failobj=None)

    返回信息头当中 Content-Type 字段当中名为 `boundary` 的参数值。如果字段当中没有此参数，或者这些个字段压根就不存在，返回 failobj 。返回的字符串永远按照 `email.utils.unquote()` 方法去除引号。

  `set_boundary`(boundary)

    将 Content-Type 头字段的 `boundary` 参数设置为 boundary 。 `set_boundary()` 方法永远都会在必要的时候为 boundary 添加引号。如果信息对象中没有 Content-Type 头字段，抛出 `HeaderParseError` 异常。

    请注意使用这个方法与直接删除旧的 Content-Type 头字段然后使用 `add_header()` 方法添加一个带有新边界值参数的 Content-Type 头字段有细微差距。 `set_boundary()` 方法会保留 Content-Type 头字段在原信息头当中的位置。

  `get_content_charset`(failobj=None)

    返回 Content-Type 头字段中的 `charset` 参数，强制小写。如果字段当中没有此参数，或者这个字段压根不存在，返回 failobj 。

  `get_charsets`(failobj=None)

    返回一个包含了信息内所有字符集名字的列表。如果信息是 multipart 类型的，那么列表当中的每一项都对应其负载的子部分的字符集名字。否则，该列表是一个长度为1的列表。

    列表当中的每一项都是一个字符串，其值为对应子部分的 Content-Type 头字段的 `charset` 参数值。如果该子部分没有此头字段，或者没有此参数，或者其主要 MIME 类型并非 text ，那么列表中的那一项即为 failobj 。

  `is_attachment`()

    如果信息头当中存在一个名为 Content-Disposition 的字段，且该字段的值为 `attachment` （大小写无关），返回 `True` 。否则，返回 `False` 。

    在 3.4.2 版更改: 为了与 `is_multipart()` 方法一致，is_attachment 现在是一个方法，不再是属性了。

  `get_content_disposition`()

    如果信息的 Content-Disposition 头字段存在，返回其字段值；否则返回 `None` 。返回的值均为小写，不包含参数。如果信息遵循 RFC 2183 标准，则返回值只可能在 inline 、 attachment 和 `None` 之间选择。

    3.5 新版功能.

  下列方法与信息内容（负载）之访问与操控有关。

  `walk`()

    `walk()` 方法是一个多功能生成器。它可以被用来以深度优先顺序遍历信息对象树的所有部分和子部分。一般而言， `walk()` 会被用作 `for` 循环的迭代器，每一次迭代都返回其下一个子部分。

    以下例子会打印出一封具有多部分结构之信息的每个部分的 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` 。使用 `_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` 的部分并非 `multiparts` ，但是它们真的包含子部分！ `is_multipart()` 返回 `True` ， `walk` 也深入进这些子部分中。

  `get_body`(preferencelist=('related', 'html', 'plain'))

    返回信息的 MIME 部分。这个部分是最可能成为信息体的部分。

    preferencelist 必须是一个字符串序列，其内容从 `related` 、 `html` 和 `plain` 这三者组成的集合中选取。这个序列代表着返回的部分的内容类型之偏好。

    在 `get_body` 方法被调用的对象上寻找匹配的候选者。

    If `related` is not included in preferencelist, consider the root part (or subpart of the root part) of any related encountered as a candidate if the (sub-)part matches a preference.

    When encountering a `multipart/related`, check the `start` parameter and if a part with a matching Content-ID is found, consider only it when looking for candidate matches. Otherwise consider only the first (default root) part of the `multipart/related`.

    If a part has a Content-Disposition header, only consider the part a candidate match if the value of the header is `inline`.

    If none of the candidates matches any of the preferences in preferencelist, return `None`.

    Notes: (1) For most applications the only preferencelist combinations that really make sense are `('plain',)`, `('html', 'plain')`, and the default `('related', 'html', 'plain')`. (2) Because matching starts with the object on which `get_body` is called, calling `get_body` on a `multipart/related` will return the object itself unless preferencelist has a non-default value. (3) Messages (or message parts) that do not specify a Content-Type or whose Content-Type header is invalid will be treated as if they are of type `text/plain`, which may occasionally cause `get_body` to return unexpected results.

  `iter_attachments`()

    Return an iterator over all of the immediate sub-parts of the message that are not candidate “body” parts. That is, skip the first occurrence of each of `text/plain`, `text/html`, `multipart/related`, or `multipart/alternative` (unless they are explicitly marked as attachments via Content-Disposition: attachment), and return all remaining parts. When applied directly to a `multipart/related`, return an iterator over the all the related parts except the root part (ie: the part pointed to by the `start` parameter, or the first part if there is no `start` parameter or the `start` parameter doesn’t match the Content-ID of any of the parts). When applied directly to a `multipart/alternative` or a non-`multipart`, return an empty iterator.

  `iter_parts`()

    Return an iterator over all of the immediate sub-parts of the message, which will be empty for a non-`multipart`. (See also `walk()`.)

  `get_content`(*args, content_manager=None, **kw)

    Call the `get_content()` method of the content_manager, passing self as the message object, and passing along any other arguments or keywords as additional arguments. If content_manager is not specified, use the `content_manager` specified by the current `policy`.

  `set_content`(*args, content_manager=None, **kw)

    Call the `set_content()` method of the content_manager, passing self as the message object, and passing along any other arguments or keywords as additional arguments. If content_manager is not specified, use the `content_manager` specified by the current `policy`.

  `make_related`(boundary=None)

    Convert a non-`multipart` message into a `multipart/related` message, moving any existing Content- headers and payload into a (new) first part of the `multipart`. If boundary is specified, use it as the boundary string in the multipart, otherwise leave the boundary to be automatically created when it is needed (for example, when the message is serialized).

  `make_alternative`(boundary=None)

    Convert a non-`multipart` or a `multipart/related` into a `multipart/alternative`, moving any existing Content- headers and payload into a (new) first part of the `multipart`. If boundary is specified, use it as the boundary string in the multipart, otherwise leave the boundary to be automatically created when it is needed (for example, when the message is serialized).

  `make_mixed`(boundary=None)

    Convert a non-`multipart`, a `multipart/related`, or a `multipart-alternative` into a `multipart/mixed`, moving any existing Content- headers and payload into a (new) first part of the `multipart`. If boundary is specified, use it as the boundary string in the multipart, otherwise leave the boundary to be automatically created when it is needed (for example, when the message is serialized).

  `add_related`(*args, content_manager=None, **kw)

    If the message is a `multipart/related`, create a new message object, pass all of the arguments to its `set_content()` method, and `attach()` it to the `multipart`. If the message is a non-`multipart`, call `make_related()` and then proceed as above. If the message is any other type of `multipart`, raise a `TypeError`. If content_manager is not specified, use the `content_manager` specified by the current `policy`. If the added part has no Content-Disposition header, add one with the value `inline`.

  `add_alternative`(*args, content_manager=None, **kw)

    If the message is a `multipart/alternative`, create a new message object, pass all of the arguments to its `set_content()` method, and `attach()` it to the `multipart`. If the message is a non-`multipart` or `multipart/related`, call `make_alternative()` and then proceed as above. If the message is any other type of `multipart`, raise a `TypeError`. If content_manager is not specified, use the `content_manager` specified by the current `policy`.

  `add_attachment`(*args, content_manager=None, **kw)

    If the message is a `multipart/mixed`, create a new message object, pass all of the arguments to its `set_content()` method, and `attach()` it to the `multipart`. If the message is a non-`multipart`, `multipart/related`, or `multipart/alternative`, call `make_mixed()` and then proceed as above. If content_manager is not specified, use the `content_manager` specified by the current `policy`. If the added part has no Content-Disposition header, add one with the value `attachment`. This method can be used both for explicit attachments (Content-Disposition: attachment) and `inline` attachments (Content-Disposition: inline), by passing appropriate options to the `content_manager`.

  `clear`()

    Remove the payload and all of the headers.

  `clear_content`()

    Remove the payload and all of the `Content-` headers, leaving all other headers intact and in their original order.

  `EmailMessage` objects have the following instance attributes:

  `preamble`

    The format of a MIME document allows for some text between the blank line following the headers, and the first multipart boundary string. Normally, this text is never visible in a MIME-aware mail reader because it falls outside the standard MIME armor. However, when viewing the raw text of the message, or when viewing the message in a non-MIME aware reader, this text can become visible.

    The preamble attribute contains this leading extra-armor text for MIME documents. When the `Parser` discovers some text after the headers but before the first boundary string, it assigns this text to the message’s preamble attribute. When the `Generator` is writing out the plain text representation of a MIME message, and it finds the message has a preamble attribute, it will write this text in the area between the headers and the first boundary. See `email.parser` and `email.generator` for details.

    Note that if the message object has no preamble, the preamble attribute will be `None`.

  `epilogue`

    The epilogue attribute acts the same way as the preamble attribute, except that it contains text that appears between the last boundary and the end of the message. As with the `preamble`, if there is no epilog text this attribute will be `None`.

  `defects`

    The defects attribute contains a list of all the problems found when parsing this message. See `email.errors` for a detailed description of the possible parsing defects.

class `email.message.``MIMEPart`(policy=default)

  This class represents a subpart of a MIME message. It is identical to `EmailMessage`, except that no MIME-Version headers are added when `set_content()` is called, since sub-parts do not need their own MIME-Version headers.

备注

1

  原先在3.4版本中以 provisional module 添加。过时的文档被移动至 email.message.Message: Representing an email message using the compat32 API 。