为什么在Python中使用email模块时必须"from module import"?

1 投票
1 回答
859 浏览
提问于 2025-04-18 12:03

抱歉,如果这个问题不太准确,我在表达上有点困难。

我正在尝试使用电子邮件模块来创建一个简单的纯文本电子邮件,并在满足某些条件时发送它。但我遇到了各种异常情况,想要了解这些问题。我从官方示例中拿了一个简单的测试(https://docs.python.org/3.4/library/email-examples.html),它运行得很好。当我开始在我的项目中实现这个功能时,却出现了各种 "'module' object has no attribute 'something'" 的错误。我可以运行下面这样的代码,它工作得很好。

import email
import smtplib

# Create message object
msg = email.message.EmailMessage()

# Create the from and to Addresses
from_address = email.headerregistry.Address("Stack of Pancakes", "pancakes@gmail.com") 
to_address = email.headerregistry.Address("Joe", "pythontest@mailinator.com")

# Create email headers
msg['Subject'] = "subject of email"
msg['From'] = from_address
msg['To'] = to_address

#
email_content = "This is a plain text email"
msg.set_content(email_content)

server = smtplib.SMTP('smtp.gmail.com', 587)
server.starttls()
server.login("pancakes@gmail.com", "password")
server.send_message(msg)
server.quit()

这段代码运行得非常完美。然而,如果我改变代码的顺序,事情就开始出问题了,我不明白为什么。例如,如果我把 from_addressto_address 的代码放在调用 EmailMessage 之前,就像这样:

import email
import smtplib

# Create the from and to Addresses
from_address = email.headerregistry.Address("Stack of Pancakes", "pancakes@gmail.com") 
to_address = email.headerregistry.Address("Joe", "pythontest@mailinator.com")

# Create message object
msg = email.message.EmailMessage()

... other code

它就会报错 'module' object has no attribute 'headerregistry'。为什么创建 EmailMessage 时,其他代码能正常运行呢?

事实上,如果我有一个只包含这个的文件:

import email

to_address = email.headerregistry.Address("joe", "joe@joe.com")

它也会报同样的错误。

为了让这小段代码运行,我不得不这样做:

from email.headerregistry import Address

to_address = Address("joe", "joe@joe.com")

另外,还有一件非常奇怪的事,我可以让这段代码运行:

import email
import smtplib

email.message.EmailMessage()
to_address = email.headerregistry.Address("joe", "joe@joe.com")

但是如果我去掉 import smtplib,它又会开始出错,即使在那四行代码中我并没有使用 smtplib 的任何东西。


我很确定我可以不断尝试各种组合,最终让它正常工作,但我更希望能理解这些行为。这样我在生产环境中运行代码时会更有信心。

为什么我不能直接调用 import email 并用 email.headerregistry.Address 来声明我的对象,为什么我必须明确地用 from email.headerregistry import Address 来导入这个特定的函数?为什么在 import smtplib 的情况下能编译,但去掉它又失败了?为什么只有在调用 EmailMessage() 之后它才会正常工作?

通常我很擅长找到答案,但在这种情况下,我不知道该搜索什么。有很多关于“模块对象没有属性”的解决方案,但大多数都是重复命名的文件、循环导入、调用不存在的函数或检查某个属性是否存在。没有一个似乎能解释导入行为是如何工作的。我是把代码结构搞错了,还是电子邮件模块在捣乱?

1 个回答

5

使用import email并不会自动导入email包里面的所有模块。这意味着,如果你想使用email.headerregistry,你必须单独导入它,方法很简单:

import email.headerregistry

这样你就可以使用email.headerregistry.Address了。

你的代码在写了from email.headerregistry import Address之后也能正常工作,因为这条语句实际上会做和import email.headerregistry一样的事情,加载这个模块并获取Address。同样,smtplib也会导入一些和email相关的模块,其中一些可能会导入email.headerregistry

总结一下:一旦任何模块导入了email.headerregistry,这个子模块就会对所有导入了email的模块可见,即使它们从未明确请求过email.headerregistry。导入一个模块可能会让不相关的包的子模块可用,这种副作用可能会导致一些麻烦的错误,比如一个模块只有在某些其他模块之后导入时才会正常工作。幸运的是,现代工具像pylint和Eclipse的pydev在解决这种问题上表现得很好。

撰写回答