本文介绍一些Python使用的小技巧。

实现进度条

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import sys, time
import os
import datetime
def progressbar(t_n):
sys.stdout.write('\r')
sys.stdout.write(t_n)
sys.stdout.flush()
starttime = datetime.datetime.now()
for progress in range(100):
time.sleep(0.5)
endtime = datetime.datetime.now()
progressbar("Download progress: %d%%,running time: %s \r" % (progress,(endtime - starttime)))
# 第二种 通过清屏方式实现
starttime = datetime.datetime.now()
for progress in range(100):
time.sleep(0.5)
endtime = datetime.datetime.now()
os.system('cls') # windows
# os.system('clear') # Linux
print("Download progress: %d%%,running time: %s" % (progress,(endtime - starttime)))
# 通过tqdm模块
import sys
import time
from tqdm import tqdm
for i in tqdm(range(1, 500)):
time.sleep(0.01)

列表去重

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import itertools
from functools import reduce,wraps
import time
import random
def fn_timer(function):
@wraps(function)
def function_timer(*args,**kwargs):
s=time.time()
result=function(*args,**kwargs)
e=time.time()
print ("Total time running %s: %s" % (function.__name__, e - s))
return result
return function_timer
@fn_timer
def m1(data):
result=[]
for i in data:
if i not in result:
result.append(i)
return result
@fn_timer
def m2(data):
return list(set(data))
@fn_timer
def m3(data):
return list({}.fromkeys(data).keys())
@fn_timer
def m4(data):
data.sort()
it = itertools.groupby(data)
result=[k for k,g in it]
return result
@fn_timer
def m5(data):
func = lambda x, y: x if y in x else x + [y]
return reduce(func, [[], ] + data)
BIG = 200000
data=[]
for i in range(BIG):
random_number = random.randint(1,BIG/2)
data.append(random_number)
m1(data) # Total time running m1: 158.83623671531677
m2(data) # Total time running m2: 0.022988080978393555
m3(data) # Total time running m3: 0.031052827835083008
m4(data) # Total time running m4: 0.12702035903930664
m5(data) # Total time running m5: 468.24223589897156

发邮件

SMTP发送邮件

  SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。
Python对SMTP支持有smtplib和email两个模块,email负责构造邮件,smtplib负责发送邮件。

1
2
import smtplib
smtpObj = smtplib.SMTP( [host [, port [, local_hostname]]] )

参数说明:

  • host: SMTP 服务器主机。 你可以指定主机的ip地址或者域名如: baidu.com,这个是可选参数
  • port: 如果你提供了 host 参数, 你需要指定 SMTP 服务使用的端口号,一般情况下 SMTP 端口号为25
  • local_hostname: 如果 SMTP 在你的本机上,你只需要指定服务器地址为 localhost 即可

Python SMTP 对象使用 sendmail 方法发送邮件:

1
SMTP.sendmail(from_addr, to_addrs, msg[, mail_options, rcpt_options])

参数说明:

  • from_addr: 邮件发送者地址
  • pto_addrs: 字符串列表,邮件发送地址
  • lmsg: 发送消息,msg 是字符串,表示邮件。

使用QQ发送时,密码使用的是QQ邮箱的授权码,而不是邮箱登录密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import smtplib
from email.mime.text import MIMEText
from email.header import Header
from email.utils import parseaddr, formataddr
def _format_addr(s):
name, addr = parseaddr(s)
return formataddr((Header(name, 'utf-8').encode(),addr if isinstance(addr, str) else addr.decode('utf-8')))

receivers = [''] # 接收邮件
'''
邮箱 POP3服务器(端口995) SMTP服务器(端口587)
qq.com pop.qq.com smtp.qq.com
'''
smtp_server='smtp.qq.com'
smtp_port='587'
from_addr=''
password='' # QQ邮箱授权码,非邮箱登录密码
'''
构造MIMEText对象时,第一个参数就是邮件正文
第二个参数是MIME的subtype,传入'plain',最终的MIME就是'text/plain'
第三个参数是utf-8编码
发件人、收件人等信息并不是通过SMTP协议发给MTA,而是包含在发给MTA的文本中的
'''
message = MIMEText('Python 问候...', 'plain', 'utf-8')
# message['From'] = Header("QQ mail", 'utf-8') # 发送者
# message['To'] = Header("163 mail", 'utf-8') # 接收者
subject = 'Python SMTP 邮件测试'
# message['Subject'] = Header(subject, 'utf-8')
message['From'] = _format_addr('QQ <%s>' % from_addr)
message['To'] = _format_addr('163 <%s>' % receivers[0])
message['Subject'] = Header(subject, 'utf-8').encode()
try:
# smtpObj = smtplib.SMTP('localhost')
smtpObj = smtplib.SMTP(smtp_server,'25')
# smtpObj = smtplib.SMTP_SSL()
# smtpObj.connect(smtp_server, 465) # 启用SSL发信, 端口一般是465
smtpObj.set_debuglevel(1) # 打印出和SMTP服务器交互的所有信息
smtpObj.login(from_addr,password)
'''
SMTP协议就是简单的文本命令和响应
由于可以一次发给多个人,所以传入一个list,邮件正文是一个str,as_string()把MIMEText对象变成str
'''
smtpObj.sendmail(from_addr, receivers, message.as_string())
smtpObj.quit()
print("邮件发送成功")
except smtplib.SMTPException as e:
print("Error: 无法发送邮件",e)

发送HTML邮件

  在构造MIMEText对象时,把HTML字符串传进去,再把第二个参数由plain变为html就可以了:

1
2
3
message = MIMEText('<html><body><h1>Hello</h1>' +
'<p>send by <a href="http://www.python.org">Python</a>...</p>' +
'</body></html>', 'html', 'utf-8')

发送附件

  带附件的邮件可以看做包含若干部分的邮件:文本和各个附件本身,所以,可以构造一个MIMEMultipart对象代表邮件本身,然后往里面加上一个MIMEText作为邮件正文,再继续往里面加上表示附件的MIMEBase对象即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 邮件对象:
msg = MIMEMultipart()
msg['From'] = _format_addr(u'Python爱好者 <%s>' % from_addr)
msg['To'] = _format_addr(u'管理员 <%s>' % to_addr)
msg['Subject'] = Header(u'来自SMTP的问候……', 'utf-8').encode()
# 邮件正文是MIMEText:
msg.attach(MIMEText('send with file...', 'plain', 'utf-8'))
# 添加附件就是加上一个MIMEBase,从本地读取一个图片:
with open('test.png', 'rb') as f:
# 设置附件的MIME和文件名,这里是png类型:
mime = MIMEBase('image', 'png', filename='test.png')
# 加上必要的头信息:
mime.add_header('Content-Disposition', 'attachment', filename='test.png')
mime.add_header('Content-ID', '<0>')
mime.add_header('X-Attachment-Id', '0')
# 把附件的内容读进来:
mime.set_payload(f.read())
# 用Base64编码:
encoders.encode_base64(mime)
# 添加到MIMEMultipart:
msg.attach(mime)

发送图片

  由于大部分邮件服务商都会自动屏蔽带有外链的图片,因为不知道这些链接是否指向恶意网站。所以要把图片嵌入到邮件正文中,我们只需按照发送附件的方式,先把邮件作为附件添加进去,然后,在HTML中通过引用src=”cid:0”就可以把附件作为图片嵌入了。如果有多个图片,给它们依次编号,然后引用不同的cid:x即可。

1
2
3
msg.attach(MIMEText('<html><body><h1>Hello</h1>' +
'<p><img src="cid:0"></p>' +
'</body></html>', 'html', 'utf-8'))

同时支持HTML和Plain格式

  利用MIMEMultipart就可以组合一个HTML和Plain,要注意指定subtype是alternative:

1
2
3
4
5
6
7
msg = MIMEMultipart('alternative')
msg['From'] = ...
msg['To'] = ...
msg['Subject'] = ...

msg.attach(MIMEText('hello', 'plain', 'utf-8'))
msg.attach(MIMEText('<html><body><h1>Hello</h1></body></html>', 'html', 'utf-8'))

加密SMTP

  使用标准的25端口连接SMTP服务器时,使用的是明文传输,发送邮件的整个过程可能会被窃听。要更安全地发送邮件,可以加密SMTP会话,实际上就是先创建SSL安全连接,然后再使用SMTP协议发送邮件。
下面是Gmail的加密传输:

1
2
3
4
smtp_server = 'smtp.gmail.com'
smtp_port = 587
server = smtplib.SMTP(smtp_server, smtp_port)
server.starttls()

  构造一个邮件对象就是一个Messag对象,如果构造一个MIMEText对象,就表示一个文本邮件对象,如果构造一个MIMEImage对象,就表示一个作为附件的图片,要把多个对象组合起来,就用MIMEMultipart对象,而MIMEBase可以表示任何对象。它们的继承关系如下:

1
2
3
4
5
6
7
Message
+- MIMEBase
+- MIMEMultipart
+- MIMENonMultipart
+- MIMEMessage
+- MIMEText
+- MIMEImage