將之前利用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()
如果本文對您有幫助,請給我留個言。謝謝!