snjl

我大概率会编程。


  • 首页

  • 标签

  • 分类

  • 归档

  • 搜索

requests包

发表于 2019-01-25 | 分类于 python
字数统计: 1.1k 字 | 阅读时长 ≈ 5 分钟

安装requests

如果安装了Anaconda,requests就已经可用了。否则,需要在命令行下通过pip安装:

$ pip install requests
如果遇到Permission denied安装失败,请加上sudo重试。

使用requests

要通过GET访问一个页面,只需要几行代码:

1
2
3
4
5
6
7
8
9
10
11
12
>>> import requests
>>> r = requests.get('https://www.douban.com/') # 豆瓣首页
>>> r.status_code
200
>>> r.text
r.text
'<!DOCTYPE HTML>\n<html>\n<head>\n<meta name="description" content="提供图书、电影、音乐唱片的推荐、评论和...'
对于带参数的URL,传入一个dict作为params参数:

>>> r = requests.get('https://www.douban.com/search', params={'q': 'python', 'cat': '1001'})
>>> r.url # 实际请求的URL
'https://www.douban.com/search?q=python&cat=1001'

requests自动检测编码,可以使用encoding属性查看:

1
2
>>> r.encoding
'utf-8'

无论响应是文本还是二进制内容,我们都可以用content属性获得bytes对象:

1
2
>>> r.content
b'<!DOCTYPE html>\n<html>\n<head>\n<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n...'

requests的方便之处还在于,对于特定类型的响应,例如JSON,可以直接获取:

1
2
3
>>> r = requests.get('https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20%3D%202151330&format=json')
>>> r.json()
{'query': {'count': 1, 'created': '2017-11-17T07:14:12Z', ...

需要传入HTTP Header时,我们传入一个dict作为headers参数:

1
2
3
>>> r = requests.get('https://www.douban.com/', headers={'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit'})
>>> r.text
'<!DOCTYPE html>\n<html>\n<head>\n<meta charset="UTF-8">\n <title>豆瓣(手机版)</title>...'

要发送POST请求,只需要把get()方法变成post(),然后传入data参数作为POST请求的数据:

1
>>> r = requests.post('https://accounts.douban.com/login', data={'form_email': 'abc@example.com', 'form_password': '123456'})

requests默认使用application/x-www-form-urlencoded对POST数据编码。如果要传递JSON数据,可以直接传入json参数:

1
2
params = {'key': 'value'}
r = requests.post(url, json=params) # 内部自动序列化为JSON

类似的,上传文件需要更复杂的编码格式,但是requests把它简化成files参数:

1
2
>>> upload_files = {'file': open('report.xls', 'rb')}
>>> r = requests.post(url, files=upload_files)

在读取文件时,注意务必使用’rb’即二进制模式读取,这样获取的bytes长度才是文件的长度。

把post()方法替换为put(),delete()等,就可以以PUT或DELETE方式请求资源。

除了能轻松获取响应内容外,requests对获取HTTP响应的其他信息也非常简单。例如,获取响应头:

1
2
3
4
>>> r.headers
{Content-Type': 'text/html; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Content-Encoding': 'gzip', ...}
>>> r.headers['Content-Type']
'text/html; charset=utf-8'

requests对Cookie做了特殊处理,使得我们不必解析Cookie就可以轻松获取指定的Cookie:

1
2
>>> r.cookies['ts']
'example_cookie_12345'

要在请求中传入Cookie,只需准备一个dict传入cookies参数:

1
2
>>> cs = {'token': '12345', 'status': 'working'}
>>> r = requests.get(url, cookies=cs)

最后,要指定超时,传入以秒为单位的timeout参数:

1
>>> r = requests.get(url, timeout=2.5) # 2.5秒后超时

requests包的异常处理

可以使用raise_for_status()来获取所有错误,并且在except语句中使用 requests.RequestException来得到错误原因:

1
2
3
4
5
6
7
8
9
def get_bs_obj(link):
try:
response = requests.get(link, headers=headers, timeout=10)
response.raise_for_status()
bs_obj = bs(response.text)
return bs_obj
except requests.RequestException as e:
print(e)
return None

例如会产生报错:

1
2
3
4
5
6
7
HTTPConnectionPool(host='synthezise.christuniversity.in', port=80): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x0000021B9CB8A0F0>: Failed to establish a new connection: [Errno 11001] getaddrinfo failed',))

HTTPConnectionPool(host='icu2018cls.umk.edu.my', port=80): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x0000021B9CB8ACF8>: Failed to establish a new connection: [Errno 11001] getaddrinfo failed',))

HTTPSConnectionPool(host='icsah.eu', port=443): Max retries exceeded with url: /events (Caused by SSLError(SSLError("bad handshake: SysCallError(-1, 'Unexpected EOF')",),))

HTTPSConnectionPool(host='icsah.eu', port=443): Max retries exceeded with url: /events (Caused by SSLError(SSLError("bad handshake: SysCallError(-1, 'Unexpected EOF')",),))

获取对象信息

发表于 2019-01-25 | 分类于 python
字数统计: 1.7k 字 | 阅读时长 ≈ 7 分钟

当我们拿到一个对象的引用时,如何知道这个对象是什么类型、有哪些方法呢?

使用type()

首先,我们来判断对象类型,使用type()函数:

基本类型都可以用type()判断:

1
2
3
4
5
6
>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>

如果一个变量指向函数或者类,也可以用type()判断:

1
2
3
4
>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>

但是type()函数返回的是什么类型呢?它返回对应的Class类型。如果我们要在if语句中判断,就需要比较两个变量的type类型是否相同:

1
2
3
4
5
6
7
8
9
10
>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False

判断基本数据类型可以直接写int,str等,但如果要判断一个对象是否是函数怎么办?可以使用types模块中定义的常量:

1
2
3
4
5
6
7
8
9
10
11
12
>>> import types
>>> def fn():
... pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True

使用isinstance()

对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。

我们回顾上次的例子,如果继承关系是:

object -> Animal -> Dog -> Husky
那么,isinstance()就可以告诉我们,一个对象是否是某种类型。先创建3种类型的对象:

1
2
3
>>> a = Animal()
>>> d = Dog()
>>> h = Husky()

然后,判断:

1
2
>>> isinstance(h, Husky)
True

没有问题,因为h变量指向的就是Husky对象。

再判断:

1
2
>>> isinstance(h, Dog)
True

h虽然自身是Husky类型,但由于Husky是从Dog继承下来的,所以,h也还是Dog类型。换句话说,isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。

因此,我们可以确信,h还是Animal类型:

1
2
>>> isinstance(h, Animal)
True

同理,实际类型是Dog的d也是Animal类型:

1
2
>>> isinstance(d, Dog) and isinstance(d, Animal)
True

但是,d不是Husky类型:

1
2
>>> isinstance(d, Husky)
False

能用type()判断的基本类型也可以用isinstance()判断:

1
2
3
4
5
6
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True

并且还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:

1
2
3
4
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True

注意:总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”。

使用dir()

如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:

1
2
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

类似xxx的属性和方法在Python中都是有特殊用途的,比如len方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的len()方法,所以,下面的代码是等价的:

1
2
3
4
>>> len('ABC')
3
>>> 'ABC'.__len__()
3

我们自己写的类,如果也想用len(myObj)的话,就自己写一个len()方法:

1
2
3
4
5
6
7
>>> class MyDog(object):
... def __len__(self):
... return 100
...
>>> dog = MyDog()
>>> len(dog)
100

剩下的都是普通属性或方法,比如lower()返回小写的字符串:

1
2
>>> 'ABC'.lower()
'abc'

仅仅把属性和方法列出来是不够的,配合getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态:

1
2
3
4
5
6
7
>>> class MyObject(object):
... def __init__(self):
... self.x = 9
... def power(self):
... return self.x * self.x
...
>>> obj = MyObject()

紧接着,可以测试该对象的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19

如果试图获取不存在的属性,会抛出AttributeError的错误:

1
2
3
4
>>> getattr(obj, 'z') # 获取属性'z'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'

可以传入一个default参数,如果属性不存在,就返回默认值:

1
2
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404

也可以获得对象的方法:

1
2
3
4
5
6
7
8
9
>>> hasattr(obj, 'power') # 有属性'power'吗?
True
>>> getattr(obj, 'power') # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # 调用fn()与调用obj.power()是一样的
81

小结

通过内置的一系列函数,我们可以对任意一个Python对象进行剖析,拿到其内部的数据。要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息。如果可以直接写:

1
sum = obj.x + obj.y

就不要写:

1
sum = getattr(obj, 'x') + getattr(obj, 'y')

一个正确的用法的例子如下:

1
2
3
4
def readImage(fp):
if hasattr(fp, 'read'):
return readData(fp)
return None

假设我们希望从文件流fp中读取图像,我们首先要判断该fp对象是否存在read方法,如果存在,则该对象是一个流,如果不存在,则无法读取。hasattr()就派上了用场。

请注意,在Python这类动态语言中,根据鸭子类型,有read()方法,不代表该fp对象就是一个文件流,它也可能是网络流,也可能是内存中的一个字节流,但只要read()方法返回的是有效的图像数据,就不影响读取图像的功能。

生成器

发表于 2019-01-25 | 分类于 python
字数统计: 1.7k 字 | 阅读时长 ≈ 7 分钟

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

1
2
3
4
5
6
>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>

创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。

我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?

如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

我们讲过,generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> g = (x * x for x in range(10))
>>> for n in g:
... print(n)
...
0
1
4
9
16
25
36
49
64
81

所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。

generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1
1, 1, 2, 3, 5, 8, 13, 21, 34, ...

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

1
2
3
4
5
6
7
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return 'done'

注意,赋值语句:

1
a, b = b, a + b

相当于:

1
2
3
t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]

但不必显式写出临时变量t就可以赋值。

上面的函数可以输出斐波那契数列的前N个数:

1
2
3
4
5
6
7
8
>>> fib(6)
1
1
2
3
5
8
'done'

仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

1
2
3
4
5
6
7
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

1
2
3
>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>

这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

举个简单的例子,定义一个generator,依次返回数字1,3,5:

1
2
3
4
5
6
7
def odd():
print('step 1')
yield 1
print('step 2')
yield(3)
print('step 3')
yield(5)

调用该generator时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> o = odd()
>>> next(o)
step 1
1
>>> next(o)
step 2
3
>>> next(o)
step 3
5
>>> next(o)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

可以看到,odd不是普通函数,而是generator,在执行过程中,遇到yield就中断,下次又继续执行。执行3次yield后,已经没有yield可以执行了,所以,第4次调用next(o)就报错。

回到fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。

同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:

1
2
3
4
5
6
7
8
9
>>> for n in fib(6):
... print(n)
...
1
1
2
3
5
8

但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> g = fib(6)
>>> while True:
... try:
... x = next(g)
... print('g:', x)
... except StopIteration as e:
... print('Generator return value:', e.value)
... break
...
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done

关于如何捕获错误,后面的错误处理还会详细讲解。

小结

generator是非常强大的工具,在Python中,可以简单地把列表生成式改成generator,也可以通过函数实现复杂逻辑的generator。

要理解generator的工作原理,它是在for循环的过程中不断计算出下一个元素,并在适当的条件结束for循环。对于函数改成的generator来说,遇到return语句或者执行到函数体最后一行语句,就是结束generator的指令,for循环随之结束。

请注意区分普通函数和generator函数,普通函数调用直接返回结果:

1
2
3
>>> r = abs(6)
>>> r
6

generator函数的“调用”实际返回一个generator对象:

1
2
3
>>> g = fib(6)
>>> g
<generator object fib at 0x1022ef948>

python:发送邮件-封装版

发表于 2019-01-23 | 分类于 python
字数统计: 856 字 | 阅读时长 ≈ 4 分钟

项目地址:https://github.com/snjl/python.email2.git

功能:封装了信息,发送者,接收者,每次默认从发送者中随机抽一个发送信息给所有接收者,分为三种发送方式,1是简单信息发送send_easy_email,2是带一个附件发送send_file,3是带多个附件发送send_files。

每次使用需要初始化Email的用户信息和接收者,然后可以通过修改类的信息来修改邮件内容。

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
from email.header import Header
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

from email.utils import parseaddr, formataddr
import random
import smtplib


class Email(object):
# 获取多个发送者信息,每个发送者以tuple(email_address,password,smtp_server,port)存储,
# 每次发送邮件会随机从里面选择一个邮箱发送
__senders = list()
# 传入多个接收address
__send_to_addresses = list()
# 传入信息
__message = "默认信息"
# 封面显示标题
__message_title = "默认标题"
# 显示发件人
__message_sender_name = "发送者"

# 获取senders的用户信息
def get_senders(self):
return self.__senders

def get_send_to_addresses(self):
return self.__send_to_addresses

def set_senders(self, senders):
self.__senders = senders

def set_send_to_addresses(self, send_to_addresses):
self.__send_to_addresses = send_to_addresses

def get_range_sender_info(self):
return random.choice(self.__senders)

# 发送简单信息,不带附件
def send_easy_email(self):
# 格式化内容
def _format_addr(s):
name, addr = parseaddr(s)
return formataddr((Header(name, 'utf-8').encode(), addr))

sender = self.get_range_sender_info()

sender_address = sender[0]
sender_password = sender[1]
smtp_server = sender[2]
smtp_port = sender[3]
message = MIMEText(self.__message, 'plain', 'utf-8')
# 显示发件人名和发件地址
message['From'] = _format_addr(self.__message_sender_name + '<%s>' % sender_address)
# 标题
message['Subject'] = Header(self.__message_title, 'utf-8').encode()

server = smtplib.SMTP_SSL(smtp_server, smtp_port)
server.set_debuglevel(1)
# 登录
server.login(sender_address, sender_password)
# 邮件发送
server.sendmail(sender_address, self.__send_to_addresses, message.as_string())
server.quit()

# 发送一个附件,传入文件名,文件必须在该目录
def send_file(self, file_name):
def _format_addr(s):
name, addr = parseaddr(s)
return formataddr((Header(name, 'utf-8').encode(), addr))

sender = self.get_range_sender_info()

sender_address = sender[0]
sender_password = sender[1]
smtp_server = sender[2]
smtp_port = sender[3]

message = MIMEMultipart()

message.attach(MIMEText(self.__message, 'plain', 'utf-8'))
# 显示发件人名和发件地址
message['From'] = _format_addr(self.__message_sender_name + '<%s>' % sender_address)
# 标题
message['Subject'] = Header(self.__message_title, 'utf-8').encode()
# 传入附件名
attach = MIMEText(open(file_name, 'rb').read(), 'base64', 'utf-8')
attach["Content-Type"] = 'application/octet-stream'
attach["Content-Disposition"] = 'attachment; filename="' + file_name + '"'
message.attach(attach)

server = smtplib.SMTP_SSL(smtp_server, smtp_port)
server.set_debuglevel(1)
# 登录
server.login(sender_address, sender_password)
# 邮件发送
server.sendmail(sender_address, self.__send_to_addresses, message.as_string())
server.quit()

# 发送多个附件,传入文件的名字列表,文件必须在该目录
def send_files(self, file_names):
def _format_addr(s):
name, addr = parseaddr(s)
return formataddr((Header(name, 'utf-8').encode(), addr))

sender = self.get_range_sender_info()

sender_address = sender[0]
sender_password = sender[1]
smtp_server = sender[2]
smtp_port = sender[3]

message = MIMEMultipart()

message.attach(MIMEText(self.__message, 'plain', 'utf-8'))
# 显示发件人名和发件地址
message['From'] = _format_addr(self.__message_sender_name + '<%s>' % sender_address)
# 标题
message['Subject'] = Header(self.__message_title, 'utf-8').encode()

for file_name in file_names:
attach = MIMEText(open(file_name, 'rb').read(), 'base64', 'utf-8')
attach["Content-Type"] = 'application/octet-stream'
attach["Content-Disposition"] = 'attachment; filename="' + file_name + '"'
message.attach(attach)

server = smtplib.SMTP_SSL(smtp_server, smtp_port)
server.set_debuglevel(1)
# 登录
server.login(sender_address, sender_password)
# 邮件发送
server.sendmail(sender_address, self.__send_to_addresses, message.as_string())
server.quit()


if __name__ == '__main__':
email_tool = Email()
# 可以从数据库中获取
senders = [('', '', '', 465), ]
# 从数据库中获取需要发送的列表,但是获取后就保存在内存中,如果新添加,通过某个接口刷新获取或者从重启服务
email_tool.set_send_to_addresses(['', ''])

email_tool.set_senders(senders)
email_tool.send_files(['test.txt', 'test2.txt'])

python:发送邮件

发表于 2019-01-23 | 分类于 python
字数统计: 2.3k 字 | 阅读时长 ≈ 10 分钟

项目地址:https://github.com/snjl/python.email1.git

Python3 SMTP发送邮件

SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。

python的smtplib提供了一种很方便的途径发送电子邮件。它对smtp协议进行了简单的封装。

阅读全文 »

Ubuntu:tomcat安装和部署

发表于 2019-01-23 | 分类于 服务器
字数统计: 243 字 | 阅读时长 ≈ 1 分钟

两种方式,一种是apt直接安装,一种是tar解压安装。

使用apt的方法简单,但是服务部署位置和日志位置,甚至配置的位置都是不一样的,分别在/var/lib/tomcat8,/var/log/tomcat8,/etc/tomcat8,所以如果有需要自动创建log文件夹的,不适配这种方式,更适用于解压版。

apt安装tomcat8

1
apt install tomcat8

会自动安装和配置。

tomcat服务启动关闭重启

1
2
3
service tomcat8 start
service tomcat8 stop
service tomcat8 restart

tomcat服务部署位置

1
/var/lib/tomcat8/webapps

tomcat日志位置

1
/var/log/tomcat8

一般是catalina.out。

tomcat配置位置

1
/etc/tomcat8/

tar解压安装

例如安装tomcat8,先下载

1
wget http://mirrors.hust.edu.cn/apache/tomcat/tomcat-8/v8.5.37/bin/apache-tomcat-8.5.37.tar.gz

解压

1
tar -zxvf apache-tomcat-8.5.37.tar.gz

进入bin目录,启动和停止

1
2
./startup.sh
./shutdown.sh

项目文件放在webapps文件夹里即可,启动后访问localhost:8080显示tomcat的欢迎界面即完成。

docker:run参数

发表于 2019-01-23 | 分类于 docker
字数统计: 962 字 | 阅读时长 ≈ 3 分钟

在run的时候,给docker分配cpu0、1,分配4G内存,并且共享/home文件夹生成docker的容器,使用-it可以直接进入命令行。

1
docker run --cpuset-cpus 0-1 -it -m 4096M -v /home:/home ubuntu:18.04

参数:

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
-d, --detach=false         指定容器运行于前台还是后台,默认为false     
-i, --interactive=false 打开STDIN,用于控制台交互
-t, --tty=false 分配tty设备,该可以支持终端登录,默认为false
-u, --user="" 指定容器的用户
-a, --attach=[] 登录容器(必须是以docker run -d启动的容器)
-w, --workdir="" 指定容器的工作目录
-c, --cpu-shares=0 设置容器CPU权重,在CPU共享场景使用
-e, --env=[] 指定环境变量,容器中可以使用该环境变量
-m, --memory="" 指定容器的内存上限
-P, --publish-all=false 指定容器暴露的端口
-p, --publish=[] 指定容器暴露的端口
-h, --hostname="" 指定容器的主机名
-v, --volume=[] 给容器挂载存储卷,挂载到容器的某个目录
--volumes-from=[] 给容器挂载其他容器上的卷,挂载到容器的某个目录
--cap-add=[] 添加权限,权限清单详见:http://linux.die.net/man/7/capabilities
--cap-drop=[] 删除权限,权限清单详见:http://linux.die.net/man/7/capabilities
--cidfile="" 运行容器后,在指定文件中写入容器PID值,一种典型的监控系统用法
--cpuset="" 设置容器可以使用哪些CPU,此参数可以用来容器独占CPU
--device=[] 添加主机设备给容器,相当于设备直通
--dns=[] 指定容器的dns服务器
--dns-search=[] 指定容器的dns搜索域名,写入到容器的/etc/resolv.conf文件
--entrypoint="" 覆盖image的入口点
--env-file=[] 指定环境变量文件,文件格式为每行一个环境变量
--expose=[] 指定容器暴露的端口,即修改镜像的暴露端口
--link=[] 指定容器间的关联,使用其他容器的IP、env等信息
--lxc-conf=[] 指定容器的配置文件,只有在指定--exec-driver=lxc时使用
--name="" 指定容器名字,后续可以通过名字进行容器管理,links特性需要使用名字
--net="bridge" 容器网络设置:
bridge 使用docker daemon指定的网桥
host //容器使用主机的网络
container:NAME_or_ID >//使用其他容器的网路,共享IP和PORT等网络资源
none 容器使用自己的网络(类似--net=bridge),但是不进行配置
--privileged=false 指定容器是否为特权容器,特权容器拥有所有的capabilities
--restart="no" 指定容器停止后的重启策略:
no:容器退出时不重启
on-failure:容器故障退出(返回值非零)时重启
always:容器退出时总是重启
--rm=false 指定容器停止后自动删除容器(不支持以docker run -d启动的容器)
--sig-proxy=true 设置由代理接受并处理信号,但是SIGCHLD、SIGSTOP和SIGKILL不能被代理

使用docker镜像nginx:latest以后台模式启动一个容器,并将容器命名为mynginx。

1
docker run --name mynginx -d nginx:latest

使用镜像nginx:latest以后台模式启动一个容器,并将容器的80端口映射到主机随机端口。

1
docker run -P -d nginx:latest

使用镜像 nginx:latest,以后台模式启动一个容器,将容器的 80 端口映射到主机的 80 端口,主机的目录 /data 映射到容器的 /data。

1
docker run -p 80:80 -v /data:/data -d nginx:latest

绑定容器的 8080 端口,并将其映射到本地主机 127.0.0.1 的 80 端口上。

1
docker run -p 127.0.0.1:80:8080/tcp ubuntu bash

使用镜像nginx:latest以交互模式启动一个容器,在容器内执行/bin/bash命令。

1
2
runoob@runoob:~$ docker run -it nginx:latest /bin/bash
root@b8573233d675:/#

python:继承和多态

发表于 2019-01-23 | 分类于 python
字数统计: 1.6k 字 | 阅读时长 ≈ 6 分钟

在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。

比如,我们已经编写了一个名为Animal的class,有一个run()方法可以直接打印:

1
2
3
class Animal(object):
def run(self):
print('Animal is running...')

当我们需要编写Dog和Cat类时,就可以直接从Animal类继承:

1
2
3
4
5
class Dog(Animal):
pass

class Cat(Animal):
pass

对于Dog来说,Animal就是它的父类,对于Animal来说,Dog就是它的子类。Cat和Dog类似。

继承有什么好处?最大的好处是子类获得了父类的全部功能。由于Animial实现了run()方法,因此,Dog和Cat作为它的子类,什么事也没干,就自动拥有了run()方法:

1
2
3
4
5
dog = Dog()
dog.run()

cat = Cat()
cat.run()

运行结果如下:

1
2
Animal is running...
Animal is running...

当然,也可以对子类增加一些方法,比如Dog类:

1
2
3
4
5
6
7
class Dog(Animal):

def run(self):
print('Dog is running...')

def eat(self):
print('Eating meat...')

继承的第二个好处需要我们对代码做一点改进。你看到了,无论是Dog还是Cat,它们run()的时候,显示的都是Animal is running…,符合逻辑的做法是分别显示Dog is running…和Cat is running…,因此,对Dog和Cat类改进如下:

1
2
3
4
5
6
7
8
9
class Dog(Animal):

def run(self):
print('Dog is running...')

class Cat(Animal):

def run(self):
print('Cat is running...')

再次运行,结果如下:

1
2
Dog is running...
Cat is running...

当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。

要理解什么是多态,我们首先要对数据类型再作一点说明。当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样:

1
2
3
a = list() # a是list类型
b = Animal() # b是Animal类型
c = Dog() # c是Dog类型

判断一个变量是否是某个类型可以用isinstance()判断:

1
2
3
4
5
6
>>> isinstance(a, list)
True
>>> isinstance(b, Animal)
True
>>> isinstance(c, Dog)
True

看来a、b、c确实对应着list、Animal、Dog这3种类型。

但是等等,试试:

1
2
>>> isinstance(c, Animal)
True

看来c不仅仅是Dog,c还是Animal!

不过仔细想想,这是有道理的,因为Dog是从Animal继承下来的,当我们创建了一个Dog的实例c时,我们认为c的数据类型是Dog没错,但c同时也是Animal也没错,Dog本来就是Animal的一种!

所以,在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行:

1
2
3
>>> b = Animal()
>>> isinstance(b, Dog)
False

Dog可以看成Animal,但Animal不可以看成Dog。

要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:

1
2
3
def run_twice(animal):
animal.run()
animal.run()

当我们传入Animal的实例时,run_twice()就打印出:

1
2
3
>>> run_twice(Animal())
Animal is running...
Animal is running...

当我们传入Dog的实例时,run_twice()就打印出:

1
2
3
>>> run_twice(Dog())
Dog is running...
Dog is running...

当我们传入Cat的实例时,run_twice()就打印出:

1
2
3
>>> run_twice(Cat())
Cat is running...
Cat is running...

看上去没啥意思,但是仔细想想,现在,如果我们再定义一个Tortoise类型,也从Animal派生:

1
2
3
class Tortoise(Animal):
def run(self):
print('Tortoise is running slowly...')

当我们调用run_twice()时,传入Tortoise的实例:

1
2
3
>>> run_twice(Tortoise())
Tortoise is running slowly...
Tortoise is running slowly...

你会发现,新增一个Animal的子类,不必对run_twice()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。

多态的好处就是,当我们需要传入Dog、Cat、Tortoise……时,我们只需要接收Animal类型就可以了,因为Dog、Cat、Tortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态的意思:

对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat还是Tortoise对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:

对扩展开放:允许新增Animal子类;

对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。

静态语言 vs 动态语言

对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。

对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了:

1
2
3
class Timer(object):
def run(self):
print('Start...')

这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()方法,返回其内容。但是,许多对象,只要有read()方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()方法的对象。

小结

继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。

动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。

python:对xlsx文件和csv文件的读写

发表于 2019-01-23 | 分类于 python
字数统计: 733 字 | 阅读时长 ≈ 3 分钟

项目地址:https://github.com/snjl/python.csvandxlsx.git

xlsx

写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from openpyxl import Workbook
from openpyxl.utils import get_column_letter

# 在内存中创建一个workbook对象,而且会至少创建一个 worksheet
wb = Workbook()

#获取当前活跃的worksheet,默认就是第一个worksheet
ws = wb.active

#设置单元格的值,A1等于6(测试可知openpyxl的行和列编号从1开始计算),B1等于7
ws.cell(row=1, column=1).value = 6

#从第2行开始,写入9行10列数据,值为对应的列序号A、B、C、D...
for row in range(2,11):
for col in range (1,11):
ws.cell(row=row, column=col).value = get_column_letter(col)

#可以使用append插入一行数据
ws.append(["我","你","她"])

#保存
wb.save(filename="a.xlsx")

读取

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
from openpyxl import load_workbook

#打开一个workbook
wb = load_workbook(filename="a.xlsx")

#获取当前活跃的worksheet,默认就是第一个worksheet
#ws = wb.active

#当然也可以使用下面的方法

#获取所有表格(worksheet)的名字
sheets = wb.get_sheet_names()
#第一个表格的名称
sheet_first = sheets[0]
#获取特定的worksheet
ws = wb.get_sheet_by_name(sheet_first)

#获取表格所有行和列,两者都是可迭代的
rows = ws.rows
columns = ws.columns

#迭代所有的行
for row in rows:
line = [col.value for col in row]
print(line)

#通过坐标读取值
#print(ws.cell('A1').value) # A表示列,1表示行print ws.cell(row=1, column=1).value
print(ws['A1'].value)

csv写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def save_csv(file_name, headers, rows):
"""
将表头和数据写入新生成的文件中
:param file_name: 生成文件名,例如xxx.csv
:param headers:文件第一行的表头元素,list存储
:param rows:文件数据,每一行为一个list
"""
# 保存格式必须以.csv,不然加上.csv
if file_name.endwith(".csv") is not True:
file_name += '.csv'
with open(file_name, 'a', encoding='utf8', errors='ignore', newline='') as f:
f_csv = csv.writer(f)
f_csv.writerow(headers)
f_csv.writerows(rows)

csv、xlsx写入内容封装

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
from openpyxl import Workbook
import csv


def save_excel(file_name, headers, rows):
"""
将表头和数据写入新生成的excel文件中,生成*.xlsx文件
:param file_name:文件名
:param headers:文件第一行的表头元素,list存储
:param rows:文件数据,每一行为一个list
"""
# 在内存中创建一个workbook对象,而且会至少创建一个 worksheet
wb = Workbook()
# 获取当前活跃的worksheet,默认就是第一个worksheet
ws = wb.active
ws.append(headers)
for row in rows:
ws.append(row)
# 保存格式必须以.xlsx结尾,不然加上.xlsx
if file_name.endswith(".xlsx") is not True:
file_name += '.xlsx'
wb.save(filename=file_name)
wb.close()


def save_csv(file_name, headers, rows):
"""
将表头和数据写入新生成的文件中
:param file_name: 生成文件名,例如xxx.csv
:param headers:文件第一行的表头元素,list存储
:param rows:文件数据,每一行为一个list
"""
# 保存格式必须以.csv,不然加上.csv
if file_name.endwith(".csv") is not True:
file_name += '.csv'
with open(file_name, 'a', encoding='utf8', errors='ignore', newline='') as f:
f_csv = csv.writer(f)
f_csv.writerow(headers)
f_csv.writerows(rows)

frp内网穿透配置

发表于 2019-01-23 | 分类于 服务器
字数统计: 427 字 | 阅读时长 ≈ 1 分钟

参考:https://github.com/fatedier/frp/blob/master/README_zh.md

frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp, udp 协议,为 http 和 https 应用协议提供了额外的能力,且尝试性支持了点对点穿透。

下载:

1
wget https://github.com/fatedier/frp/releases/download/v0.23.1/frp_0.23.1_linux_amd64.tar.gz

解压:

1
tar -xzvf frp_0.23.1_linux_amd64.tar.gz

根据对应的操作系统及架构,从 Release 页面下载最新版本的程序。

将 frps 及 frps.ini 放到具有公网 IP 的机器上。

将 frpc 及 frpc.ini 放到处于内网环境的机器上。

不需要的可以用rm -rf frpc或rm -rf frps删除。

通过 ssh 访问公司内网机器

修改 frps.ini 文件,这里使用了最简化的配置:

1
2
3
# frps.ini
[common]
bind_port = 7000

ps:如果需要部署多台内网服务器,则可能需要复制一个frps.ini为frps1.ini<bind_port设置为7001(有时候不需要,玄学)

1
cp frps.ini frps1.ini

启动frps:

1
./frps -c ./frps.ini

内网端配置,修改 frpc.ini 文件,假设 frps 所在服务器的公网 IP 为 x.x.x.x;

1
2
3
4
5
6
7
8
9
10
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000

[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000

启动 frpc:

1
./frpc -c ./frpc.ini

通过 ssh 访问内网机器,假设用户名为 test:

1
ssh -oPort=6000 test@x.x.x.x

有多台服务器,例如第二台,可以将frpc.ini写成:

1
2
3
4
5
6
7
8
9
10
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000

[ssh2]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6001

注意server_port可能要改7001,remote_port要改成不冲突原6000的端口,项目名要改为ssh2或者其他。

杀frp进程

使用htop可以看到frp的进程,可以使用

1
kill -9 frp

杀掉所有frp的进程。

1…8910…21
snjl

snjl

越过山丘,才发现无人等候。

203 日志
44 分类
107 标签
RSS
GitHub E-Mail Weibo
© 2019 snjl
总访问量次 | 总访客人 |
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4