登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  python教程

Python email.headerregistry.Address 怎样区分邮箱显示名与地址

来源:17golang原创

时间:2026-10-27 13:44:41 308浏览 收藏

处理邮件头时,最容易混淆的是“别人看到的名字”和“真正用于投递的地址”。email.headerregistry.Address 把这两部分拆成了不同属性:display_name 是显示名,username 与 domain 组成邮箱主体,addr_spec 则是适合放进裸地址位置的完整字符串。

官方文档:https://docs.python.org/3/library/email/headerregistry.html

实际项目中可以先记住一个判断:要展示联系人,用 display_name;要拿实际邮箱,用 addr_spec;要批量处理 To、Cc 等字段,先访问地址头的 addresses。下面用一个解析场景把这几个属性串起来。

Address 的四个字段分别表示什么

假设地址写成 张三 。它不是一个需要手工切分的普通字符串,而是一个结构化的 Address 对象:

属性含义适合的用途
display_name显示名,去掉了外层引号联系人列表、邮件 UI
username@ 左侧的用户名部分需要拆分地址时使用
domain@ 右侧的域名部分域名统计或路由判断
addr_spec可作为裸地址使用的 username@domain去重、存储和下游接口

str(address) 表示符合邮件地址规则的完整形式,可能包含显示名;address.addr_spec 则只保留地址主体。两者都不是简单地对原始字符串做截取。

Python Address 对象拆分为显示名用户名域名和 addr_spec 的结构说明图
图1:Address 字段拆分说明图,展示显示名与实际地址部分的关系,不是运行截图。

单地址头怎样取出显示名和实际地址

从 From 读取单个发件人时,默认邮件策略会把结构化头部交给 headerregistry。单地址头可以通过 address 取得一个 Address 对象,然后分别读取属性:

from email import policy
from email.parser import Parser

# 使用默认策略,让地址头返回结构化对象,而不是只返回普通字符串。
raw_message = "From: 张三 \nSubject: 测试\n\n正文"
message = Parser(policy=policy.default).parsestr(raw_message)

# From 是单地址场景,address 对象包含显示名和实际地址。
sender = message["From"].address
print(sender.display_name)  # 张三:给读者看的名称
print(sender.addr_spec)     # zhangsan@example.com:实际地址
print(sender.username)      # zhangsan:@ 左侧
print(sender.domain)        # example.com:@ 右侧

这里不要把 str(message["From"]) 当成邮箱地址保存,因为它可能包含显示名,也可能带有邮件头的格式化结果。需要给数据库、去重逻辑或下游服务使用时,通常应选择 sender.addr_spec。

多地址头为什么要遍历 addresses

To、Cc 可能包含多个地址,甚至包含 RFC 5322 的地址组。此时应该读取地址头的 addresses,而不是只取第一个对象。这个属性返回一个地址元组;如果原始头部含有组,组中的单个地址也会按出现位置展平到这个元组中。

from email import policy
from email.parser import Parser

# To 头包含两个独立地址,演示批量读取时保留显示名与裸地址的区别。
raw_message = "To: 张三 , li@example.net\n\n"
message = Parser(policy=policy.default).parsestr(raw_message)

# 多地址头使用 addresses;不要假设 address 属性代表整个列表。
for recipient in message["To"].addresses:
    # 空显示名是正常情况,此时可以只展示 addr_spec。
    label = recipient.display_name or recipient.addr_spec
    print(f"{label} => {recipient.addr_spec}")
邮件头解析层与 Address addresses Group 关系的结构图
图2:AddressHeader 与 addresses、Group 的关系结构图,帮助区分单地址读取和列表遍历,不是运行截图。

如果只关心实际收件地址,可以在循环里统一取 recipient.addr_spec;如果要生成联系人展示文本,再组合 recipient.display_name 和 recipient.addr_spec。这样可以避免把没有显示名的地址误判成解析失败。

构造 Address 时怎样选择参数

构造对象有两种常见写法。已知完整地址时,可以直接传 addr_spec,库会解析出 username 和 domain;如果已经分别拿到了各部分,也可以显式传 display_name、username 和 domain。

from email.headerregistry import Address

# 已经拿到完整地址时,让 Address 负责拆出 username 和 domain。
address_from_spec = Address(display_name="李四", addr_spec="lisi@example.org")
print(address_from_spec.display_name)  # 李四
print(address_from_spec.addr_spec)     # lisi@example.org

# 已经拆开字段时,可以明确传入用户名和域名。
address_from_parts = Address(
    display_name="王五",       # 显示给收件人的名字
    username="wangwu",         # @ 左侧用户名
    domain="example.org",      # @ 右侧域名
)
print(str(address_from_parts))  # 王五 

addr_spec 是只读属性;如果需要改变邮箱主体,应创建新的 Address 对象,而不是试图给旧对象的 addr_spec 重新赋值。地址中的 Unicode 显示名可以被正确表示,但用户名部分仍需符合邮件地址语法,不能把任意 Unicode 文本直接当作用户名。

几个容易写错的判断

  • 把显示名当成地址:“张三”只是 display_name,真正地址在 addr_spec。
  • 用字符串切分邮箱:带引号、编码词或地址组时,手工按尖括号和 @ 切分很脆弱,优先使用结构化属性。
  • 多地址头只取一个:To 和 Cc 先看 addresses,不要把单地址头的读取方式套到列表上。
  • 把空显示名当成异常:没有显示名时,display_name 为空字符串是正常语义。

一段速查式完整写法

from email import policy
from email.parser import Parser

# 统一把收件人头部转换成可存储的裸地址记录。
raw_message = "To: 张三 , li@example.net\n\n"
message = Parser(policy=policy.default).parsestr(raw_message)

records = []
for address in message["To"].addresses:
    # display_name 为空时回退到 addr_spec,保证展示字段非空。
    records.append({
        "display_name": address.display_name,
        "username": address.username,
        "domain": address.domain,
        "addr_spec": address.addr_spec,
        "label": address.display_name or address.addr_spec,
    })

for record in records:
    print(record["label"], record["addr_spec"])

这段写法的关键不在于多写几个属性,而在于先判断当前数据是单地址还是地址列表:单地址头取 address,多地址头取 addresses;展示层用 display_name,业务层通常保存 addr_spec。

相关问题

没有显示名时应该读取什么?读取 addr_spec 作为展示回退值,display_name 为空字符串并不表示地址无效。

为什么不直接保存 str(Address)?str() 可能包含显示名和格式化内容;如果下游只需要邮箱主体,保存 addr_spec 更明确。

一个地址头里出现多个收件人怎么办?访问该头部对象的 addresses 元组,逐个处理其中的 Address 对象。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>