利用smtplib和email实现SMTP发送邮件 V2.0

将之前利用smtplib和email实现SMTP发送邮件小程序进行重构。

发现了email.mime.multipart类两个使用上的小问题:

  1. MIMEMultipart类要修改参数,不能直接使用赋值,必需使用replace_header(),不然将会是该参数的内容累加。比如:设置mail[‘Subject’]后,再一次赋值给mail[‘Subject’],结果是两次赋值的内容累加。mail[‘From’]/mail[‘To’]也一样的道理。
  2. 添加一个正文后,因为已经编码,修改起来很麻烦。最好是先删除原正文MIMEText,后重新添加新的MIMEText。若是直接再次添加一个正文(MIMEText),那么在邮件中会显示多个正文内容的累加。MIMEMultipart类没有提供删除MIMEText的方法,但所有MIMEText都存放在MIMEMultipart类的_payload列表中。所以先找到正文的MIMEText对象,再使用列表的remove()方法来删除。
说明:
  1. 考虑到有可能更换SMTP服务器,所以把SMTP服务器的设置信息用类来管理。
  2. 为实现主题(mail[‘subject’])和正文(content)能直接赋值,使用了@property装饰subject和content。
  3. 其中的Smtp类实现了上下文管理,可以使用with as。
  4. QQ的smtp登录现在使用”CardDAV/CalDAV服务“,也就是你需要在QQ邮件的设置/帐户里获取授权码,用这个授权码替代密码。
  5. 未实现异常处理。----主要是我想不到怎么处理 😦
使用方法如下:
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()

如果本文对您有帮助,请给我留个言。谢谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值