将之前利用smtplib和email实现SMTP发送邮件小程序进行重构。
发现了email.mime.multipart类两个使用上的小问题:
- MIMEMultipart类要修改参数,不能直接使用赋值,必需使用replace_header(),不然将会是该参数的内容累加。比如:设置mail[‘Subject’]后,再一次赋值给mail[‘Subject’],结果是两次赋值的内容累加。mail[‘From’]/mail[‘To’]也一样的道理。
- 添加一个正文后,因为已经编码,修改起来很麻烦。最好是先删除原正文MIMEText,后重新添加新的MIMEText。若是直接再次添加一个正文(MIMEText),那么在邮件中会显示多个正文内容的累加。MIMEMultipart类没有提供删除MIMEText的方法,但所有MIMEText都存放在MIMEMultipart类的_payload列表中。所以先找到正文的MIMEText对象,再使用列表的remove()方法来删除。
说明:
- 考虑到有可能更换SMTP服务器,所以把SMTP服务器的设置信息用类来管理。
- 为实现主题(mail[‘subject’])和正文(content)能直接赋值,使用了@property装饰subject和content。
- 其中的Smtp类实现了上下文管理,可以使用with as。
- QQ的smtp登录现在使用”CardDAV/CalDAV服务“,也就是你需要在QQ邮件的设置/帐户里获取授权码,用这个授权码替代密码。
- 未实现异常处理。----主要是我想不到怎么处理 😦
使用方法如下:
from my_emailV2 import Email
from_email = '******@163.com'
to_email = '******@163.com'
# 要发多个邮箱时,请用","分隔开多个电子邮箱地址
# to_email = '******@163.com, ******@qq.com'
email = Email(from_email=from_email, to_email=to_email, smtp_server='qq')
email.subject = '测试邮件'
email.content = '邮件正文:这是一封测试邮件'
# 设置主题和正文也可使用以下方法:
# email.add_subject('测试邮件')
# email.add_content('邮件正文:这是一封测试邮件')
email.add_attachment('~/副本.txt')
email.add_attachment('~/test.py')
# 使用add_attachments()方法添加多个附件。
# email.add_attachments(['~/副本.txt', '~/test.py'])
email.send()
代码如下:
# -*- coding: utf-8 -*-
"""
电子邮件类V2.0:用OO思想对V1.0版本进行重构。
"""
import smtplib
import os
import base64
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
class Server163:
host = 'smtp.163.com'
user = '******@163.com'
pwd = '******'
class ServerQQ:
host = 'smtp.qq.com'
user = '******@qq.com'
pwd = '******' # CardDAV/CalDAV 的授权码
server_config = {
'default': ServerQQ,
'163': Server163,
'qq': ServerQQ
}
class Email(object):
"""
电子邮件类
为实现mail.subject和mail.content直接赋值,使用了@property装饰subject和content。
MIMEMultipart类要修改参数,不能直接使用赋值,必需使用replace_header(),不然将会是该参数的内容累加。
比如设置mail['Subject']后,再一次赋值给mail['Subject'],结果是两次赋值的内容累加。mail['From']/mail['To']也一样的道理。
MIMEMultipart类的附件(正文也是一个附件)已编码,因此直接修改起来很麻烦,最好是使用先删除后重建的方法,详见add_content()函数。
:param: from_email:发件人地址
:param: to_email:字串,收件人地址,多个收件人时用逗号隔开
:param: subject:邮件主题
:param: content:邮件正文,使用html
:param: smtp_server:选择邮件服务器
Example:
from_email = '******@163.com'
to_email = '******@163.com'
# 要发多个邮箱时,请用","分隔开多个电子邮箱地址
# to_email = '******@163.com, ******@qq.com'
email = Email(from_email=from_email, to_email=to_email, smtp_server='qq')
email.subject = '测试邮件'
email.content = '邮件正文:这是一封测试邮件'
# 设置主题和正文也可使用以下方法:
# email.add_subject('测试邮件')
# email.add_content('邮件正文:这是一封测试邮件')
email.add_attachment('~/副本.txt')
email.add_attachment('~/test.py')
# 使用add_attachments()方法添加多个附件。
# email.add_attachments(['~/副本.txt', '~/test.py'])
email.send()
"""
def __init__(self, from_email, to_email, subject='无主题', content='', smtp_server='default'):
self.server = smtp_server
self.mail = MIMEMultipart()
self.mail['From'] = from_email
self.mail['To'] = to_email
self.mail['Subject'] = subject
if content:
self.add_content(content)
@property
def subject(self):
"""为了能使用直接赋值,使用了@property装饰。但又觉得不必要返回原值,所以返回一个None"""
return None
@subject.setter
def subject(self, value):
self.add_subject(str(value))
@property
def content(self):
"""为了能使用直接赋值,使用了@property装饰。但又觉得不必要返回原值,所以返回一个None"""
return None
@content.setter
def content(self, value):
self.add_content(str(value))
def add_subject(self, subject):
"""
修改MIMEMultipart类的参数,只能使用replace_header(),不能使用直接赋值:self.mail['Subject'] = subject
直接赋值是将新内容累加到原内容之上
"""
self.mail.replace_header('Subject', subject)
def add_content(self, content):
"""
添加一个正文后,因为已经编码,修改起来很麻烦。就好是先删除原正文MIMEText,后重新添加新的MIMEText。
若是直接再次添加一个正文(MIMEText),那么在邮件中会显示多个正文内容的累加。
MIMEMultipart类没有提供删除MIMEText的方法,但所有MIMEText都存放在MIMEMultipart类的_payload列表中。
所以先找到正文的MIMEText对象,再使用列表的remove()方法来删除。
get_payload():列出所有的MIMEText对象。
get_content_type():显示该MIMEText对象的类型。text/html:文本;text/base64:文件
"""
for payload in self.mail.get_payload():
if payload.get_content_type() == 'text/html':
self.mail._payload.remove(payload)
self.mail.attach(MIMEText(content, _subtype='html', _charset='utf-8'))
def add_attachment(self, filename):
try:
with open(filename, 'rb') as f:
attachment = MIMEText(f.read(), 'base64', 'utf-8')
except Exception as e:
print(str(e))
return False, '发送失败:附件["{}"]读取错误【{}】'.format(filename, str(e))
else:
attachment['Content-Type'] = 'application/octet-stream'
file_name = os.path.split(filename)[1]
# 下面一句是处理附件名是中文的情况
file_name = '=?utf-8?b?' + base64.b64encode(file_name.encode()).decode() + '?='
attachment["Content-Disposition"] = 'attachment; filename="%s"' % file_name
self.mail.attach(attachment)
def add_attachments(self, filename_list):
for filename in filename_list:
self.add_attachment(filename)
def send(self):
"""两种方式实现发送"""
# 方法1:
# smtp = Smtp(config=self.server, ssl=True)
# smtp.send(self.mail)
# 方法2:
with Smtp(config=self.server, ssl=True) as smtp:
smtp.send_mail(self.mail)
class Smtp(object):
"""
邮件服务器类
已实现上下文管理器功能,可使用with as。
:param:config:邮件服务器的配置
:param:ssl:是否创造带SSL的邮件服务器
Example1:
smtp = Smtp(config='qq', ssl=True)
smtp.send(mail)
Example2:
with Smtp(config=self.server, ssl=True) as smtp:
smtp.send_mail(self.mail)
"""
def __init__(self, config='default', ssl=False):
self.server = server_config.get(config, 'default')
self.server_host = self.server.host
self.server_user = self.server.user
self.sever_pwd = self.server.pwd
self.ssl = ssl
self.smtp = self.creat()
def __enter__(self):
self.login()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""这里是否需要异常处理?"""
self.quit()
def creat(self):
"""通过ssl来判断是否生成带SSL的邮件服务器"""
if self.ssl:
return smtplib.SMTP_SSL(self.server_host, port=465) # 设置邮件服务器
else:
return smtplib.SMTP(self.server_host, port=25) # 设置邮件服务器
def login(self):
"""登录邮件服务器"""
self.smtp.login(self.server_user, self.sever_pwd) # 登陆邮件服务器
def send_mail(self, mail):
"""发送邮件"""
self.smtp.sendmail(self.server_user, mail['To'].split(','), mail.as_string()) # 发送邮件
def quit(self):
"""退出邮件服务器"""
self.smtp.quit()
def send(self, mail):
"""整合创建/发送/退出为一个函数"""
self.login()
self.send_mail(mail)
self.quit()
如果本文对您有帮助,请给我留个言。谢谢!