Python拓展

Python3 正则表达式

2017413

11:20

 

正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配。

Python 1.5版本起增加了re 模块,它提供 Perl 风格的正则表达式模式。

re 模块使 Python 语言拥有全部的正则表达式功能。

compile 函数根据一个模式字符串和可选的标志参数生成一个正则表达式对象。该对象拥有一系列方法用于正则表达式匹配和替换。

re 模块也提供了与这些方法功能完全一致的函数,这些函数使用一个模式字符串做为它们的第一个参数。

本章节主要介绍Python中常用的正则表达式处理函数。

re.match函数

re.match 尝试从字符串的起始位置匹配一个模式,如果不是起始位置匹配成功的话,match()就返回none

函数语法

re.match(pattern, string,flags=0)

函数参数说明:

参数

描述

pattern

匹配的正则表达式

string

要匹配的字符串。

flags

标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。

匹配成功re.match方法返回一个匹配的对象,否则返回None

我们可以使用group(num) groups() 匹配对象函数来获取匹配表达式。

匹配对象方法

描述

group(num=0)

匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。

groups()

返回一个包含所有小组字符串的元组,从 1 所含的小组号。

实例 1

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import re
print(re.match('www', '
www.runoob.com').span())  # 在起始位置匹配
print(re.match('com', 'www.runoob.com'))         # 不在起始位置匹配

以上实例运行输出结果为:

(0, 3)
None

实例 2

#!/usr/bin/python3
import re

line = "Cats are smarterthan dogs"

matchObj = re.match( r'(.*) are(.*?) .*', line, re.M|re.I)

if matchObj:
  
print ("matchObj.group() :", matchObj.group())
   print ("matchObj.group(1) :", matchObj.group(1))
   print ("matchObj.group(2) :", matchObj.group(2))
else:
   print ("No match!!")

以上实例执行结果如下:

matchObj.group() :  Cats are smarter than dogs
matchObj.group(1) :  Cats
matchObj.group(2) :  smarter

re.search方法

re.search 扫描整个字符串并返回第一个成功的匹配。

函数语法:

re.search(pattern, string,flags=0)

函数参数说明:

参数

描述

pattern

匹配的正则表达式

string

要匹配的字符串。

flags

标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等。

匹配成功re.search方法返回一个匹配的对象,否则返回None

我们可以使用group(num) groups() 匹配对象函数来获取匹配表达式。

匹配对象方法

描述

group(num=0)

匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。

groups()

返回一个包含所有小组字符串的元组,从 1 所含的小组号。

实例 1

#!/usr/bin/python3

import re

print(re.search('www', 'www.runoob.com').span())  # 在起始位置匹配
print(re.search('com', 'www.runoob.com').span())         # 不在起始位置匹配

以上实例运行输出结果为:

(0, 3)
(11, 14)

实例 2

#!/usr/bin/python3

import re

line = "Cats are smarterthan dogs";

searchObj = re.search( r'(.*)are (.*?) .*', line, re.M|re.I)

if searchObj:
  
print ("searchObj.group() :", searchObj.group())
   print ("searchObj.group(1) :", searchObj.group(1))
   print ("searchObj.group(2) :", searchObj.group(2))
else:
   print ("Nothing found!!")

以上实例执行结果如下:

searchObj.group() :  Cats are smarter than dogs
searchObj.group(1) :  Cats
searchObj.group(2) :  smarter

re.matchre.search的区别

re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search匹配整个字符串,直到找到一个匹配。

实例:

#!/usr/bin/python3

import re

line = "Cats are smarterthan dogs";

matchObj = re.match( r'dogs',line, re.M|re.I)
if matchObj:
  
print ("match -->matchObj.group() : ", matchObj.group())
else:
   print ("No match!!")

matchObj = re.search( r'dogs',line, re.M|re.I)
if matchObj:
  
print ("search -->matchObj.group() : ", matchObj.group())
else:
   print ("No match!!")

以上实例运行结果如下:

No match!!
search --> matchObj.group() : 
dogs

检索和替换

Python re模块提供了re.sub用于替换字符串中的匹配项。

语法:

re.sub(pattern, repl, string,count=0)

参数:

·       pattern : 正则中的模式字符串。

·       repl : 替换的字符串,也可为一个函数。

·       string : 要被查找替换的原始字符串。

·       count : 模式匹配后替换的最大次数,默认 0 表示替换所有的匹配。

实例:

#!/usr/bin/python3
import re

phone = "2004-959-559 # 这是一个电话号码"

# 删除注释
num = re.sub(r'#.*$', "", phone)
print ("
电话号码 : ", num)

# 移除非数字的内容
num = re.sub(r'\D', "", phone)
print ("
电话号码 : ", num)

以上实例执行结果如下:

电话号码2004-959-559
电话号码2004959559

repl 参数是一个函数

以下实例中将字符串中的匹配的数字乘于 2

#!/usr/bin/python

import re

# 将匹配的数字乘于2
def double(matched):
   
value = int(matched.group('value'))
    return str(value * 2)

s = 'A23G4HFD567'
print(re.sub('(?P<value>\d+)', double, s))

执行输出结果为:

A46G8HFD1134

正则表达式修饰符 - 可选标志

正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。多个标志可以通过按位 OR(|) 它们来指定。如 re.I | re.M 被设置成 I M 标志:

修饰符

描述

re.I

使匹配对大小写不敏感

re.L

做本地化识别(locale-aware)匹配

re.M

多行匹配,影响 ^ $

re.S

使 . 匹配包括换行在内的所有字符

re.U

根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B.

re.X

该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。

正则表达式模式

模式字符串使用特殊的语法来表示一个正则表达式:

字母和数字表示他们自身。一个正则表达式模式中的字母和数字匹配同样的字符串。

多数字母和数字前加一个反斜杠时会拥有不同的含义。

标点符号只有被转义时才匹配自身,否则它们表示特殊的含义。

反斜杠本身需要使用反斜杠转义。

由于正则表达式通常都包含反斜杠,所以你最好使用原始字符串来表示它们。模式元素( r'/t',等价于'//t')匹配相应的特殊字符。

下表列出了正则表达式模式语法中的特殊元素。如果你使用模式的同时提供了可选的标志参数,某些模式元素的含义会改变。

模式

描述

^

匹配字符串的开头

$

匹配字符串的末尾。

.

匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。

[...]

用来表示一组字符,单独列出:[amk] 匹配 'a''m''k'

[^...]

不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。

re*

匹配0个或多个的表达式。

re+

匹配1个或多个的表达式。

re?

匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式

re{ n}

re{ n,}

精确匹配n个前面表达式。

re{ n, m}

匹配 n m 次由前面的正则表达式定义的片段,贪婪方式

a| b

匹配ab

(re)

G匹配括号内的表达式,也表示一个组

(?imx)

正则表达式包含三种可选标志:i, m, x 。只影响括号中的区域。

(?-imx)

正则表达式关闭 i, m, x 可选标志。只影响括号中的区域。

(?: re)

类似 (...), 但是不表示一个组

(?imx: re)

在括号中使用i, m, x 可选标志

(?-imx: re)

在括号中不使用i, m, x 可选标志

(?#...)

注释.

(?= re)

前向肯定界定符。如果所含正则表达式,以 ... 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩余部分还要尝试界定符的右边。

(?! re)

前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功

(?> re)

匹配的独立模式,省去回溯。

\w

匹配字母数字

\W

匹配非字母数字

\s

匹配任意空白字符,等价于 [\t\n\r\f].

\S

匹配任意非空字符

\d

匹配任意数字,等价于 [0-9].

\D

匹配任意非数字

\A

匹配字符串开始

\Z

匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。c

\z

匹配字符串结束

\G

匹配最后匹配完成的位置。

\b

匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'

\B

匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'

\n, \t, .

匹配一个换行符。匹配一个制表符。等

\1...\9

匹配第n个分组的子表达式。

\10

匹配第n个分组的子表达式,如果它经匹配。否则指的是八进制字符码的表达式。

正则表达式实例

字符匹配

实例

描述

python

匹配 "python".

字符类

实例

描述

[Pp]ython

匹配 "Python" "python"

rub[ye]

匹配 "ruby" "rube"

[aeiou]

匹配中括号内的任意一个字母

[0-9]

匹配任何数字。类似于 [0123456789]

[a-z]

匹配任何小写字母

[A-Z]

匹配任何大写字母

[a-zA-Z0-9]

匹配任何字母及数字

[^aeiou]

除了aeiou字母以外的所有字符

[^0-9]

匹配除了数字外的字符

特殊字符类

实例

描述

.

匹配除 "\n" 之外的任何单个字符。要匹配包括 '\n' 在内的任何字符,请使用象 '[.\n]' 的模式。

\d

匹配一个数字字符。等价于 [0-9]

\D

匹配一个非数字字符。等价于 [^0-9]

\s

匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]

\S

匹配任何非空白字符。等价于 [^ \f\n\r\t\v]

\w

匹配包括下划线的任何单词字符。等价于'[A-Za-z0-9_]'

\W

匹配任何非单词字符。等价于 '[^A-Za-z0-9_]'

常见数据正则分析

2017517

21:51

QQ号表达式:

分析:

1、首先扣扣号开头不能为0;

2、QQ号必须大于5且小于11(或12,13,QQ号最长位);

则正则表达式为:    “[1-9]\\d{4,10}"

解析:

[1-9]为第一个数(第一个数不为0);

\\d:第一'\'为转义字符,'\d'为产生[0-9]的数字(第二位往后数字任意)

{4,10}表示至少4次最多10次(因为[1-9]占1位,剩下4或10位,这里默认扣扣号最短5位,最长10位);

手机号表达式:

分析:

1、手机号位数为11位;

2、开头为1,第二位为3或4或5或8;

则表达式为:    ”1[3458]\\d{9}

解析:

1:开头必须为1;

[3458]:第二位;

\\d: 第一个为转义字符,'\d'为产生任意数字;

{9}:恰好出现9次;

扩展:写出手机号表达式,且后5位相同;

”1[3458]\\d{4}(\\d)\\1{4}"

解析:

1:开头必须为1;

[3458]:第二位;

"\\d{4}":产生[0-9]的数字恰好出现4次(由于前面占2位,后面重复5次,11减去7,还剩4位);

"(\\d)\\1{4}”:首先圆括号()表示组的意思,'\\1'中的1表示的是第一组,第一个‘'\'表示转义,{4}出现4次,产生一个数最为一个组,将这组元素再重复4次;   注:(组:(\\d)\\1(\\d)\\2中的\\2表示第二组);

邮箱表达式:

[email protected]/cn/com.cn

eg:[email protected];[email protected]; [email protected][email protected]等;

分析:

1、@符号前面的可以为字母,数字,下划线,中划线,或'.';

2、@后面的可以是xxx.com、xxx.cn、xxx.com.cn;

表达式:"[\\w-\\.]+@([\\w]+\\.)+[a-z]{2,3}"

解析:

[\\w-\\.]: "\\w"为产生单个字符(a-z或A-Z或[0-9]),‘-’可能出现为中划线,“\\."表示可能出现'.' ;

’+‘表示'[]'里面出现一次或多次;

’@‘:为邮箱里面的@符号;

([\\w]+\\.)+:首先[\\w]+表示单个字符(a-z或A-Z或[0-9])出现一次或多次(如以上邮箱:@163,@alibaba,@sina,@qq,分别出现3,7,4,2次); ([\\w]+\\.)+:将()里面看成一组,()+这组出现一次或多次(为什么加上'.'?? ,因为后缀有两种格式:[email protected]或者[email protected],将(xxx.)和(com.)可看成相同格式的组);

[a-z]{2,3}: 产生后缀.com或.cn,所以出现2到3次;

用户名表达式:

题目:必须以字母开头,长度在10位以内

表达式:"[a-zA-z]\\w{0,9}"

解析:

[a-zA-Z]:用户名的第一位数为字母,[a-zA-z]表示产生这个a-z或A-Z范围的字母;

\\w{0,9}:  \\w产生单个字符 (a-z或A-Z或[0-9]),{0,9}:为至少0次最多9次;

密码表达式:

题目:任意字符,6~16位

表达式:".{6,16}"

解析:

'.' : 为产生任意字符;

{6,16}:至少6位,最多16位;

Python正则表达式七种兵器

2017517

22:14

1. 正则表达式基础

1.1. 简单介绍

正则表达式并不是Python的一部分。正则表达式是用于处理字符串的强大工具,拥有自己独特的语法以及一个独立的处理引擎,效率上可能不如str自带的方法,但功能十分强大。得益于这一点,在提供了正则表达式的语言里,正则表达式的语法都是一样的,区别只在于不同的编程语言实现支持的语法数量不同;但不用担心,不被支持的语法通常是不常用的部分。如果已经在其他语言里使用过正则表达式,只需要简单看一看就可以上手了。

下图展示了使用正则表达式进行匹配的流程: 

正则表达式的大致匹配过程是:依次拿出表达式和文本中的字符比较,如果每一个字符都能匹配,则匹配成功;一旦有匹配不成功的字符则匹配失败。如果表达式中有量词或边界,这个过程会稍微有一些不同,但也是很好理解的,看下图中的示例以及自己多使用几次就能明白。

下图列出了Python支持的正则表达式元字符和语法:   

1.2. 数量词的贪婪模式与非贪婪模式

正则表达式通常用于在文本中查找匹配的字符串。Python里数量词默认是贪婪的(在少数语言里也可能是默认非贪婪),总是尝试匹配尽可能多的字符;非贪婪的则相反,总是尝试匹配尽可能少的字符。例如:正则表达式"ab*"如果用于查找"abbbc",将找到"abbb"。而如果使用非贪婪的数量词"ab*?",将找到"a"

1.3. 反斜杠的困扰

与大多数编程语言相同,正则表达式里使用"\"作为转义字符,这就可能造成反斜杠困扰。假如你需要匹配文本中的字符"\",那么使用编程语言表示的正则表达式里将需要4个反斜杠"\\\\":前两个和后两个分别用于在编程语言里转义成反斜杠,转换成两个反斜杠后再在正则表达式里转义成一个反斜杠。Python里的原生字符串很好地解决了这个问题,这个例子中的正则表达式可以使用r"\\"表示。同样,匹配一个数字的"\\d"可以写成r"\d"。有了原生字符串,你再也不用担心是不是漏写了反斜杠,写出来的表达式也更直观。

1.4. 匹配模式

正则表达式提供了一些可用的匹配模式,比如忽略大小写、多行匹配等,这部分内容将在Pattern类的工厂方法re.compile(pattern[, flags])中一起介绍。

2. re模块

2.1. 开始使用re

Python通过re模块提供对正则表达式的支持。使用re的一般步骤是先将正则表达式的字符串形式编译为Pattern实例,然后使用Pattern实例处理文本并获得匹配结果(一个Match实例),最后使用Match实例获得信息,进行其他的操作。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

# encoding: UTF-8

import re

# 将正则表达式编译成Pattern对象

pattern = re.compile(r'hello')

# 使用Pattern匹配文本,获得匹配结果,无法匹配时将返回None

match = pattern.match('hello world!')

if match:

    # 使用Match获得分组信息

    print match.group()

### 输出 ###

# hello

re.compile(strPattern[, flag]):

这个方法是Pattern类的工厂方法,用于将字符串形式的正则表达式编译为Pattern对象。 第二个参数flag是匹配模式,取值可以使用按位或运算符'|'表示同时生效,比如re.I | re.M。另外,你也可以在regex字符串中指定模式,比如re.compile('pattern', re.I | re.M)re.compile('(?im)pattern')是等价的。 

可选值有:

·       re.I(re.IGNORECASE): 忽略大小写(括号内是完整写法,下同)

·       M(MULTILINE): 多行模式,改变'^''$'的行为(参见上图)

·       S(DOTALL): 点任意匹配模式,改变'.'的行为

·       L(LOCALE): 使预定字符类 \w \W \b \B \s \S 取决于当前区域设定

·       U(UNICODE): 使预定字符类 \w \W \b \B \s \S \d \D 取决于unicode定义的字符属性

·       X(VERBOSE): 详细模式。这个模式下正则表达式可以是多行,忽略空白字符,并可以加入注释。以下两个正则表达式是等价的:

1

2

3

4

a = re.compile(r"""\d +  # the integral part

                   \.    # the decimal point

                   \d *  # some fractional digits""", re.X)

b = re.compile(r"\d+\.\d*")

re提供了众多模块方法用于完成正则表达式的功能。这些方法可以使用Pattern实例的相应方法替代,唯一的好处是少写一行re.compile()代码,但同时也无法复用编译后的Pattern对象。这些方法将在Pattern类的实例方法部分一起介绍。如上面这个例子可以简写为:

1

2

m = re.match(r'hello', 'hello world!')

print m.group()

re模块还提供了一个方法escape(string),用于将string中的正则表达式元字符如*/+/?等之前加上转义符再返回,在需要大量匹配元字符时有那么一点用。

2.2. Match

Match对象是一次匹配的结果,包含了很多关于此次匹配的信息,可以使用Match提供的可读属性或方法来获取这些信息。

属性:

·       string: 匹配时使用的文本。

·       re: 匹配时使用的Pattern对象。

·       pos: 文本中正则表达式开始搜索的索引。值与Pattern.match()Pattern.seach()方法的同名参数相同。

·       endpos: 文本中正则表达式结束搜索的索引。值与Pattern.match()Pattern.seach()方法的同名参数相同。

·       lastindex: 最后一个被捕获的分组在文本中的索引。如果没有被捕获的分组,将为None

·       lastgroup: 最后一个被捕获的分组的别名。如果这个分组没有别名或者没有被捕获的分组,将为None

方法:

·       group([group1, …]): 
获得一个或多个分组截获的字符串;指定多个参数时将以元组形式返回。group1可以使用编号也可以使用别名;编号0代表整个匹配的子串;不填写参数时,返回group(0);没有截获字符串的组返回None;截获了多次的组返回最后一次截获的子串。

·       groups([default]): 
以元组形式返回全部分组截获的字符串。相当于调用group(1,2,…last)default表示没有截获字符串的组以这个值替代,默认为None

·       groupdict([default]): 
返回以有别名的组的别名为键、以该组截获的子串为值的字典,没有别名的组不包含在内。default含义同上。

·       start([group]): 
返回指定的组截获的子串在string中的起始索引(子串第一个字符的索引)。group默认值为0

·       end([group]): 
返回指定的组截获的子串在string中的结束索引(子串最后一个字符的索引+1)。group默认值为0

·       span([group]): 
返回(start(group), end(group))

·       expand(template): 
将匹配到的分组代入template中然后返回。template中可以使用\id\g<id>\g<name>引用分组,但不能使用编号0\id\g<id>是等价的;但\10将被认为是第10个分组,如果你想表达\1之后是字符'0',只能使用\g<1>0

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

import re

m = re.match(r'(\w+) (\w+)(?P<sign>.*)', 'hello world!')

print "m.string:", m.string

print "m.re:", m.re

print "m.pos:", m.pos

print "m.endpos:", m.endpos

print "m.lastindex:", m.lastindex

print "m.lastgroup:", m.lastgroup

print "m.group(1,2):", m.group(1, 2)

print "m.groups():", m.groups()

print "m.groupdict():", m.groupdict()

print "m.start(2):", m.start(2)

print "m.end(2):", m.end(2)

print "m.span(2):", m.span(2)

print r"m.expand(r'\2 \1\3'):", m.expand(r'\2 \1\3')

### output ###

# m.string: hello world!

# m.re: <_sre.SRE_Pattern object at 0x016E1A38>

# m.pos: 0

# m.endpos: 12

# m.lastindex: 3

# m.lastgroup: sign

# m.group(1,2): ('hello', 'world')

# m.groups(): ('hello', 'world', '!')

# m.groupdict(): {'sign': '!'}

# m.start(2): 6

# m.end(2): 11

# m.span(2): (6, 11)

# m.expand(r'\2 \1\3'): world hello!

2.3. Pattern

Pattern对象是一个编译好的正则表达式,通过Pattern提供的一系列方法可以对文本进行匹配查找。

Pattern不能直接实例化,必须使用re.compile()进行构造。

Pattern提供了几个可读属性用于获取表达式的相关信息:

·       pattern: 编译时用的表达式字符串。

·       flags: 编译时用的匹配模式。数字形式。

·       groups: 表达式中分组的数量。

·       groupindex: 以表达式中有别名的组的别名为键、以该组对应的编号为值的字典,没有别名的组不包含在内。

1

2

3

4

5

6

7

8

9

10

11

12

13

import re

p = re.compile(r'(\w+) (\w+)(?P<sign>.*)', re.DOTALL)

print "p.pattern:", p.pattern

print "p.flags:", p.flags

print "p.groups:", p.groups

print "p.groupindex:", p.groupindex

### output ###

# p.pattern: (\w+) (\w+)(?P<sign>.*)

# p.flags: 16

# p.groups: 3

# p.groupindex: {'sign': 3}

实例方法[ | re模块方法]

match(string[,pos[, endpos]]) | re.match(pattern, string[, flags]): 
这个方法将从stringpos下标处起尝试匹配pattern;如果pattern结束时仍可匹配,则返回一个Match对象;如果匹配过程中pattern无法匹配,或者匹配未结束就已到达endpos,则返回None 
pos
endpos的默认值分别为0len(string)re.match()无法指定这两个参数,参数flags用于编译pattern时指定匹配模式。 
注意:这个方法并不是完全匹配。当pattern结束时若string还有剩余字符,仍然视为成功。想要完全匹配,可以在表达式末尾加上边界匹配符'$' 
示例参见2.1小节。

search(string[,pos[, endpos]]) | re.search(pattern, string[, flags]): 
这个方法用于查找字符串中可以匹配成功的子串。从stringpos下标处起尝试匹配pattern,如果pattern结束时仍可匹配,则返回一个Match对象;若无法匹配,则将pos1后重新尝试匹配;直到pos=endpos时仍无法匹配则返回None 
pos
endpos的默认值分别为0len(string))re.search()无法指定这两个参数,参数flags用于编译pattern时指定匹配模式。 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

# encoding: UTF-8

import re

# 将正则表达式编译成Pattern对象

pattern = re.compile(r'world')

# 使用search()查找匹配的子串,不存在能匹配的子串时将返回None

# 这个例子中使用match()无法成功匹配

match = pattern.search('hello world!')

if match:

    # 使用Match获得分组信息

    print match.group()

### 输出 ###

# world

split(string[,maxsplit]) | re.split(pattern, string[, maxsplit]): 
按照能够匹配的子串将string分割后返回列表。maxsplit用于指定最大分割次数,不指定将全部分割。 

1

2

3

4

5

6

7

import re

p = re.compile(r'\d+')

print p.split('one1two2three3four4')

### output ###

# ['one', 'two', 'three', 'four', '']

findall(string[,pos[, endpos]]) | re.findall(pattern, string[, flags]): 
搜索string,以列表形式返回全部能匹配的子串。 

1

2

3

4

5

6

7

import re

p = re.compile(r'\d+')

print p.findall('one1two2three3four4')

### output ###

# ['1', '2', '3', '4']

finditer(string[,pos[, endpos]]) | re.finditer(pattern, string[, flags]): 
搜索string,返回一个顺序访问每一个匹配结果(Match对象)的迭代器。 

1

2

3

4

5

6

7

8

import re

p = re.compile(r'\d+')

for m in p.finditer('one1two2three3four4'):

    print m.group(),

### output ###

# 1 2 3 4

sub(repl,string[, count]) | re.sub(pattern, repl, string[, count]): 
使用repl替换string中每一个匹配的子串后返回替换后的字符串。 
repl是一个字符串时,可以使用\id\g<id>\g<name>引用分组,但不能使用编号0 
repl是一个方法时,这个方法应当只接受一个参数(Match对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。 
count
用于指定最多替换次数,不指定时全部替换。 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

import re

p = re.compile(r'(\w+) (\w+)')

s = 'i say, hello world!'

print p.sub(r'\2 \1', s)

def func(m):

    return m.group(1).title() + ' ' + m.group(2).title()

print p.sub(func, s)

### output ###

# say i, world hello!

# I Say, Hello World!

subn(repl,string[, count]) |re.sub(pattern, repl, string[, count]): 
返回 (sub(repl, string[, count]), 替换次数) 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

import re

p = re.compile(r'(\w+) (\w+)')

s = 'i say, hello world!'

print p.subn(r'\2 \1', s)

def func(m):

    return m.group(1).title() + ' ' + m.group(2).title()

print p.subn(func, s)

### output ###

# ('say i, world hello!', 2)

# ('I Say, Hello World!', 2)

 pythonRE模块之purge 方法

照旧,我们使用help:

purge()

   Clear the regular expression cache

解释:清空缓存中的正则表达式。

 

>>> p = re.compile(r'(\w+) (\w+)')  

>>> p.search("hello world 123 zhou write").group()      -- 匹配字符串中的两个单词正常  

'hello world'  

>>> p = re.purge()                                      -- 清空RE缓存  

>>> p.search("hello world 123 zhou write").group()      -- 再次使用search,报错!此时变成了'NoneType'对象,而不是Pattern对象  

Traceback (most recent call last):  

  File "<stdin>", line 1, in <module>  

AttributeError: 'NoneType' object has no attribute 'search'  

正则匹配

2017517

22:43

在正则表达式中,如果直接给出字符,就是精确匹配。用\d可以匹配一个数字,\w可以匹配一个字母或数字,所以:

·       '00\d'可以匹配'007',但无法匹配'00A'

·       '\d\d\d'可以匹配'010'

·       '\w\w\d'可以匹配'py3'

.可以匹配任意字符,所以:

·       'py.'可以匹配'pyc''pyo''py!'等等。

要匹配变长的字符,在正则表达式中,用*表示任意个字符(包括0个),用+表示至少一个字符,用?表示0个或1个字符,用{n}表示n个字符,用{n,m}表示n-m个字符:

来看一个复杂的例子:\d{3}\s+\d{3,8}

我们来从左到右解读一下:

·       \d{3}表示匹配3个数字,例如'010'

·       \s可以匹配一个空格(也包括Tab等空白符),所以\s+表示至少有一个空格,例如匹配' '' '等;

·       \d{3,8}表示3-8个数字,例如'1234567'

综合起来,上面的正则表达式可以匹配以任意个空格隔开的带区号的电话号码。

如果要匹配'010-12345'这样的号码呢?由于'-'是特殊字符,在正则表达式中,要用'\'转义,所以,上面的正则是\d{3}\-\d{3,8}

但是,仍然无法匹配'010 - 12345',因为带有空格。所以我们需要更复杂的匹配方式。

进阶

要做更精确地匹配,可以用[]表示范围,比如:

·       [0-9a-zA-Z\_]可以匹配一个数字、字母或者下划线;

·       [0-9a-zA-Z\_]+可以匹配至少由一个数字、字母或者下划线组成的字符串,比如'a100''0_Z''Py3000'等等;

·       [a-zA-Z\_][0-9a-zA-Z\_]*可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是Python合法的变量;

·       [a-zA-Z\_][0-9a-zA-Z\_]{0, 19}更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。

A|B可以匹配AB,所以(P|p)ython可以匹配'Python'或者'python'

^表示行的开头,^\d表示必须以数字开头。

$表示行的结束,\d$表示必须以数字结束。

你可能注意到了,py也可以匹配'python',但是加上^py$就变成了整行匹配,就只能匹配'py'了。

 

提取子串非常有用。来看一个更凶残的例子:

>>>t = '19:05:30'
>>> m = re.match(r'^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$', t)
>>> m.groups()
(
'19', '05', '30')

这个正则表达式可以直接识别合法的时间。但是有些时候,用正则表达式也无法做到完全验证,比如识别日期:

'^(0[1-9]|1[0-2]|[0-9])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]|[0-9])$'

对于'2-30''4-31'这样的非法日期,用正则还是识别不了,或者说写出来非常困难,这时就需要程序配合识别了。

贪婪匹配

最后需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的0

>>>re.match(r'^(\d+)(0*)$', '102300').groups()
(
'102300', '')

由于\d+采用贪婪匹配,直接把后面的0全部匹配了,结果0*只能匹配空字符串了。

必须让\d+采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0匹配出来,加个?就可以让\d+采用非贪婪匹配:

>>>re.match(r'^(\d+?)(0*)$', '102300').groups()
(
'1023', '00')

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

正则表达式用法

2017517

22:08

1 Python正则式的基本用法

Python的正则表达式的模块是 ‘re’,它的基本语法规则就是指定一个字符序列,比如你要在一个字符串s=’123abc456’ 中查找字符串 ’abc’,只要这样写:

>>>import re

>>>s='123abc456eabc789'

>>>re.findall(r’abc’,s)

结果就是:

['abc','abc']

这里用到的函数 ”findall(rule , target [,flag] )” 是个比较直观的函数,就是在目标字符串中查找符合规则的字符串。第一个参数是规则,第二个参数是目标字符串,后面还可以跟一个规则选项(选项功能将在compile函数的说明中详细说明)。返回结果结果是一个列表,中间存放的是符合规则的字符串。如果没有符合规则的字符串被找到,就返回一个空列表。

为什么要用r’ ..‘字符串(raw字符串)?由于正则式的规则也是由一个字符串定义的,而在正则式中大量使用转义字符’\’,如果不用raw字符串,则在需要写一个’\’的地方,你必须得写成’\\’,那么在要从目标字符串中匹配一个’\’的时候,你就得写上4个’\’成为’\\\\’!这当然很麻烦,也不直观,所以一般都使用r’’来定义规则字符串。当然,某些情况下,可能不用raw字符串比较好。

以上是个最简单的例子。当然实际中这么简单的用法几乎没有意义。为了实现复杂的规则查找,re规定了若干语法规则。它们分为这么几类:

功能字符 : ‘.’ ‘*’ ‘+’ ‘|’ ‘?’ ‘^’ ‘$’ ‘\’ 等,它们有特殊的功能含义。特别是’/’字符,它是转义引导符号,跟在它后面的字符一般有特殊的含义。

规则分界符: ‘[‘ ‘]’ ‘(’ ‘)’ ‘{‘‘}’ 等,也就是几种括号了。

预定义转义字符集: “\d” “\w” “\s”等等,它们是以字符’\’开头,后面接一个特定字符的形式,用来指示一个预定义好的含义。

其它特殊功能字符: ’#’ ‘!’ ‘:’ ‘-‘等,它们只在特定的情况下表示特殊的含义,比如(?# …)就表示一个注释,里面的内容会被忽略。

下面来一个一个的说明这些规则的含义,不过说明的顺序并不是按照上面的顺序来的,而是我认为由浅入深,由基本到复杂的顺序来编排的。同时为了直观,在说明的过程中尽量多举些例子以方便理解。

1.1 基本规则

‘[‘‘]’ 字符集合设定符

首先说明一下字符集合设定的方法。由一对方括号括起来的字符,表明一个字符集合,能够匹配包含在其中的任意一个字符。比如 [abc123],表明字符’a’ ‘b’‘c’ ‘1’ ‘2’‘3’都符合它的要求。可以被匹配。

在’[‘‘]’中还可以通过 ’-‘ 减号来指定一个字符集合的范围,比如可以用[a-zA-Z]来指定所以英文字母的大小写,因为英文字母是按照从小到大的顺序来排的。你不可以把大小的顺序颠倒了,比如写成[z-a]就不对了。

如果在’[‘ ‘]’里面的开头写一个 ‘^’号,则表示取非,即在括号里的字符都不匹配。如[^a-zA-Z]表明不匹配所有英文字母。但是如果 ‘^’不在开头,则它就不再是表示取非,而表示其本身,如[a-z^A-Z]表明匹配所有的英文字母和字符’^’。

‘|’或规则

将两个规则并列起来,以‘|’连接,表示只要满足其中之一就可以匹配。比如

[a-zA-Z]|[0-9]表示满足数字或字母就可以匹配,这个规则等价于 [a-zA-Z0-9]

注意:关于’|’要注意两点:

第一, 它在’[‘ ‘]’之中不再表示或,而表示他本身的字符。如果要在’[‘ ‘]’外面表示一个’|’字符,必须用反斜杠引导,即’/|’ ;

第二, 它的有效范围是它两边的整条规则,比如‘dog|cat’匹配的是‘dog’和’cat’,而不是’g’和’c’。如果想限定它的有效范围,必需使用一个无捕获组 ‘(?: )’包起来。比如要匹配 ‘I have a dog’或’I have a cat’,需要写成r’Ihave a (?:dog|cat)’ ,而不能写成 r’Ihave a dog|cat’

>>>s = ‘I have a dog , I have a cat’

>>>re.findall( r’I have a (?:dog|cat)’ , s )

['I have adog', 'I have a cat'] #正如我们所要的

下面再看看不用无捕获组会是什么后果:

>>>re.findall( r’I have a dog|cat’ , s )

['I have adog', 'cat'] #它将’I have a dog’ 和’cat’当成两个规则了

至于无捕获组的使用,后面将仔细说明。这里先跳过。

‘.’匹配所有字符

匹配除换行符’\n’外的所有字符。如果使用了’S’选项,匹配包括’\n’的所有字符。

例:

>>>s=’123 \n456 \n789’

>>>findall(r‘.+’,s)

['123','456', '789']

>>>re.findall(r‘.+’ , s, re.S)

['123\n456\n789']

‘^’和’$’ 匹配字符串开头和结尾

注意’^’不能在‘[ ]’中,否则含意就发生变化,具体请看上面的’[‘ ‘]’说明。 在多行模式下,它们可以匹配每一行的行首和行尾。具体请看后面compile函数说明的’M’选项部分

‘\d’匹配数字

这是一个以’\’开头的转义字符,’\d’表示匹配一个数字,即等价于[0-9]

‘\D’匹配非数字

这个是上面的反集,即匹配一个非数字的字符,等价于[^0-9]。注意它们的大小写。下面我们还将看到Python的正则规则中很多转义字符的大小写形式,代表互补的关系。这样很好记。

‘\w’匹配字母和数字

匹配所有的英文字母和数字,即等价于[a-zA-Z0-9]。

‘\W’匹配非英文字母和数字

即’\w’的补集,等价于[^a-zA-Z0-9]。

‘\s’匹配间隔符

即匹配空格符、制表符、回车符等表示分隔意义的字符,它等价于[ \t\r\n\f\v]。(注意最前面有个空格)

‘\S’匹配非间隔符

即间隔符的补集,等价于[^ \t\r\n\f\v]

‘\A’匹配字符串开头

匹配字符串的开头。它和’^’的区别是,’\A’只匹配整个字符串的开头,即使在’M’模式下,它也不会匹配其它行的行首。

‘\Z’匹配字符串结尾

匹配字符串的结尾。它和’$’的区别是,’\Z’只匹配整个字符串的结尾,即使在’M’模式下,它也不会匹配其它各行的行尾。

例:

>>>s= '12 34\n56 78\n90'

>>>re.findall( r'^\d+' , s , re.M ) #匹配位于行首的数字

['12', '56','90']

>>>re.findall( r’\A\d+’,s , re.M ) #匹配位于字符串开头的数字

['12']

>>>re.findall( r'\d+$' , s , re.M ) #匹配位于行尾的数字

['34', '78','90']

>>>re.findall( r’\d+\Z’, s , re.M ) #匹配位于字符串尾的数字

['90']

‘\b’匹配单词边界

它匹配一个单词的边界,比如空格等,不过它是一个‘0’长度字符,它匹配完的字符串不会包括那个分界的字符。而如果用’\s’来匹配的话,则匹配出的字符串中会包含那个分界符。

例:

>>>s = 'abc abcde bc bcd'

>>>re.findall( r’\bbc\b’, s ) #匹配一个单独的单词 ‘bc’ ,而当它是其它单词的一部分的时候不匹配

['bc'] #只找到了那个单独的’bc’

>>>re.findall( r’\sbc\s’, s ) #匹配一个单独的单词 ‘bc’

[' bc '] #只找到那个单独的’bc’,不过注意前后有两个空格,可能有点看不清楚

‘\B’匹配非边界

和’\b’相反,它只匹配非边界的字符。它同样是个0长度字符。

接上例:

>>>re.findall( r’\Bbc\w+’ , s ) #匹配包含’bc’但不以’bc’为开头的单词

['bcde'] #成功匹配了’abcde’中的’bcde’,而没有匹配’bcd’

‘(?:)’无捕获组

当你要将一部分规则作为一个整体对它进行某些操作,比如指定其重复次数时,你需要将这部分规则用’(?:’ ‘)’把它包围起来,而不能仅仅只用一对括号,那样将得到绝对出人意料的结果。

例:匹配字符串中重复的’ab’

>>>s=’ababab abbabb aabaab’

>>>re.findall( r’\b(?:ab)+\b’ , s )

['ababab']

如果仅使用一对括号,看看会是什么结果:

>>>re.findall( r’bab)+\b’ , s ) 

['ab'] 

这是因为如果只使用一对括号,那么这就成为了一个组(group)。组的使用比较复杂,将在后面详细讲解。 

‘(?# )’ 注释 

Python允许你在正则表达式中写入注释,在’(?#’ ‘)’之间的内容将被忽略。 

(?iLmsux) 编译选项指定 

Python的正则式可以指定一些选项,这个选项可以写在findall或compile的参数中,也可以写在正则式里,成为正则式的一部分。这在某些情况下会便利一些。具体的选项含义请看后面的compile函数的说明。 

此处编译选项’i’ 等价于IGNORECASE ,L 等价于 LOCAL ,m 等价于 MULTILINE ,s 等价于 DOTALL ,u 等价于 UNICODE , x 等价于 VERBOSE 。 

请注意它们的大小写。在使用时可以只指定一部分,比如只指定忽略大小写,可写为 ‘(?i)’,要同时忽略大小写并使用多行模式,可以写为 ‘(?im)’。 

另外要注意选项的有效范围是整条规则,即写在规则的任何地方,选项都会对全部整条正则式有效。 

1.2 重复 

正则式需要匹配不定长的字符串,那就一定需要表示重复的指示符。Python的正则式表示重复的功能很丰富灵活。重复规则的一般的形式是在一条字符规则后面紧跟一个表示重复次数的规则,已表明需要重复前面的规则一定的次数。重复规则有: 

‘*’ 0或多次匹配 

表示匹配前面的规则0次或多次。 

‘+’ 1次或多次匹配 

表示匹配前面的规则至少1次,可以多次匹配 

例:匹配以下字符串中的前一部分是字母,后一部分是数字或没有的变量名字 

>>> s = ‘ aaa bbb111 cc22cc 33dd ‘ 

>>> re.findall( r’\b[a-z]+\d*\b’ , s ) #必须至少1个字母开头,以连续数字结尾或没有数字 

['aaa', 'bbb111'] 

注意上例中规则前后加了表示单词边界的’\b’指示符,如果不加的话结果就会变成: 

>>> re.findall( r’[a-z]+\d*’ , s ) 

['aaa', 'bbb111', 'cc22', 'cc', 'dd'] #把单词给拆开了 

大多数情况下这不是我们期望的结果。 

‘?’ 0或1次匹配 

只匹配前面的规则0次或1次。 

例,匹配一个数字,这个数字可以是一个整数,也可以是一个科学计数法记录的数字,比如123和10e3都是正确的数字。 

>>> s = ‘ 123 10e3 20e4e4 30ee5 ‘ 

>>> re.findall( r’ \b\d+[eE]?\d*\b’ , s ) 

['123', '10e3'] 

它正确匹配了123和10e3,正是我们期望的。注意前后的’\b’的使用,否则将得到不期望的结果。 

1.2.1 精确匹配和最小匹配 

Python正则式还可以精确指定匹配的次数。指定的方式是 

‘{m}’ 精确匹配m次 

‘{m,n}’ 匹配最少m次,最多n次。(n>m) 

如果你只想指定一个最少次数或只指定一个最多次数,你可以把另外一个参数空起来。比如你想指定最少3次,可以写成 {3,} (注意那个逗号),同样如果只想指定最大为5次,可以写成{,5},也可以写成{0,5}。 

例 寻找下面字符串中 

a:3位数 

b: 2位数到4位数 

c: 5位数以上的数 

d: 4位数以下的数 

>>> s= ‘ 1 22 333 4444 55555 666666 ‘ 

>>> re.findall( r’\b\d{3}\b’ , s ) # a:3位数 

['333'] 

>>> re.findall( r’\b\d{2,4}\b’ , s ) # b: 2位数到4位数 

['22', '333', '4444'] 

>>> re.findall( r’\b\d{5,}\b’, s ) # c: 5位数以上的数 

['55555', '666666'] 

>>> re.findall( r’\b\d{1,4}\b’ , s ) # 4位数以下的数 

['1', '22', '333', '4444'] 

‘*?’ ‘+?’ ‘??’ 最小匹配 

‘*’ ‘+’ ‘?’通常都是尽可能多的匹配字符。有时候我们希望它尽可能少的匹配。比如一个c语言的注释 ‘/* part 1 */ /* part 2 */’,如果使用最大规则: 

>>> s =r ‘/* part 1 */ code /* part 2 */’ 

>>> re.findall( r’/\*.*\*/’ , s ) 

[‘/* part 1 */ code /* part 2 */’] 

结果把整个字符串都包括进去了。如果把规则改写成 

>>> re.findall( r’/\*.*?\*/’ , s ) #在*后面加上?,表示尽可能少的匹配 

['/* part 1 */', '/* part 2 */'] 

结果正确的匹配出了注释里的内容 

1.3 前向界定与后向界定 

有时候需要匹配一个跟在特定内容后面的或者在特定内容前面的字符串,Python提供一个简便的前向界定和后向界定功能,或者叫前导指定和跟从指定功能。它们是: 

‘(?<=…)’ 前向界定 

括号中’…’代表你希望匹配的字符串的前面应该出现的字符串。 

‘(?=…)’ 后向界定 

括号中的’…’代表你希望匹配的字符串后面应该出现的字符串。 

例: 你希望找出c语言的注释中的内容,它们是包含在’/*’和’*/’之间,不过你并不希望匹配的结果把’/*’和’*/’也包括进来,那么你可以这样用: 

>>> s=r’/* comment 1 */ code /* comment 2 */’ 

>>> re.findall( r’(?<=/\*).+?(?=\*/)’ , s ) 

[' comment 1 ', ' comment 2 '] 

注意这里我们仍然使用了最小匹配,以避免把整个字符串给匹配进去了。 

要注意的是,前向界定括号中的表达式必须是常值,也即你不可以在前向界定的括号里写正则式。比如你如果在下面的字符串中想找到被字母夹在中间的数字,你不可以用前向界定: 

例: 

>>> s = ‘aaa111aaa , bbb222 , 333ccc ‘ 

>>> re.findall( r’(?<=[a-z]+)\d+(?=[a-z]+)' , s ) # 错误的用法 

它会给出一个错误信息: 

error: look-behind requires fixed-width pattern 

不过如果你只要找出后面接着有字母的数字,你可以在后向界定写正则式: 

>>> re.findall( r’\d+(?=[a-z]+)’, s ) 

['111', '333'] 

如果你一定要匹配包夹在字母中间的数字,你可以使用组(group)的方式 

>>> re.findall (r'[a-z]+(\d+)[a-z]+' , s ) 

['111'] 

组的使用将在后面详细讲解。 

除了前向界定前向界定和后向界定外,还有前向非界定和后向非界定,它的写法为: 

‘(?<!...)’前向非界定 

只有当你希望的字符串前面不是’…’的内容时才匹配 

‘(?!...)’后向非界定 

只有当你希望的字符串后面不跟着’…’内容时才匹配。 

接上例,希望匹配后面不跟着字母的数字 

>>> re.findall( r’\d+(?!\w+)’ , s ) 

['222'] 

注意这里我们使用了\w而不是像上面那样用[a-z],因为如果这样写的话,结果会是: 

>>> re.findall( r’\d+(?![a-z]+)’ , s ) 

['11', '222', '33'] 

这和我们期望的似乎有点不一样。它的原因,是因为’111’和’222’中的前两个数字也是满足这个要求的。因此可看出,正则式的使用还是要相当小心的,因为我开始就是这样写的,看到结果后才明白过来。不过Python试验起来很方便,这也是脚本语言的一大优点,可以一步一步的试验,快速得到结果,而不用经过烦琐的编译、链接过程。也因此学习Python就要多试,跌跌撞撞的走过来,虽然曲折,却也很有乐趣。 

1.4 组的基本知识 

上面我们已经看过了Python的正则式的很多基本用法。不过如果仅仅是上面那些规则的话,还是有很多情况下会非常麻烦,比如上面在讲前向界定和后向界定时,取夹在字母中间的数字的例子。用前面讲过的规则都很难达到目的,但是用了组以后就很简单了。 

‘(‘’)’ 无命名组 

最基本的组是由一对圆括号括起来的正则式。比如上面匹配包夹在字母中间的数字的例子中使用的(\d+),我们再回顾一下这个例子: 

>>> s = ‘aaa111aaa , bbb222 , 333ccc ‘ 

>>> re.findall (r'[a-z]+(\d+)[a-z]+' , s ) 

['111'] 

可以看到findall函数只返回了包含在’()’中的内容,而虽然前面和后面的内容都匹配成功了,却并不包含在结果中。 

除了最基本的形式外,我们还可以给组起个名字,它的形式是 

‘(?P<name>…)’ 命名组 

‘(?P’代表这是一个Python的语法扩展’<…>’里面是你给这个组起的名字,比如你可以给一个全部由数字组成的组叫做’num’,它的形式就是’(?P<num>\d+)’。起了名字之后,我们就可以在后面的正则式中通过名字调用这个组,它的形式是 

‘(?P=name)’ 调用已匹配的命名组 

要注意,再次调用的这个组是已被匹配的组,也就是说它里面的内容是和前面命名组里的内容是一样的。 

我们可以看更多的例子:请注意下面这个字符串各子串的特点。 

>>> s='aaa111aaa,bbb222,333ccc,444ddd444,555eee666,fff777ggg' 

我们看看下面的正则式会返回什么样的结果: 

>>> re.findall( r'([a-z]+)\d+([a-z]+)' , s ) # 找出中间夹有数字的字母 

[('aaa', 'aaa'), ('fff', 'ggg')] 

>>> re.findall( r '(?P<g1>[a-z]+)\d+(?P=g1)' , s ) #找出被中间夹有数字的前后同样的字母 

['aaa'] 

>>> re.findall( r'[a-z]+(/d+)([a-z]+)' , s ) #找出前面有字母引导,中间是数字,后面是字母的字符串中的中间的数字和后面的字母 

[('111', 'aaa'), ('777', 'ggg')] 

我们可以通过命名组的名字在后面调用已匹配的命名组,不过名字也不是必需的。 

‘\number’ 通过序号调用已匹配的组 

正则式中的每个组都有一个序号,序号是按组从左到右,从1开始的数字,你可以通过下面的形式来调用已匹配的组 

比如上面找出被中间夹有数字的前后同样的字母的例子,也可以写成: 

>>> re.findall( r’([a-z]+)\d+\1’ , s ) 

['aaa'] 

结果是一样的。 

我们再看一个例子 

>>> s='111aaa222aaa111 , 333bbb444bb33' 

>>> re.findall( r'(\d+)([a-z]+)(\d+)(\2)(\1)' , s ) #找出完全对称的 数字-字母-数字-字母-数字 中的数字和字母 

[('111', 'aaa', '222', 'aaa', '111')] 

Python2.4以后的re模块,还加入了一个新的条件匹配功能 

‘(?(id/name)yes-pattern|no-pattern)’ 判断指定组是否已匹配,执行相应的规则 

这个规则的含义是,如果id/name指定的组在前面匹配成功了,则执行yes-pattern的正则式,否则执行no-pattern的正则式。 

举个例子,比如要匹配一些形如 usr@mail 的邮箱地址,不过有的写成< usr@mail >即用一对<>括起来,有点则没有,要匹配这两种情况,可以这样写 

>>> s='<usr1@mail1> usr2@maill2' 

>>> re.findall( r'(<)?\s*(\w+@\w+)\s*(?(1)>)' , s ) 

[('<', 'usr1@mail1'), ('', 'usr2@maill2')] 

不过如果目标字符串如下 

>>> s='<usr1@mail1> usr2@maill2 <usr3@mail3 usr4@mail4> < usr5@mail5 ' 

而你想得到要么由一对<>包围起来的一个邮件地址,要么得到一个没有被<>包围起来的地址,但不想得到一对<>中间包围的多个地址或不完整的<>中的地址,那么使用这个式子并不能得到你想要的结果 

>>> re.findall( r'(<)?\s*(\w+@\w+)\s*(?(1)>)' , s ) 

[('<', 'usr1@mail1'), ('', 'usr2@maill2'), ('', 'usr3@mail3'), ('', 'usr4@mail4'), ('', 'usr5@mail5')] 

它仍然找到了所有的邮件地址。 

想要实现这个功能,单纯的使用findall有点吃力,需要使用其它的一些函数,比如match或search函数,再配合一些控制功能。这部分的内容将在下面详细讲解。 

小结:以上基本上讲述了Python正则式的语法规则。虽然大部分语法规则看上去都很简单,可是稍不注意,仍然会得到与期望大相径庭的结果,所以要写好正则式,需要仔细的体会正则式规则的含义后不同规则之间细微的差别。 

详细的了解了规则后,再配合后面就要介绍的功能函数,就能最大的发挥正则式的威力了。 

<FONT style="FONT-SIZE: 10.5pt" face="""> 

2 re模块的基本函数 

在上面的说明中,我们已经对re模块的基本函数 ‘findall’很熟悉了。当然如果光有findall的话,很多功能是不能实现的。下面开始介绍一下re模块其它的常用基本函数。灵活搭配使用这些函数,才能充分发挥Python正则式的强大功能。 

首先还是说下老熟人findall函数吧 

findall(rule , target [,flag] ) 

在目标字符串中查找符合规则的字符串。 

第一个参数是规则,第二个参数是目标字符串,后面还可以跟一个规则选项(选项功能将在compile函数的说明中详细说明)。 

返回结果结果是一个列表,中间存放的是符合规则的字符串。如果没有符合规则的字符串被找到,就返回一个空列表。 

2.1 使用compile加速 

compile( rule [,flag] ) 

将正则规则编译成一个Pattern对象,以供接下来使用。 

第一个参数是规则式,第二个参数是规则选项。 

返回一个Pattern对象 

直接使用findall ( rule , target )的方式来匹配字符串,一次两次没什么,如果是多次使用的话,由于正则引擎每次都要把规则解释一遍,而规则的解释又是相当费时间的,所以这样的效率就很低了。如果要多次使用同一规则来进行匹配的话,可以使用re.compile函数来将规则预编译,使用编译过返回的Regular Expression Object或叫做Pattern对象来进行查找。 

例 

>>> s='111,222,aaa,bbb,ccc333,444ddd' 

>>> rule=r’\b\d+\b’ 

>>> compiled_rule=re.compile(rule) 

>>> compiled_rule.findall(s) 

['111', '222'] 

可见使用compile过的规则使用和未编译的使用很相似。compile函数还可以指定一些规则标志,来指定一些特殊选项。多个选项之间用 ’|’(位或)连接起来。 

I IGNORECASE 忽略大小写区别。 

L LOCAL 字符集本地化。这个功能是为了支持多语言版本的字符集使用环境的,比如在转义符\w,在英文环境下,它代表[a-zA-Z0-9],即所以英文字符和数字。如果在一个法语环境下使用,缺省设置下,不能匹配"é" 或 "ç"。加上这L选项和就可以匹配了。不过这个对于中文环境似乎没有什么用,它仍然不能匹配中文字符。 

M MULTILINE 多行匹配。在这个模式下’^’(代表字符串开头)和’$’(代表字符串结尾)将能够匹配多行的情况,成为行首和行尾标记。比如 

>>> s=’123 456\n789 012\n345 678’ 

>>> rc=re.compile(r’^\d+’) #匹配一个位于开头的数字,没有使用M选项 

>>> rc.findall(s) 

['123'] #结果只能找到位于第一个行首的’123’ 

>>> rcm=re.compile(r’^\d+’,re.M) #使用 M 选项 

>>> rcm.findall(s) 

['123', '789', '345'] #找到了三个行首的数字 

同样,对于’$’来说,没有使用M选项,它将匹配最后一个行尾的数字,即’678’,加上以后,就能匹配三个行尾的数字456 012和678了. 

>>> rc=re.compile(r’\d+$’) 

>>> rcm=re.compile(r’\d+$’,re.M) 

>>> rc.findall(s) 

['678'] 

>>> rcm.findall(s) 

['456', '012', '678'] 

S DOTALL ‘.’号将匹配所有的字符。缺省情况下’.’匹配除换行符’\n’外的所有字符,使用这一选项以后,’.’就能匹配包括’\n’的任何字符了。 

U UNICODE \w, \W, \b, \B, \d, \D, \s 和 \S都将使用Unicode。 

X VERBOSE 这个选项忽略规则表达式中的空白,并允许使用’#’来引导一个注释。这样可以让你把规则写得更美观些。比如你可以把规则 

>>> rc = re.compile(r"\d+|[a-zA-Z]+") #匹配一个数字或者单词 

使用X选项写成: 

>>> rc = re.compile(r""" # start a rule 

\d+ # number 

| [a-zA-Z]+ # word 

""", re.VERBOSE) 

在这个模式下,如果你想匹配一个空格,你必须用'\ '的形式('\'后面跟一个空格)  

2.2 match与search 

match( rule , targetString [,flag] ) 

search( rule , targetString [,flag] ) 

(注:re的match 与search函数同compile过的Pattern对象的match与search函数的参数是不一样的。Pattern对象的match与search函数更为强大,是真正最常用的函数) 

按照规则在目标字符串中进行匹配。 

第一个参数是正则规则,第二个是目标字符串,第三个是选项(同compile函数的选项) 

返回:若成功返回一个Match对象,失败无返回 

findall虽然很直观,但是在进行更复杂的操作时,就有些力不从心了。此时更多的使用的是match和search函数。他们的参数和findall是一样的,都是: 

match( rule , targetString [,flag] ) 

search( rule , targetString [,flag] ) 

不过它们的返回不是一个简单的字符串列表,而是一个MatchObject (如果匹配成功的话).。通过操作这个matchObject,我们可以得到更多的信息。 

需要注意的是,如果匹配不成功,它们则返回一个NoneType。所以在对匹配完的结果进行操作之前,你必需先判断一下是否匹配成功了,比如: 

>>> m=re.match( rule , target ) 

>>> if m: #必需先判断是否成功 

doSomethin 

这两个函数唯一的区别是:match从字符串的开头开始匹配,如果开头位置没有匹配成功,就算失败了;而search会跳过开头,继续向后寻找是否有匹配的字符串。针对不同的需要,可以灵活使用这两个函数。 

关于match返回的MatchObject如果使用的问题,是Python正则式的精髓所在,它与组的使用密切相关。我将在下一部分详细讲解,这里只举个最简单的例子: 

例: 

>>> s= 'Tom:9527 , Sharry:0003' 

>>> m=re.match( r'(?P<name>\w+):(?P<num>\d+)' , s ) 

>>> m.group() 

'Tom:9527' 

>>> m.groups() 

('Tom', '9527') 

>>> m.group(‘name’) 

'Tom' 

>>> m.group(‘num’) 

'9527' 

2.3 finditer 

finditer( rule , target [,flag] ) 

参数同findall 

返回一个迭代器 

finditer函数和findall函数的区别是,findall返回所有匹配的字符串,并存为一个列表,而finditer则并不直接返回这些字符串,而是返回一个迭代器。关于迭代器,解释起来有点复杂,还是看看例子把: 

>>> s=’111 222 333 444’ 

>>> for i in re.finditer(r’\d+’ , s ): 

print i.group(),i.span() #打印每次得到的字符串和起始结束位置 

结果是 

111 (0, 3) 

222 (4, 7) 

333 (8, 11) 

444 (12, 15) 

简单的说吧,就是finditer返回了一个可调用的对象,使用 for i in finditer()的形式,可以一个一个的得到匹配返回的 Match对象。这在对每次返回的对象进行比较复杂的操作时比较有用。 

2.4 字符串的替换和修改 

re模块还提供了对字符串的替换和修改函数,他们比字符串对象提供的函数功能要强大一些。这几个函数是 

sub ( rule , replace , target [,count] ) 

subn(rule , replace , target [,count] ) 

在目标字符串中规格规则查找匹配的字符串,再把它们替换成指定的字符串。你可以指定一个最多替换次数,否则将替换所有的匹配到的字符串。 

第一个参数是正则规则,第二个参数是指定的用来替换的字符串,第三个参数是目标字符串,第四个参数是最多替换次数。 

这两个函数的唯一区别是返回值。 

sub返回一个被替换的字符串 

sub返回一个元组,第一个元素是被替换的字符串,第二个元素是一个数字,表明产生了多少次替换。 

例,将下面字符串中的’dog’全部替换成’cat’ 

>>> s=’ I have a dog , you have a dog , he have a dog ‘ 

>>> re.sub( r’dog’ , ‘cat’ , s ) 

' I have a cat , you have a cat , he have a cat ' 

如果我们只想替换前面两个,则 

>>> re.sub( r’dog’ , ‘cat’ , s , 2 ) 

' I have a cat , you have a cat , he have a dog ' 

或者我们想知道发生了多少次替换,则可以使用subn 

>>> re.subn( r’dog’ , ‘cat’ , s ) 

(' I have a cat , you have a cat , he have a cat ', 3) 

split( rule , target [,maxsplit] ) 

切片函数。使用指定的正则规则在目标字符串中查找匹配的字符串,用它们作为分界,把字符串切片。 

第一个参数是正则规则,第二个参数是目标字符串,第三个参数是最多切片次数 

返回一个被切完的子字符串的列表 

这个函数和str对象提供的split函数很相似。举个例子,我们想把上例中的字符串被’,’分割开,同时要去掉逗号前后的空格 

>>> s=’ I have a dog , you have a dog , he have a dog ‘ 

>>> re.split( ‘\s*,\s*’ , s ) 

[' I have a dog', 'you have a dog', 'he have a dog '] 

结果很好。如果使用str对象的split函数,则由于我们不知道’,’两边会有多少个空格,而不得不对结果再进行一次处理。 

escape( string ) 

这是个功能比较古怪的函数,它的作用是将字符串中的non-alphanumerics字符(我已不知道该怎么翻译比较好了)用反义字符的形式显示出来。有时候你可能希望在正则式中匹配一个字符串,不过里面含有很多re使用的符号,你要一个一个的修改写法实在有点麻烦,你可以使用这个函数, 

例 在目标字符串s中匹配’(*+?)’这个子字符串 

>>> s= ‘111 222 (*+?) 333’ 

>>> rule= re.escape( r’(*+?)’ ) 

>>> print rule 

\(\*\+\?

>>>re.findall( rule , s )

['(*+?)']

<FONTstyle="FONT-SIZE: 10.5pt" face=""">

3 更深入的了解re的组与对象

前面对Python正则式的组进行了一些简单的介绍,由于还没有介绍到match对象,而组又是和match对象密切相关的,所以必须将它们结合起来介绍才能充分地说明它们的用途。

不过再详细介绍它们之前,我觉得有必要先介绍一下将规则编译后的生成的patter对象

3.1编译后的Pattern对象

将一个正则式,使用compile函数编译,不仅是为了提高匹配的速度,同时还能使用一些附加的功能。编译后的结果生成一个Pattern对象,这个对象里面有很多函数,他们看起来和re模块的函数非常象,它同样有findall , match , search ,finditer , sub , subn , split 这些函数,只不过它们的参数有些小小的不同。一般说来,re模块函数的第一个参数,即正则规则不再需要了,应为规则就包含在Pattern对象中了,编译选项也不再需要了,因为已经被编译过了。因此re模块中函数的这两个参数的位置,就被后面的参数取代了。

findall ,match , search 和finditer这几个函数的参数是一样的,除了少了规则和选项两个参数外,它们又加入了另外两个参数,它们是:查找开始位置和查找结束位置,也就是说,现在你可以指定查找的区间,除去你不感兴趣的区间。它们现在的参数形式是:

findall (targetString [, startPos [,endPos] ] )

finditer (targetString [, startPos [,endPos] ] )

match (targetString [, startPos [,endPos] ] )

search (targetString [, startPos [,endPos] ] )

这些函数的使用和re模块的同名函数使用完全一样。所以就不多介绍了。

除了和re模块的函数同样的函数外,Pattern对象还多了些东西,它们是:

flags 查询编译时的选项

pattern 查询编译时的规则

groupindex 规则里的组

这几个不是函数,而是一个值。它们提供你一些规则的信息。比如下面这个例子

>>>p=re.compile(r'(?P<word>\b[a-z]+\b)|(?P<num>\b\d+\b)|(?P<id>\b[a-z_]+\w*\b)', re.I )

>>>p.flags

2

>>>p.pattern

'(?P<word>\\b[a-z]+\\b)|(?P<num>\\b\\d+\\b)|(?P<id>\\b[a-z_]+\\w*\\b)'

>>>p.groupindex

{'num': 2,'word': 1, 'id': 3}

我们来分析一下这个例子:这个正则式是匹配单词、或数字、或一个由字母或’_’开头,后面接字母或数字的一个ID。我们给这三种情况的规则都包入了一个命名组,分别命名为’word’ , ‘num’和 ‘id’。我们规定大小写不敏感,所以使用了编译选项 ‘I’。

编译以后返回的对象为p,通过p.flag我们可以查看编译时的选项,不过它显示的不是’I’,而是一个数值2 。其实re.I是一个整数,2就是它的值。我们可以查看一下:

>>>re.I

2

>>>re.L

4

>>>re.M

8

每个选项都是一个数值。

通过p.pattern可以查看被编译的规则是什么。使用print的话会更好看一些

>>>print p.pattern

(?P<word>\b[a-z]+\b)|(?P<num>\b\d+\b)|(?P<id>\b[a-z_]+\w*\b)

看,和我们输入的一样。

接下来的p.groupindex则是一个字典,它包含了规则中的所有命名组。字典的key是名字,values是组的序号。由于字典是以名字作为key,所以一个无命名的组不会出现在这里。

3.2 组与Match对象

组与Match对象是Python正则式的重点。只有掌握了组和Match对象的使用,才算是真正学会了Python正则式。

3.2.1 组的名字与序号

正则式中的每个组都有一个序号,它是按定义时从左到右的顺序从1开始编号的。其实,re的正则式还有一个0号组,它就是整个正则式本身。

我们来看个例子

>>>p=re.compile( r’(?P<name>[a-z]+)\s+(?P<age>\d+)\s+(?P<tel>\d+).*’ , re.I )

>>>p.groupindex

{'age': 2,'tel': 3, 'name': 1}

>>>s=’Tom 24 88888888 <=’

>>>m=p.search(s)

>>>m.groups() # 看看匹配的各组的情况

('Tom','24', '8888888')

>>>m.group(‘name’)# 使用组名获取匹配的字符串

‘Tom’

>>>m.group( 1 ) # 使用组序号获取匹配的字符串,同使用组名的效果一样

>>>m.group(0) # 0 组里面是什么呢?

'Tom 2488888888 <='

原来0组就是整个正则式,包括没有被包围到组里面的内容。当获取0组的时候,你可以不写这个参数。m.group(0)和m.group()的效果是一样的:

>>>m.group()

'Tom 2488888888 <='

接下来看看更多的Match对象的方法,看看我们能做些什么。

3.2.2 Match对象的方法

group([index|id])获取匹配的组,缺省返回组0,也就是全部值

groups() 返回全部的组

groupdict() 返回以组名为key,匹配的内容为values的字典

接上例:

>>>m.groupindex()

{'age':'24', 'tel': '88888888', 'name': 'Tom'}

start([group] ) 获取匹配的组的开始位置

end( [group]) 获取匹配的组的结束位置

span([group] ) 获取匹配的组的(开始,结束)位置

expand(template ) 根据一个模版用找到的内容替换模版里的相应位置

这个功能比较有趣,它根据一个模版来用匹配到的内容替换模版中的相应位置,组成一个新的字符串返回。它使用\g<index|name>或 \index 来指示一个组。

接上例

>>>m.expand(r'name is \g<1> , age is \g<age> , tel is \3')

'name is Tom, age is 24 , tel is 88888888'

除了以上这些函数外,Match对象还有些属性

pos 搜索开始的位置参数

endpos 搜索结束的位置参数

这两个是使用findall或match等函数时,传入的参数。在上面这个例子里,我们没有指定开始和结束位置,那么缺省的开始位置就是0,结束位置就是最后。

>>>m.pos

0

>>>m.endpos

19

lastindex 最后匹配的组的序号

>>>m.lastindex

3

lastgroup 最后匹配的组名

>>>m.lastgroup

'tel'

re 产生这个匹配的Pattern对象,可以认为是个逆引用

>>>m.re.pattern

'(?P<name>[a-z]+)\\s+(?P<age>\\d+)\\s+(?P<tel>\\d+).*'

得到了产生这个匹配的规则

string 匹配的目标字符串

>>>m.string

'Tom 2488888888 <='

正则表达式补充

2017518

21:37

\<num>

引用分组num匹配到的字符串

(?P<name>)

分组起别名

(?P=name)

引用别名为name分组匹配到的字符串

需求:匹配出<html>hh</html>

#coding=utf-8

import re

# 能够完成对正确的字符串的匹配
ret = re.match("<[a-zA-Z]*>\w*</[a-zA-Z]*>","<html>hh</html>")
ret.group()

# 如果遇到非正常的html格式字符串,匹配出错
ret = re.match("<[a-zA-Z]*>\w*</[a-zA-Z]*>","<html>hh</htmlbalabala>")
ret.group()

# 正确的理解思路:如果在第一对<>中是什么,按理说在后面的那对<>中就应该是什么

# 通过引用分组中匹配到的数据即可,但是要注意是元字符串,即类似 r""这种格式
ret = re.match(r"<([a-zA-Z]*)>\w*</\1>","<html>hh</html>")
ret.group()

# 因为2<>中的数据不一致,所以没有匹配出来
ret = re.match(r"<([a-zA-Z]*)>\w*</\1>","<html>hh</htmlbalabala>")
ret.group()

 

 

 

\number

需求:匹配出<html><h1>www.tsinghua.edu.cn</h1></html>

#coding=utf-8

import re

ret =re.match(r"<(\w*)><(\w*)>.*</\2></\1>","<html><h1>www.tsinghua.edu.cn</h1></html>")
ret.group()

ret =re.match(r"<(\w*)><(\w*)>.*</\2></\1>","<html><h1>www.tsinghua.edu.cn</h2></html>")
ret.group()

 

 

(?P) (?P=name)

需求:匹配出<html><h1>www.qq.com</h1></html>

#coding=utf-8

import re

ret =re.match(r"<(?P<name1>\w*)><(?P<name2>\w*)>.*</(?P=name2)></(?P=name1)>","<html><h1>www.qq.com</h1></html>")
ret.group()

ret =re.match(r"<(?P<name1>\w*)><(?P<name2>\w*)>.*</(?P=name2)></(?P=name1)>","<html><h1>www.qq.com</h2></html>")
ret.group()

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Python3 多线程

2017413

11:20

 

多线程类似于同时执行多个不同程序,多线程运行有如下优点:

·       使用线程可以把占据长时间的程序中的任务放到后台去处理。

·       用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度

·       程序的运行速度可能加快

·       在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。

线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。

指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。

·       线程可以被抢占(中断)。

·       在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) -- 这就是线程的退让。

线程可以分为:

·       内核线程:由操作系统内核创建和撤销。

·       用户线程:不需要内核支持而在用户程序中实现的线程。

Python3 线程中常用的两个模块为:

·       _thread

·       threading(推荐使用)

thread 模块已被废弃。用户可以使用 threading 模块代替。所以,在 Python3 中不能再使用"thread"模块。为了兼容性,Python3 thread 重命名为"_thread"

开始学习Python线程

Python中使用线程有两种方式:函数或者用类来包装线程对象。

函数式:调用 _thread 模块中的start_new_thread()函数来产生新线程。语法如下:

_thread.start_new_thread ( function, args[, kwargs] )

参数说明:

·       function - 线程函数。

·       args - 传递给线程函数的参数,他必须是个tuple类型。

·       kwargs - 可选参数。

实例:

#!/usr/bin/python3

import_thread
importtime

# 为线程定义一个函数
def print_time( threadName, delay):
   count =0
   while count < 5:
      time.sleep(delay)
      count +=1
      print("%s:%s" % ( threadName, time.ctime(time.time()) ))

# 创建两个线程
try:
   _thread.start_new_thread( print_time, ("Thread-1",2, ) )
   _thread.start_new_thread( print_time, ("Thread-2",4, ) )
except:
   print("Error: 无法启动线程")

while 1:
   pass

执行以上程序输出结果如下:

Thread-1: WedApr  6 11:36:31 2016
Thread-1: WedApr  6 11:36:33 2016
Thread-2: WedApr  6 11:36:33 2016
Thread-1: WedApr  6 11:36:35 2016
Thread-1: WedApr  6 11:36:37 2016
Thread-2: WedApr  6 11:36:37 2016
Thread-1: WedApr  6 11:36:39 2016
Thread-2: WedApr  6 11:36:41 2016
Thread-2: WedApr  6 11:36:45 2016
Thread-2: WedApr  6 11:36:49 2016

执行以上程后可以按下 ctrl-c to 退出。

线程模块

Python3 通过两个标准库 _thread threading 提供对线程的支持。

_thread 提供了低级别的、原始的线程以及一个简单的锁,它相比于 threading 模块的功能还是比较有限的。

threading 模块除了包含 _thread 模块中的所有方法外,还提供的其他方法:

·       threading.currentThread(): 返回当前的线程变量。

·       threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。

·       threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:

·       run(): 用以表示线程活动的方法。

·       start():启动线程活动。

·       join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。

·       isAlive(): 返回线程是否活动的。

·       getName(): 返回线程名。

·       setName(): 设置线程名。

使用 threading 模块创建线程

我们可以通过直接从 threading.Thread 继承创建一个新的子类,并实例化后调用 start() 方法启动新线程,即它调用了线程的 run() 方法:

#!/usr/bin/python3

importthreading
importtime

exitFlag =0

classmyThread (threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
       
self.name = name
       
self.counter = counter
   
def run(self):
        print("开始线程:"+ self.name)
        print_time(self.name, self.counter, 5)
        print("退出线程:"+ self.name)

defprint_time(threadName, delay, counter):
    while counter:
        if exitFlag:
            threadName.exit()
        time.sleep(delay)
        print("%s:%s" % (threadName, time.ctime(time.time())))
        counter -=1

# 创建新线程
thread1 = myThread(1,"Thread-1",1)
thread2 = myThread(2,"Thread-2",2)

# 开启新线程
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print ("退出主线程")

以上程序执行结果如下;

开始线程:Thread-1
开始线程:Thread-2
Thread-1: WedApr  6 11:46:46 2016
Thread-1: WedApr  6 11:46:47 2016
Thread-2: WedApr  6 11:46:47 2016
Thread-1: WedApr  6 11:46:48 2016
Thread-1: WedApr  6 11:46:49 2016
Thread-2: WedApr  6 11:46:49 2016
Thread-1: WedApr  6 11:46:50 2016
退出线程:Thread-1
Thread-2: WedApr  6 11:46:51 2016
Thread-2: WedApr  6 11:46:53 2016
Thread-2: WedApr  6 11:46:55 2016
退出线程:Thread-2
退出主线程

线程同步

如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。

使用 Thread 对象的 Lock Rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire release 方法之间。如下:

多线程的优势在于可以同时运行多个任务(至少感觉起来是这样)。但是当线程需要共享数据时,可能存在数据不同步的问题。

考虑这样一种情况:一个列表里所有元素都是0,线程"set"从后向前把所有元素改成1,而线程"print"负责从前往后读取列表并打印。

那么,可能线程"set"开始改的时候,线程"print"便来打印列表了,输出就成了一半0一半1,这就是数据的不同步。为了避免这种情况,引入了锁的概念。

锁有两种状态——锁定和未锁定。每当一个线程比如"set"要访问共享数据时,必须先获得锁定;如果已经有别的线程比如"print"获得锁定了,那么就让线程"set"暂停,也就是同步阻塞;等到线程"print"访问完毕,释放锁以后,再让线程"set"继续。

经过这样的处理,打印列表时要么全部输出0,要么全部输出1,不会再出现一半0一半1的尴尬场面。

实例:

#!/usr/bin/python3

importthreading
importtime

classmyThread (threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
       
self.name = name
       
self.counter = counter
   
def run(self):
        print("开启线程:" + self.name)
        #获取锁,用于线程同步
        threadLock.acquire()
        print_time(self.name, self.counter, 3)
        #释放锁,开启下一个线程
        threadLock.release()

defprint_time(threadName, delay, counter):
    while counter:
        time.sleep(delay)
        print("%s:%s" % (threadName, time.ctime(time.time())))
        counter -=1

threadLock = threading.Lock()
threads = []

# 创建新线程
thread1 = myThread(1,"Thread-1",1)
thread2 = myThread(2,"Thread-2",2)

# 开启新线程
thread1.start()
thread2.start()

# 添加线程到线程列表
threads.append(thread1)
threads.append(thread2)

# 等待所有线程完成
for t in threads:
    t.join()
print ("退出主线程")

执行以上程序,输出结果为:

开启线程: Thread-1
开启线程: Thread-2
Thread-1: WedApr  6 11:52:57 2016
Thread-1: WedApr  6 11:52:58 2016
Thread-1: WedApr  6 11:52:59 2016
Thread-2: WedApr  6 11:53:01 2016
Thread-2: WedApr  6 11:53:03 2016
Thread-2: WedApr  6 11:53:05 2016
退出主线程

线程优先级队列( Queue

Python Queue 模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列QueueLIFO(后入先出)队列LifoQueue,和优先级队列 PriorityQueue

这些队列都实现了锁原语,能够在多线程中直接使用,可以使用队列来实现线程间的同步。

Queue 模块中的常用方法:

·       Queue.qsize() 返回队列的大小

·       Queue.empty() 如果队列为空,返回True,反之False

·       Queue.full() 如果队列满了,返回True,反之False

·       Queue.full maxsize 大小对应

·       Queue.get([block[, timeout]])获取队列,timeout等待时间

·       Queue.get_nowait() 相当Queue.get(False)

·       Queue.put(item) 写入队列,timeout等待时间

·       Queue.put_nowait(item) 相当Queue.put(item, False)

·       Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号

·       Queue.join() 实际上意味着等到队列为空,再执行别的操作

实例:

#!/usr/bin/python3

importqueue
importthreading
importtime

exitFlag =0

classmyThread (threading.Thread):
    def __init__(self, threadID, name, q):
        threading.Thread.__init__(self)
        self.threadID = threadID
       
self.name = name
       
self.q = q
   
def run(self):
        print("开启线程:"+ self.name)
        process_data(self.name, self.q)
        print("退出线程:"+ self.name)

defprocess_data(threadName, q):
    whilenot exitFlag:
        queueLock.acquire()
        ifnot workQueue.empty():
            data = q.get()
            queueLock.release()
            print("%sprocessing %s" % (threadName, data))
        else:
            queueLock.release()
        time.sleep(1)

threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = queue.Queue(10)
threads = []
threadID = 1

# 创建新线程
for tName in threadList:
    thread = myThread(threadID, tName, workQueue)
    thread.start()
    threads.append(thread)
    threadID +=1

# 填充队列
queueLock.acquire()
for word in nameList:
    workQueue.put(word)
queueLock.release()

# 等待队列清空
while notworkQueue.empty():
    pass

# 通知线程是时候退出
exitFlag = 1

# 等待所有线程完成
for t in threads:
    t.join()
print ("退出主线程")

以上程序执行结果:

开启线程:Thread-1
开启线程:Thread-2
开启线程:Thread-3
Thread-3 processing One
Thread-1 processing Two
Thread-2 processing Three
Thread-3 processing Four
Thread-1 processing Five
退出线程:Thread-3
退出线程:Thread-2
退出线程:Thread-1
退出主线程

现实生活中多任务

2017518

20:21

现实生活中

有很多的场景中的事情是同时进行的,比如开车的时候手和脚共同来驾驶汽车,再比如唱歌跳舞也是同时进行的;试想,如果把唱歌和跳舞这2件事情分开依次完成的话,估计就没有那么好的效果了(想一下场景:先唱歌,然后在跳舞,O(∩_∩)O哈哈~

程序中

如下程序,来模拟唱歌跳舞这件事情


    #coding=utf-8

from time import sleep

def sing():
        for i in range(3):
           
print("正在唱歌...%d"%i)
           
sleep(1)

def dance():
        for i in range(3):
           
print("正在跳舞...%d"%i)
           
sleep(1)

if __name__ == '__main__':
       
sing() #唱歌
        dance() #跳舞

 

 

!!!注意

·       很显然刚刚的程序并没有完成唱歌和跳舞同时进行的要求

·       如果想要实现“唱歌跳舞”同时进行,那么就需要一个新的方法,叫做:多线程

 

多任务的概念

2017518

20:22

多任务的概念

什么叫多任务呢?简单地说,就是操作系统可以同时运行多个任务。打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。

现在,多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?

答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。

2017518

20:22

多线程-thread

<1> 实现多任务

    #coding=utf-8
    import thread
   
from time importsleep,ctime

def sing():
        for i in range(3):
           
print "正在唱歌...%d"%i
           
sleep(1)

def dance():
        for i in range(3):
           
print "正在跳舞...%d"%i
           
sleep(1)

if __name__ == '__main__':
       
print '---开始---:', ctime()

# 第一个参数:创建的新线程要执行的代码
        # 第二个参数给新线程执行sing函数时传递的参数,即使没有参数也要传递空元组
        thread.start_new_thread(sing, ())
        thread.start_new_thread(dance,())
        sleep(5)
       
print '---结束---:', ctime()

 

说明:

·       直接调用singdance函数,实现的效果是先唱完歌曲之后,在单独进行跳舞

·       通过thread.start_new_thread间接调用singdance函数,实现了唱歌和跳舞同时进行的完美结合

<2>过程解析

单任务:

多任务:

<3>试一试

·       现需要把“吹风”这个功能,也要添加到上面程序中同时进行,该怎样办?并编程实现

·       把sleep(5)这行代码屏蔽,再次运行程序,看执行结果有什么不同

多线程-threading

2017518

20:22

pythonthread模块是比较底层的模块,pythonthreading模块是对thread做了一些包装的,可以更加方便的被使用

<1>使用threading模块

单线程执行

#coding=utf-8
import time

def saySorry():
    print "亲爱的,我错了,我能吃饭了吗?"
    time.sleep(1)

if __name__ == "__main__":
   
for i in range(5):
       
saySorry()

运行结果:

多线程执行

#coding=utf-8
import threading
import time

def saySorry():
    print "亲爱的,我错了,我能吃饭了吗?"
    time.sleep(1)

if __name__ == "__main__":
   
for i in range(5):
       
t =threading.Thread(target=saySorry)
        t.start()

运行结果:

说明:可以明显看出使用了多线程并发的操作,花费时间要短很多

<2>验证上一小节的第2个问题

#coding=utf-8
import threading
from time import sleep,ctime

def sing():
    for i in range(3):
       
print "正在唱歌...%d"%i
       
sleep(1)

def dance():
    for i in range(3):
       
print "正在跳舞...%d"%i
       
sleep(1)

if __name__ == '__main__':
   
print '---开始---:', ctime()

t1 = threading.Thread(target=sing)
   
t2 = threading.Thread(target=dance)

t1.start()
   
t2.start()

#sleep(5) # 屏蔽此行代码,试试看,程序是否会立马结束?
    print '---结束---:', ctime()

<2>线程的执行

pythonthreading.Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时,就会调用run方法执行线程。让我们开始第一个例子:

#coding=utf-8
import threading
import time

class MyThread(threading.Thread):
   
def run(self):
        for i in range(3):
           
time.sleep(1)
           
msg = "I'm "+self.name+' @ '+str(i)
           
print msg
def test():
    for i in range(5):
       
t = MyThread()
        t.start()
if __name__ == '__main__':
   
test()

执行结果:(运行的结果可能不一样,但是大体是一致的)

    I'm Thread-1 @ 0
    I'm Thread-2 @ 0
    I'm Thread-5 @ 0
    I'm Thread-3 @ 0
    I'm Thread-4 @ 0
    I'm Thread-3 @ 1
    I'm Thread-4 @ 1
    I'm Thread-5 @ 1
    I'm Thread-1 @ 1
    I'm Thread-2 @ 1
    I'm Thread-4 @ 2
    I'm Thread-5 @ 2
    I'm Thread-2 @ 2
    I'm Thread-1 @ 2
    I'm Thread-3 @ 2

说明:

从代码和执行结果我们可以看出,多线程程序的执行顺序是不确定的。当执行到sleep语句时,线程将被阻塞(Blocked),到sleep结束后,线程进入就绪(Runnable)状态,等待调度。而线程调度将自行选择一个线程执行。上面的代码中只能保证每个线程都运行完整个run函数,但是线程的启动顺序、run函数中每次循环的执行顺序都不能确定。此外需要注意的是:

1.   每个线程一定会有一个名字,尽管上面的例子中没有指定线程对象的name,但是python会自动为线程指定一个名字。

2.   当线程的run()方法结束时该线程完成。

3.   无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式。

上面的例子只是简单的演示了创建了线程、主动挂起以及退出线程。下一节,将讨论用互斥锁进行线程同步。

2017518

20:22

使用互斥锁同步线程

问题的提出上一节的例子中,每个线程互相独立,相互之间没有任何关系。现在假设这样一个例子:有一个全局的计数num,每个线程获取这个全局的计数,根据num进行一些处理,然后将num1。很容易写出这样的代码:

    #coding=utf-8
    import threading
   
import time

class MyThread(threading.Thread):
       
def run(self):
            global num

             num = num+1
            time.sleep(0.5) #用来模拟适当的数据处理
            msg = self.name+' set num to '+str(num)
           
print msg

num = 0

def test():

for i in range(5):
           
t = MyThread()
            t.start()
    if __name__ == '__main__':
       
test()

 

啊???为什么结果会这样?

问题产生的原因就是没有控制多个线程对同一资源的访问,对数据造成破坏,使得线程运行的结果不可预期。这种现象称为线程不安全

互斥锁同步

上面的例子引出了多线程编程的最常见问题:数据共享

当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制

线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为锁定,其他线程不能更改;直到该线程释放资源,将资源的状态变成非锁定,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

threading模块中定义了Lock类,可以方便的处理锁定:

#创建锁
mutex = threading.Lock()
#
锁定
mutex.acquire([timeout])
#
释放
mutex.release()

其中,锁定方法acquire可以有一个超时时间的可选参数timeout。如果设定了timeout,则在超时后通过返回值可以判断是否得到了锁,从而可以进行一些其他的处理。

使用互斥锁实现上面的例子的代码如下:

    #coding=utf-8
    import threading
   
import time

class MyThread(threading.Thread):
       
def run(self):
            global num

            if mutex.acquire(1): 
                num = num+1
                time.sleep(0.5)
               
msg = self.name+' set num to '+str(num)
               
print msg
               
mutex.release()
    num = 0
    mutex = threading.Lock()
    def test():
        for i in range(5):
           
t = MyThread()
            t.start()
    if __name__ == '__main__':
       
test()

运行结果:

可以看到,加入互斥锁后,运行结果与预期相符。

总结

当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为同步阻塞

直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。

锁的好处:

·       确保了某段关键代码只能由一个线程从头到尾完整地执行

锁的坏处:

·       阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了

·       由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁

死锁

2017518

20:22

死锁

现实社会中,男女双方都在等待对方先道歉

如果双方都这样固执的等待对方先开口,弄不好,就分搜了

死锁

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。

尽管死锁很少发生,但一旦发生就会造成应用的停止响应。下面看一个死锁的例子

#coding=utf-8
import threading
import time

class MyThread1(threading.Thread):
   
def run(self):
        if mutexA.acquire():
           
print(self.name+'----do1---up----')
           
time.sleep(1)

if mutexB.acquire():
               
print(self.name+'----do1---down----')
               
mutexB.release()
            mutexA.release()

class MyThread2(threading.Thread):
   
def run(self):
        if mutexB.acquire():
           
print(self.name+'----do2---up----')
           
time.sleep(1)
           
if mutexA.acquire():
               
print(self.name+'----do2---down----')
               
mutexA.release()
            mutexB.release()

mutexA = threading.Lock()
mutexB = threading.Lock()

if __name__ == '__main__':
   
t1 = MyThread1()
    t2 = MyThread2()
    t1.start()
    t2.start()

线程同步之可重入锁

2017518

21:13

线程同步之可重入锁

可重入锁

更简单的死锁情况是一个线程迭代请求同一个资源,直接就会造成死锁:

import threading
import time

class MyThread(threading.Thread):
   
def run(self):
        global num
        time.sleep(1)

if mutex.acquire(1): 
            num = num+1
            msg = self.name+' set num to'+str(num)
            print msg
            mutex.acquire()
            mutex.release()
            mutex.release()
num = 0
mutex = threading.Lock()
def test():
    for i in range(5):
        t = MyThread()
        t.start()
if __name__ == '__main__':
    test()

为了支持在同一线程中多次请求同一资源,python提供了可重入锁threading.RLockRLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

import threading
import time

class MyThread(threading.Thread):
   
def run(self):
        global num
        time.sleep(1)

if mutex.acquire(1): 
            num = num+1
            msg = self.name+' set num to'+str(num)
            print msg
            mutex.acquire()
            mutex.release()
            mutex.release()
num = 0
mutex = threading.RLock()
def test():
    for i in range(5):
        t = MyThread()
        t.start()
if __name__ == '__main__':
    test()

运行结果:

    Thread-1 set num to 1
    Thread-3 set num to 2
    Thread-2 set num to 3
    Thread-5 set num to 4
    Thread-4 set num to 5

线程同步之条件变量

2017518

21:13

线程同步之条件变量

互斥锁是最简单的线程同步机制,Python提供的Condition对象提供了对复杂线程同步问题的支持。Condition被称为条件变量,除了提供与Lock类似的acquirerelease方法外,还提供了waitnotify方法。线程首先acquire一个条件变量,然后判断一些条件。如果条件不满足则wait;如果条件满足,进行一些处理改变条件后,通过notify方法通知其他线程,其他处于wait状态的线程接到通知后会重新判断条件。不断的重复这一过程,从而解决复杂的同步问题。

可以认为Condition对象维护了一个锁(Lock/RLock)和一个waiting池。线程通过acquire获得Condition对象,当调用wait方法时,线程会释放Condition内部的锁并进入blocked状态,同时在waiting池中记录这个线程。当调用notify方法时,Condition对象会从waiting池中挑选一个线程,通知其调用acquire方法尝试取到锁。

Condition对象的构造函数可以接受一个Lock/RLock对象作为参数,如果没有指定,则Condition对象会在内部自行创建一个RLock

除了notify方法外,Condition对象还提供了notifyAll方法,可以通知waiting池中的所有线程尝试acquire内部锁。由于上述机制,处于waiting状态的线程只能通过notify方法唤醒,所以notifyAll的作用在于防止有线程永远处于沉默状态。

演示条件变量同步的经典问题是生产者与消费者问题:假设有一群生产者(Producer)和一群消费者(Consumer)通过一个市场来交互产品。生产者的策略是如果市场上剩余的产品少于1000个,那么就生产100个产品放到市场上;而消费者的策略是如果市场上剩余产品的数量多余100个,那么就消费3个产品。用Condition解决生产者与消费者问题的代码如下:

import threading
import time

class Producer(threading.Thread):
    def run(self):
        globalcount
        whileTrue:
            ifcon.acquire():
                ifcount > 1000:
                    con.wait()
                else:
                    count = count+100
                    msg = self.name+' produce 100, count=' + str(count)
                    print msg
                    con.notify()
                con.release()
                time.sleep(1)

class Consumer(threading.Thread):
    def run(self):
        globalcount
        whileTrue:
            ifcon.acquire():
                ifcount < 100:
                    con.wait()
                else:
                    count = count-3
                    msg = self.name+' consume 3, count='+str(count)
                    print msg
                    con.notify()
                con.release()
                time.sleep(1)

count = 500
con = threading.Condition()

def test():
    fori in range(2):
        p = Producer()
        p.start()
    fori in range(5):
        c = Consumer()
        c.start()
if __name__ == '__main__':
    test()

线程同步之队列

2017518

21:13

让我们考虑更复杂的一种场景:产品是各不相同的。这时只记录一个数量就不够了,还需要记录每个产品的细节。很容易想到需要用一个容器将这些产品记录下来。

PythonQueue模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列QueueLIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue。这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步。

FIFO队列实现上述生产者与消费者问题的代码如下:

#encoding=utf-8
import threading
import time
from Queue importQueue

class Producer(threading.Thread):
    def run(self):
        globalqueue
        count = 0
        whileTrue:
            fori in range(100):
                ifqueue.qsize() > 1000:
                     pass
                else:
                     count = count +1
                     msg = '生成产品'+str(count)
                     queue.put(msg)
                     print msg
            time.sleep(1)

class Consumer(threading.Thread):
    def run(self):
        globalqueue
        whileTrue:
            fori in range(3):
                ifqueue.qsize() < 100:
                    pass
                else:
                    msg = self.name + '消费了 '+queue.get()
                    print msg
            time.sleep(1)

queue = Queue()

def test():
    fori in range(500):
        queue.put('初始产品'+str(i))
    fori in range(2):
        p = Producer()
        p.start()
    fori in range(5):
        c = Consumer()
        c.start()
if __name__ == '__main__':
    test()

线程间通信

2017518

21:13

线程间通信

很多时候,线程之间会有互相通信的需要。常见的情形是次要线程为主要线程执行特定的任务,在执行过程中需要不断报告执行的进度情况。前面的条件变量同步已经涉及到了线程间的通信(threading.Conditionnotify方法)。更通用的方式是使用threading.Event对象。 threading.Event可以使一个线程等待其他线程的通知。其内置了一个标志,初始值为False。线程通过wait()方法进入等待状态,直到另一个线程调用set()方法将内置标志设置为True时,Event通知所有等待状态的线程恢复运行。还可以通过isSet()方法查询Envent对象内置状态的当前值。

举例如下:

import threading
import random
import time

class MyThread(threading.Thread):
    def __init__(self,threadName,event):
       threading.Thread.__init__(self,name=threadName)
        self.threadEvent = event

def run(self):
        print"%s is ready" % self.name
        self.threadEvent.wait()
        print"%s run!" % self.name

sinal =threading.Event()
for i inrange(10):
    t = MyThread(str(i),sinal)
    t.start()

time.sleep(1)

sinal.set()

线程的合并和后台线程

2017518

21:16

线程的合并和后台线程

线程的合并

pythonThread类中还提供了join()方法,使得一个线程可以等待另一个线程执行结束后再继续运行。这个方法还可以设定一个timeout参数,避免无休止的等待。因为两个线程顺序完成,看起来象一个线程,所以称为线程的合并。一个例子:

import threading
import random
import time

class MyThread(threading.Thread):

def run(self):
        wait_time=random.randrange(1,10)
       
print "%s will wait %d seconds" % (self.name, wait_time)
       
time.sleep(wait_time)
        print "%s finished!" % self.name

if __name__=="__main__":
   
threads = []
    for i in range(5):
       
t = MyThread()
        t.start()
        threads.append(t)
    print 'main thread is waitting for exit...'       
    for t in threads:
       
t.join(1)

print 'main thread finished!'

运行结果:

    Thread-1 willwait 3 seconds
    Thread-2 will wait 4 seconds
    Thread-3 will wait 1 seconds
    Thread-4 will wait 5 seconds
    Thread-5 will wait 3 seconds
    main thread is waitting for exit...
    Thread-3 finished!
    Thread-1 finished!
    Thread-5 finished!
    main thread finished!
    Thread-2 finished!
    Thread-4 finished!

对于sleep时间过长的线程(这里是24),将不被等待。

后台线程

默认情况下,主线程在退出时会等待所有子线程的结束。如果希望主线程不等待子线程,而是在退出时自动结束所有的子线程,就需要设置子线程为后台线程(daemon)。方法是通过调用线程类的setDaemon()方法。如下:

import threading
import random
import time

class MyThread(threading.Thread):

def run(self):
       
wait_time=random.randrange(1,10)
        print "%s will wait %dseconds" % (self.name, wait_time)
        time.sleep(wait_time)
        print "%s finished!" %self.name

if __name__=="__main__":
   
print 'main thread is waitting forexit...'       

for i in range(5):
       
t = MyThread()
        t.setDaemon(True)
        t.start()

print 'main thread finished!'

运行结果:

    main thread iswaitting for exit...
    Thread-1 will wait 3 seconds
    Thread-2 will wait 3 seconds
    Thread-3 will wait 4 seconds
    Thread-4 will wait 7 seconds
    Thread-5 will wait 7 seconds
    main thread finished!

可以看出,主线程没有等待子线程的执行,而直接退出。

小结join()方法使得线程可以等待另一个线程的运行,而setDaemon()方法使得线程在结束时不等待子线程。join和setDaemon都可以改变线程之间的运行顺序。

ThreadLocal

2017518

21:16

ThreadLocal

在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁。

但是局部变量也有问题,就是在函数调用的时候,传递起来很麻烦:

def process_student(name):
   
std = Student(name)
    # std是局部变量,但是每个函数都要用它,因此必须传进去:
    do_task_1(std)
    do_task_2(std)

def do_task_1(std):
   
do_subtask_1(std)
    do_subtask_2(std)

def do_task_2(std):
   
do_subtask_2(std)
    do_subtask_2(std)

每个函数一层一层调用都这么传参数那还得了?用全局变量?也不行,因为每个线程处理不同的Student对象,不能共享。

如果用一个全局dict存放所有的Student对象,然后以thread自身作为key获得线程对应的Student对象如何?

global_dict = {}

def std_thread(name):
   
std = Student(name)
    # std放到全局变量global_dict中:
   global_dict[threading.current_thread()] = std
    do_task_1()
    do_task_2()

def do_task_1():
   
# 不传入std,而是根据当前线程查找:
    std =global_dict[threading.current_thread()]
    ...

def do_task_2():
   
# 任何函数都可以查找出当前线程的std变量:
    std =global_dict[threading.current_thread()]
    ...

这种方式理论上是可行的,它最大的优点是消除了std对象在每层函数中的传递问题,但是,每个函数获取std的代码有点丑。

有没有更简单的方式?

ThreadLocal应运而生,不用查找dictThreadLocal帮你自动做这件事:

import threading

# 创建全局ThreadLocal对象:
local_school = threading.local()

def process_student():
   
# 获取当前线程关联的student:
   
std = local_school.student
    print('Hello, %s (in %s)' % (std,threading.current_thread().name))

def process_thread(name):
   
# 绑定ThreadLocalstudent:
   
local_school.student = name
    process_student()

t1 = threading.Thread(target=process_thread, args=('Alice',), name='Thread-A')
t2 = threading.Thread(target= process_thread, args=('Bob',), name='Thread-B')
t1.start()
t2.start()
t1.join()
t2.join()

执行结果:

Hello, Alice (in Thread-A)
Hello, Bob (in Thread-B)

全局变量local_school就是一个ThreadLocal对象,每个Thread对它都可以读写student属性,但互不影响。你可以把local_school看成全局变量,但每个属性如local_school.student都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal内部会处理。

可以理解为全局变量local_school是一个dict,不但可以用local_school.student,还可以绑定其他变量,如local_school.teacher等等。

ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。

小结

一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题

线程相关函数

2017522

8:28

Event

2017325

17:39

condtion

2017325

17:41

semapore

2017325

17:41

Timer

2017325

17:42

TLS

2017325

17:42

多进程概述

2017523

8:54

老手说:Python下多线程是鸡肋,推荐使用多进程!,但是为什么这么说呢?

要知其然,更要知其所以然。所以有了下面的深入研究:

首先强调背景:

1. GIL是什么?

GIL的全称是Global InterpreterLock(全局解释器锁),来源是python设计之初的考虑,为了数据安全所做的决定。

2. 每个CPU在同一时间只能执行一个线程

在单核CPU下的多线程其实都只是并发,不是并行,并发和并行从宏观上来讲都是同时处理多路请求的概念。但并发和并行又有区别,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。

Python多线程下,每个线程的执行方式:

1.   获取GIL

2.   执行代码直到sleep或者是python虚拟机将其挂起。

3.   释放GIL

可见,某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是通行证,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。

Python2.x里,GIL的释放逻辑是当前线程遇见IO操作或者ticks计数达到100ticks可以看作是Python自身的一个计数器,专门作用于GIL,每次释放后归零,这个计数可以通过sys.setcheckinterval 来调整),进行释放。

而每次释放GIL锁,线程进行锁竞争、切换线程,会消耗资源。并且由于GIL锁存在,python里一个进程永远只能同时执行一个线程(拿到GIL的线程才能执行),这就是为什么在多核CPU上,python的多线程效率并不高。

那么是不是python的多线程就完全没用了呢?

在这里我们进行分类讨论:

1.   CPU密集型代码(各种循环处理、计数等等),在这种情况下,由于计算工作多,ticks计数很快就会达到阈值,然后触发GIL的释放与再竞争(多个线程来回切换当然是需要消耗资源的),所以python下的多线程对CPU密集型代码并不友好

2.   IO密集型代码(文件处理、网络爬虫等),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率)。所以python的多线程对IO密集型代码比较友好

而在python3.x中,GIL不使用ticks计数,改为使用计时器(执行时间达到阈值后,当前线程释放GIL),这样对CPU密集型程序更加友好,但依然没有解决GIL导致的同一时间只能执行一个线程的问题,所以效率依然不尽如人意。

请注意:多核多线程比单核多线程更差,原因是单核下的多线程,每次释放GIL,唤醒的那个线程都能获取到GIL锁,所以能够无缝执行,但多核下,CPU0释放GIL后,其他CPU上的线程都会进行竞争,但GIL可能会马上又被CPU0拿到,导致其他几个CPU上被唤醒后的线程会醒着等待到切换时间后又进入待调度状态,这样会造成线程颠簸(thrashing),导致效率更低。

回到最开始的问题:经常我们会听到老手说:python下想要充分利用多核CPU,就用多进程,原因是什么呢?

原因是:每个进程有各自独立的GIL,互不干扰,这样就可以真正意义上的并行执行,所以在python中,多进程的执行效率优于多线程(仅仅针对多核CPU而言)

所以在这里说结论:多核下,想做并行提升效率,比较通用的方法是使用多进程,能够有效提高执行效率

>

Python多进程

2017518

21:01

进程的创建-fork

1. 多进程

通过前面的学习已经知道了,可以通过多线程完成多任务的要求,其实只要说到多任务,除了线程之外还有一个能够完成多任务的功能:多进程

2. fork( )

Pythonos模块封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松创建子进程:

    import os

# 注意,fork函数,只在Unix/Linux/Mac上运行,windows不可以
    pid = os.fork()

if pid == 0:
       
print('哈哈1')
   
else:
       
print('哈哈2')

说明:

·       程序执行到os.fork()时,操作系统会创建一个新的进程(子进程),然后复制父进程的所有信息到子进程中

·       然后父进程和子进程都会从fork()函数中得到一个返回值,其进程中这个值一定是0,而父进程中是子进程的 id

Unix/Linux操作系统中,提供了一个fork()系统函数,它非常特殊。

普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。

子进程永远返回0,而父进程返回子进程的ID

这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。

 

 

 

 

 

多进程修改全局变量

2017518

21:02

在学习多线程的时候,提到过,多个线程都是可以访问全局变量的,有时会出现多个线程同时访问,而差生数据错误的情况

本小节,来探究一下多进程中,是否也存在这个问题

#coding=utf-8
import os
import time

num = 0

# 注意,fork函数,只在Unix/Linux/Mac上运行,windows不可以
    pid = os.fork()

if pid == 0:
       
num+=1
        print('哈哈1---num=%d'%num)
   
else:
       
time.sleep(1)
       
num+=1
        print('哈哈2---num=%d'%num)

运行结果

总结:

·       多进程中,每个进程中所有数据(包括全局变量)都各有拥有一份,互不影响

多次fork问题

2017518

21:02

如果在一个程序,有2次的fork函数调用,是否就会有3个进程呢?


    #coding=utf-8
    import os
   
import time

# 注意,fork函数,只在Unix/Linux/Mac上运行,windows不可以
    pid = os.fork()
    if pid == 0:
       
print('哈哈1')
   
else:
       
print('哈哈2')

pid = os.fork()
   
if pid == 0:
       
print('哈哈3')
   
else:
       
print('哈哈4')

time.sleep(1)

运行结果: 

 

 

 

 

 

获取进程号

2017518

21:02

Ubuntu中获取PID

操作系统为了更好的有序的管理每个运行的进程,需要对每个进行进行分配一个号码,这个号码成为进程号(PID)

在类UNIX系统中,查看当前系统中有哪些进程正常运行,ps-aux

Python中获取PID的方法

获取当前进程的pid的方法为:getpid()

获取父进程的pid的方法为:getpid()

    #coding=utf-8
    import os
   
import time

# 注意,fork函数,只在Unix/Linux/Mac上运行,windows不可以
    pid = os.fork()
    if pid == 0:
       
print('哈哈1---pid = %d---ppid = %d----'%(os.getpid(), os.getppid()))
   
else:
       
print('哈哈2---pid = %d---'%os.getpid())

time.sleep(1)

 

 

 

    #coding=utf-8
    import os
   
import time

# 注意,fork函数,只在Unix/Linux/Mac上运行,windows不可以
    pid = os.fork()
    if pid == 0:
       
print('哈哈1---pid = %d---'%os.getpid())
   
else:
       
print('哈哈2---pid = %d---'%os.getpid())

pid = os.fork()
   
if pid == 0:
       
print('哈哈3---pid = %d---'%os.getpid())
   
else:
       
print('哈哈4---pid = %d---'%os.getpid())

time.sleep(1)

subprocess标准子进程

2017522

23:39

subprocess以及常用的封装函数

当我们运行python的时候,我们都是在创建并运行一个进程。

在Python中,我们通过标准库中的subprocess包来fork一个子进程,并运行一个外部的程序。

subprocess包中定义有数个创建子进程的函数,这些函数分别以不同的方式创建子进程,所以我们可以根据需要来从中选取一个使用。

另外subprocess还提供了一些管理标准流(standard stream)和管道(pipe)的工具,从而在进程间使用文本通信。

使用subprocess包中的函数创建子进程的时候,要注意:

1) 在创建子进程之后,父进程是否暂停,并等待子进程运行。

2) 函数返回什么

3) 当returncode不为0时,父进程如何处理。

subprocess.call()

父进程等待子进程完成

返回退出信息(returncode,相当于exitcode)

subprocess.check_call()

父进程等待子进程完成

返回0

检查退出信息,如果returncode不为0,则举出错误subprocess.CalledProcessError,

该对象包含有returncode属性,可用try…except…来检查。

subprocess.check_output()

父进程等待子进程完成

返回子进程向标准输出的输出结果

检查退出信息,如果returncode不为0,则举出错误subprocess.CalledProcessError,

该对象包含有returncode属性和output属性,output属性为标准输出的输出结果,可用try…except…来检查。

这三个函数的使用方法相类似,以subprocess.call()来说明:

import subprocess

rc =subprocess.call(["ls","-l"])

将程序名(ls)和所带的参数(-l)一起放在一个表中传递给subprocess.call()

可以通过一个shell来解释一整个字符串:

[python] view plain copy

import subprocess 

import subprocess 

child =subprocess.Popen(["ping","-c","5","www.google.com"]) 

print("parent process") 

[python] view plain copy

out = subprocess.call("ls-l", shell=True) 

out = subprocess.call("cd ..",shell=True) 

使用了shell=True这个参数,这个时候,我们使用一整个字符串,而不是一个表来运行子进程。

Python将先运行一个shell,再用这个shell来解释这整个字符串。

shell命令中有一些是shell的内建命令,这些命令必须通过shell运行,$cd。shell=True允许我们运行这样一些命令。

Popen()

实际上,我们上面的三个函数都是基于Popen()的封装(wrapper)。这些封装的目的在于让我们容易使用子进程。

当我们想要更个性化我们的需求的时候,就要转向Popen类,该类生成的对象用来代表子进程。

与上面的封装不同,Popen对象创建后,主程序不会自动等待子进程完成。

我们必须调用对象的wait()方法,父进程才会等待 (也就是阻塞block):

从运行结果中看到,父进程在开启子进程之后并没有等待child的完成,而是直接运行print。

对比等待的情况:

[python] view plain copy

import subprocess 

child =subprocess.Popen(["ping","-c","5","www.google.com"]) 

child.wait() 

print("parent process") 

此外,你还可以在父进程中对子进程进行其它操作,比如我们上面例子中的child对象:

child.poll()           # 检查子进程状态

child.kill()           # 终止子进程

child.send_signal()    # 向子进程发送信号

child.terminate()      # 终止子进程

子进程的PID存储在child.pid

子进程的文本流控制

子进程的标准输入,标准输出和标准错误也可以通过如下属性表示:

child.stdin

child.stdout

child.stderr

我们可以在Popen()建立子进程的时候改变标准输入、标准输出和标准错误,

并可以利用subprocess.PIPE将多个子进程的输入和输出连接在一起,构成管道(pipe):

[python] view plain copy

import subprocess 

child1 =subprocess.Popen(["ls","-l"], stdout=subprocess.PIPE) 

child2 =subprocess.Popen(["wc"],stdin=child1.stdout,stdout=subprocess.PIPE) 

out = child2.communicate() 

print(out) 

subprocess.PIPE实际上为文本流提供一个缓存区。

child1的stdout将文本输出到缓存区,随后child2的stdin从该PIPE中将文本读取走。

child2的输出文本也被存放在PIPE中,直到communicate()方法从PIPE中读取出PIPE中的文本。

要注意的是,communicate()是Popen对象的一个方法,该方法会阻塞父进程,直到子进程完成。

我们还可以利用communicate()方法来使用PIPE给子进程输入:

[python] view plain copy

importsubprocess 

child =subprocess.Popen(["cat"], stdin=subprocess.PIPE) 

child.communicate("vamei") 

我们启动子进程之后,cat会等待输入,直到我们用communicate()输入”vamei”。

通过使用subprocess包,我们可以运行外部程序。这极大的拓展了Python的功能。

如果你已经了解了操作系统的某些应用,你可以从Python中直接调用该应用(而不是完全依赖Python),并将应用的结果输出给Python,并让Python继续处理。

shell的功能(比如利用文本流连接各个应用),就可以在Python中实现。

Python-signal信号

2017522

23:28

signal包负责在Python程序内部处理信号,典型的操作包括预设信号处理函数,暂停并等待信号,以及定时发出SIGALRM等。

要注意,signal包主要是针对UNIX平台(比如Linux,MAC OS),而Windows内核中由于对信号机制的支持不充分,

所以在Windows上的Python不能发挥信号系统的功能。

定义信号名

signal包定义了各个信号名及其对应的整数,比如

[python] view plain copy

import signal 

print signal.SIGALRM 

print signal.SIGCONT 

Python所用的信号名和Linux一致。可以通过

$man 7 signal

查询

预设信号处理函数

signal包的核心是使用signal.signal()函数来预设(register)信号处理函数,如下所示:

singnal.signal(signalnum, handler)

signalnum为某个信号,handler为该信号的处理函数。

我们在信号基础里提到,进程可以无视信号,可以采取默认操作,还可以自定义操作。

当handler为signal.SIG_IGN时,信号被无视(ignore)。

当handler为singal.SIG_DFL,进程采取默认操作(default)。

当handler为一个函数名时,进程采取函数中定义的操作。

在主程序中,首先使用signal.signal()函数来预设信号处理函数。

然后我们执行signal.pause()来让该进程暂停以等待信号,以等待信号。

当信号SIGUSR1被传递给该进程时,进程从暂停中恢复,并根据预设,执行SIGTSTP的信号处理函数myHandler()。

myHandler的两个参数一个用来识别信号(signum),另一个用来获得信号发生时,进程栈的状况(stackframe)。

这两个参数都是由signal.singnal()函数来传递的。

importsubprocess

importsignal

defsigint_handler(signum,frame):#SIGINT信号处理函数

print("InsignalSIGINThandler")

signal.signal(signal.SIGINT,sigint_handler)#设置SIGINT信号处理函数

pingP=subprocess.Popen(args="pingwww.sina.com.cn",shell=True)

pingP.wait()#等待子进程完成,后面在这里会被中断

print(pingP.pid)

print(pingP.returncode)

上面的程序可以保存在一个文件中(比如test.py)。我们使用如下方法运行:

$python test.py

以便让进程运行。当程序运行到signal.pause()的时候,进程暂停并等待信号。

此时,通过按下CTRL+Z向该进程发送SIGTSTP信号。

可以看到,进程执行了myHandle()函数,随后返回主程序,继续执行。

当然,也可以用$ps查询processID, 再使用$kill来发出信号。

进程并不一定要使用signal.pause()暂停以等待信号,它也可以在进行工作中接受信号,

比如将上面的signal.pause()改为一个需要长时间工作的循环。

可以根据自己的需要更改myHandler()中的操作,以针对不同的信号实现个性化的处理。

import signal 

# Define signalhandler function 

def myHandler(signum,frame): 

    print("Now,it's time") 

 

# Registersignal.SIGTSTP's handler 

signal.signal(signal.SIGALRM,myHandler) 

signal,alarm(5) 

while True: 

    print("End") 

这里用了一个无限循环以便让进程持续运行。

在signal.alarm()执行5秒之后,进程将向自己发出SIGALRM信号,随后,信号处理函数myHandler开始执行。

发送信号

signal包的核心是设置信号处理函数。

除了signal.alarm()向自身发送信号之外,并没有其他发送信号的功能。

但在os包中,有类似于linux的kill命令的函数,分别为

os.kill(pid, sid)

os.killpg(pgid, sid)

分别向进程和进程组(见Linux进程关系)发送信号。sid为信号所对应的整数或者singal.SIG*。

实际上signal, pause,kill和alarm都是Linux应用编程中常见的C库函数,在这里,我们只不过是用Python语言来实现了一下。

实际上,Python 的解释器是使用C语言来编写的,所以有此相似性也并不意外。

此外,在Python 3.4中,signal包被增强,信号阻塞等功能被加入到该包中。

multiprocessing

2017518

21:02

如果你打算编写多进程的服务程序,Unix/Linux无疑是正确的选择。由于Windows没有fork调用,难道在Windows上无法用Python编写多进程的程序?

由于Python是跨平台的,自然也应该提供一个跨平台的多进程支持。multiprocessing模块就是跨平台版本的多进程模块。

multiprocessing模块提供了一个Process类来代表一个进程对象,下面的例子演示了启动一个子进程并等待其结束:

    #coding=utf-8
    from multiprocessing import Process
   
import os

# 子进程要执行的代码
    def run_proc(name):
        print('子进程运行中,name= %s ,pid=%d...' % (name, os.getpid()))

if __name__=='__main__':
       
print('父进程 %d.' % os.getpid())
       
p = Process(target=run_proc,args=('test',))
       
print('子进程将要执行')
       
p.start()
        p.join()
        print('子进程已结束')

运行结果

说明

·       创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动,这样创建进程比fork()还要简单。

·       join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。

 multiprocessing  强化

2017522

23:37

我们可以使用subprocess包来创建子进程,但这个包有两个很大的局限性:

1) 我们总是让subprocess运行外部的程序,而不是运行一个Python脚本内部编写的函数。

2) 进程间只通过管道进行文本交流。

以上限制了我们将subprocess包应用到更广泛的多进程任务。

这样的比较实际是不公平的,因为subprocessing本身就是设计成为一个shell,而不是一个多进程管理包。

一 threading和multiprocessing

multiprocessing包是Python中的多进程管理包。

与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程。

该进程可以运行在Python程序内部编写的函数。

该Process对象与Thread对象的用法相同,也有start(), run(), join()的方法。

此外multiprocessing包中也有Lock/Event/Semaphore/Condition类(这些对象可以像多线程那样,通过参数传递给各个进程),用以同步进程,其用法与threading包中的同名类一致。

所以,multiprocessing的很大一部份与threading使用同一套API,只不过换到了多进程的情境。

但在使用这些共享API的时候,我们要注意以下几点:

1)在UNIX平台上,当某个进程终结之后,该进程需要被其父进程调用wait,否则进程成为僵尸进程(Zombie)。

所以,有必要对每个Process对象调用join()方法(实际上等同于wait)。对于多线程来说,由于只有一个进程,所以不存在此必要性。

2)multiprocessing提供了threading包中没有的IPC(比如Pipe和Queue),效率上更高。

应优先考虑Pipe和Queue,避免使用Lock/Event/Semaphore/Condition等同步方式(因为它们占据的不是用户进程的资源)。

3)多进程应该避免共享资源。在多线程中,我们可以比较容易地共享资源,比如使用全局变量或者传递参数。

在多进程情况下,由于每个进程有自己独立的内存空间,以上方法并不合适。此时我们可以通过共享内存和Manager的方法来共享资源。

但这样做提高了程序的复杂度,并因为同步的需要而降低了程序的效率。Process.PID中保存有PID,如果进程还没有start(),则PID为None。

我们可以从下面的程序中看到Thread对象和Process对象在使用上的相似性与结果上的不同。各个线程和进程都做一件事:打印PID。

但问题是,所有的任务在打印的时候都会向同一个标准输出(stdout)输出。这样输出的字符会混合在一起,无法阅读。

使用Lock同步,在一个任务输出完成之后,再允许另一个任务输出,可以避免多个任务同时向终端输出。

[python] view plain copy

# Similarity and difference of multithread vs. multi process 

  

import os 

import threading 

import multiprocessing 

  

# worker function 

def worker(sign, lock): 

   lock.acquire() 

   print(sign, os.getpid()) 

   lock.release() 

  

# Main 

print('Main:',os.getpid()) 

  

# Multi-thread 

record = [] 

lock = threading.Lock() 

for i in range(5): 

   thread = threading.Thread(target=worker,args=('thread',lock)) 

   thread.start() 

   record.append(thread) 

  

for thread in record: 

   thread.join() 

  

# Multi-process 

record = [] 

lock = multiprocessing.Lock() 

for i in range(5): 

   process =multiprocessing.Process(target=worker,args=('process',lock)) 

   process.start() 

   record.append(process) 

  

for process in record: 

   process.join() 

所有Thread的PID都与主程序相同,而每个Process都有一个不同的PID。

二 Pipe和Queue

管道PIPE和消息队列messagequeue,multiprocessing包中有Pipe类和Queue类来分别支持这两种IPC机制。Pipe和Queue可以用来传送常见的对象。

1) Pipe可以是单向(half-duplex),也可以是双向(duplex)。

我们通过mutiprocessing.Pipe(duplex=False)创建单向管道 (默认为双向)。

一个进程从PIPE一端输入对象,然后被PIPE另一端的进程接收,单向管道只允许管道一端的进程输入,而双向管道则允许从两端输入。

下面的程序展示了Pipe的使用:

[python] view plain copy

# Multiprocessing with Pipe 

  

import multiprocessing as mul 

  

def proc1(pipe): 

   pipe.send('hello') 

   print('proc1 rec:',pipe.recv()) 

  

def proc2(pipe): 

   print('proc2 rec:',pipe.recv()) 

   pipe.send('hello, too') 

  

# Build a pipe 

pipe = mul.Pipe() 

  

# Pass an end of the pipe to process1 

p1  = mul.Process(target=proc1, args=(pipe[0],)) 

# Pass the other end of the pipe toprocess 2 

p2  = mul.Process(target=proc2, args=(pipe[1],)) 

p1.start() 

p2.start() 

p1.join() 

p2.join() 

这里的Pipe是双向的。

Pipe对象建立的时候,返回一个含有两个元素的表,每个元素代表Pipe的一端(Connection对象)。

我们对Pipe的某一端调用send()方法来传送对象,在另一端使用recv()来接收。

2) Queue与Pipe相类似,都是先进先出的结构。但Queue允许多个进程放入,多个进程从队列取出对象。

Queue使用mutiprocessing.Queue(maxsize)创建,maxsize表示队列中可以存放对象的最大数量。

下面的程序展示了Queue的使用:

[python] view plain copy

import os 

import multiprocessing 

import time 

#================== 

# input worker 

def inputQ(queue): 

   info = str(os.getpid()) + '(put):' + str(time.time()) 

   queue.put(info) 

  

# output worker 

def outputQ(queue,lock): 

   info = queue.get() 

   lock.acquire() 

   print (str(os.getpid()) + '(get):' + info) 

   lock.release() 

#=================== 

# Main 

record1 = []   # store input processes 

record2 = []   # store output processes 

lock = multiprocessing.Lock()    # Toprevent messy print 

queue = multiprocessing.Queue(3) 

  

# input processes 

for i in range(10): 

   process = multiprocessing.Process(target=inputQ,args=(queue,)) 

   process.start() 

   record1.append(process) 

  

# output processes 

for i in range(10): 

   process = multiprocessing.Process(target=outputQ,args=(queue,lock)) 

   process.start() 

   record2.append(process) 

  

for p in record1: 

   p.join() 

  

queue.close()  # No more object will come, close thequeue 

  

for p in record2: 

   p.join() 

一些进程使用put()在Queue中放入字符串,这个字符串中包含PID和时间。

另一些进程从Queue中取出,并打印自己的PID以及get()的字符串。

三 进程池

进程池 (ProcessPool)可以创建多个进程。这些进程就像是随时待命的士兵,准备执行任务(程序)。一个进程池中可以容纳多个待命的士兵。

比如下面的程序:

[python] view plain copy

import multiprocessing as mul 

  

def f(x): 

   return x**2 

  

pool = mul.Pool(5) 

rel = pool.map(f,[1,2,3,4,5,6,7,8,9,10]) 

print(rel) 

我们创建了一个容许5个进程的进程池 (Process Pool) 。Pool运行的每个进程都执行f()函数。

我们利用map()方法,将f()函数作用到表的每个元素上。这与built-in的map()函数类似,只是这里用5个进程并行处理。

如果进程运行结束后,还有需要处理的元素,那么的进程会被用于重新运行f()函数。除了map()方法外,Pool还有下面的常用方法。

1)apply_async(func,args)从进程池中取出一个进程执行func,args为func的参数。

它将返回一个AsyncResult的对象,你可以对该对象调用get()方法以获得结果。

2)close()进程池不再创建新的进程

3)join()wait进程池中的全部进程。必须对Pool先调用close()方法才能join。

四 共享资源

多进程共享资源必然会带来进程间相互竞争。而这种竞争又会造成racecondition,我们的结果有可能被竞争的不确定性所影响。

但如果需要,我们依然可以通过共享内存和Manager对象这么做。

1 共享内存

根据共享内存(sharedmemory)的原理,这里给出用Python实现的例子:

[python] view plain copy

import multiprocessing 

  

def f(n, a): 

   n.value   = 3.14 

   a[0]      = 5 

  

num  = multiprocessing.Value('d', 0.0) 

arr  = multiprocessing.Array('i', range(10)) 

  

p = multiprocessing.Process(target=f,args=(num, arr)) 

p.start() 

p.join() 

  

print num.value 

print arr[:] 

这里我们实际上只有主进程和Process对象代表的进程。

我们在主进程的内存空间中创建共享的内存,也就是Value和Array两个对象。对象Value被设置成为双精度数(d), 并初始化为0.0。

而Array则类似于C中的数组,有固定的类型(i, 也就是整数)。在Process进程中,我们修改了Value和Array对象。

回到主程序,打印出结果,主程序也看到了两个对象的改变,说明资源确实在两个进程之间共享。

2 Manager

Manager对象类似于服务器与客户之间的通信(server-client),与我们在Internet上的活动很类似。

我们用一个进程作为服务器,建立Manager来真正存放资源。其它的进程可以通过参数传递或者根据地址来访问Manager,建立连接后,操作服务器上的资源。

在防火墙允许的情况下,我们完全可以将Manager运用于多计算机,从而模仿了一个真实的网络情境。

下面的例子中,我们对Manager的使用类似于shared memory,但可以共享更丰富的对象类型。

[python] view plain copy

import multiprocessing 

  

def f(x, arr, l): 

   x.value = 3.14 

   arr[0] = 5 

   l.append('Hello') 

  

server =multiprocessing.Manager() 

x   = server.Value('d', 0.0) 

arr = server.Array('i', range(10)) 

l   = server.list() 

  

proc =multiprocessing.Process(target=f, args=(x, arr, l)) 

proc.start() 

proc.join() 

  

print(x.value) 

print(arr) 

print(l) 

Manager利用list()方法提供了表的共享方式。

实际上你可以利用dict()来共享词典,Lock()来共享threading.Lock(注意,我们共享的是threading.Lock,而不是进程的mutiprocessing.Lock。后者本身已经实现了进程共享)等。

这样Manager就允许我们共享更多样的对象。

进程间通信

2017518

21:07

进程间通信

Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。

Pythonmultiprocessing模块包装了底层的机制,提供了QueuePipes等多种方式来交换数据。

我们以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:

    from multiprocessing import Process, Queue
   
import os, time, random

# 写数据进程执行的代码:
    def write(q):
        for value in ['A', 'B', 'C']:
           
print 'Put %s to queue...' % value
           
q.put(value)
            time.sleep(random.random())

# 读数据进程执行的代码:
    def read(q):
        while True:
           
if not q.empty():
               
value = q.get(True)
               
print 'Get %s from queue.' % value
               
time.sleep(random.random())
            else:
               
break

if __name__=='__main__':
       
# 父进程创建Queue,并传给各个子进程:
        q = Queue()
        pw = Process(target=write,args=(q,))
        pr = Process(target=read,args=(q,))
        # 启动子进程pw,写入:
        pw.start()   
        # 等待pw结束:
        pw.join()
        # 启动子进程pr,读取:
        pr.start()
        pr.join()
        # pr进程里是死循环,无法等待其结束,只能强行终止:
        print
        print '所有数据都写入并且读完'

 

总结

·       进程间通信是通过Queue、Pipes等实现的。

进程间通信-pipe

2017518

21:07

进程间通信-pipe

    #coding:utf-8
    import multiprocessing
   
import time

def proc1(pipe):
        # 发送
        for i in xrange(5):
           
print "proc1 发送 %s"%i
           
pipe.send(i)
            time.sleep(1)

#for i in xrange(5):
        #   print 'proc1 接收:',pipe.recv()

def proc2(pipe):
        # 接收
        for i in xrange(5):
           
print 'proc2 接收:',pipe.recv()

#for i in xrange(10,15):
        #   print "proc2 发送 %s"%i
        #   pipe.send(i)
        #   time.sleep(1)

# 创建一个管道
    pipe = multiprocessing.Pipe()

# 把管道的一端传递给一个线程
    p1 =multiprocessing.Process(target=proc1, args=(pipe[0],))
   
# 把管道的另外一端传递给另外一个线程
    p2 =multiprocessing.Process(target=proc2, args=(pipe[1],))

p1.start()
   
p2.start()
    p1.join()
    p2.join()

进程与线程的区别

2017523

8:53

引入进程和线程的概念及区别

1、线程的基本概念

概念

线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

************************

好处

·       1)易于调度。

·       2)提高并发性。通过线程可方便有效地实现并发性。进程可创建多个线程来执行同一程序的不同部分。

·       3)开销少。创建线程比创建进程要快,所需开销很少

2、进程的基本状态及状态之间的关系

状态:运行、阻塞、挂起阻塞、就绪、挂起就绪

状态之间的转换:

·       1)准备就绪的进程,被CPU调度执行,变成运行态;

·       2)运行中的进程,进行I/O请求或者不能得到所请求的资源,变成阻塞态;

·       3)运行中的进程,进程执行完毕(或时间片已到),变成就绪态;

·       4)将阻塞态的进程挂起,变成挂起阻塞态,当导致进程阻塞的I/O操作在用户重启进程前完成(称之为唤醒),挂起阻塞态变成挂起就绪态,当用户在I/O操作结束之前重启进程,挂起阻塞态变成阻塞态;

·       5)将就绪(或运行)中的进程挂起,变成挂起就绪态,当该进程恢复之后,挂起就绪态变成就绪态;

3、线程和进程的关系以及区别?

**进程和线程的关系:**

·       1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。

·       2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。

·       3)处理机分给线程,即真正在处理机上运行的是线程

·       4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。线程是指进程内的一个执行单元,也是进程内的可调度实体.

进程与线程的区别:

·       1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位

·       2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行

·       3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.

·       4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。

4、进程间通信的方式?

·       1)管道(pipe)及有名管道(named pipe):管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。

·       2)信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的。

·       3)消息队列(message queue):消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。

·       4)共享内存(shared memory):可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。

·       5)信号量(semaphore):主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。

·       6)套接字(socket):这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。

5、同步和互斥的区别:

·       当有多个线程的时候,经常需要去同步这些线程以访问同一个数据或资源。例如,假设有一个程序,其中一个线程用于把文件读到内存,而另一个线程用于统计文件中的字符数。当然,在把整个文件调入内存之前,统计它的计数是没有意义的。但是,由于每个操作都有自己的线程,操作系统会把两个线程当作是互不相干的任务分别执行,这样就可能在没有把整个文件装入内存时统计字数。为解决此问题,你必须使两个线程同步工作。

·       所谓同步,是指散步在不同进程之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。如果用对资源的访问来定义的话,同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。

·       所谓互斥,是指散布在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它们之中的任一程序片段,只能等到该进程运行完这个程序片段后才可以运行。如果用对资源的访问来定义的话,互斥某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

进程VS线程

2017518

21:08

进程VS线程

我们介绍了多进程和多线程,这是实现多任务最常用的两种方式。现在,我们来讨论一下这两种方式的优缺点。

首先,要实现多任务,通常我们会设计Master-Worker模式,Master负责分配任务,Worker负责执行任务,因此,多任务环境下,通常是一个Master,多个Worker

如果用多进程实现Master-Worker,主进程就是Master,其他进程就是Worker

如果用多线程实现Master-Worker,主线程就是Master,其他线程就是Worker

多进程模式最大的优点就是稳定性高,因为一个子进程崩溃了,不会影响主进程和其他子进程。(当然主进程挂了所有进程就全挂了,但是Master进程只负责分配任务,挂掉的概率低)著名的Apache最早就是采用多进程模式。

多进程模式的缺点是创建进程的代价大,在Unix/Linux系统下,用fork调用还行,在Windows下创建进程开销巨大。另外,操作系统能同时运行的进程数也是有限的,在内存和CPU的限制下,如果有几千个进程同时运行,操作系统连调度都会成问题。

多线程模式通常比多进程快一点,但是也快不到哪去,而且,多线程模式致命的缺点就是任何一个线程挂掉都可能直接造成整个进程崩溃,因为所有线程共享进程的内存。在Windows上,如果一个线程执行的代码出了问题,你经常可以看到这样的提示:该程序执行了非法操作,即将关闭,其实往往是某个线程出了问题,但是操作系统会强制结束整个进程。

Windows下,多线程的效率比多进程要高,所以微软的IIS服务器默认采用多线程模式。由于多线程存在稳定性的问题,IIS的稳定性就不如Apache。为了缓解这个问题,IISApache现在又有多进程+多线程的混合模式,真是把问题越搞越复杂。

线程切换

无论是多进程还是多线程,只要数量一多,效率肯定上不去,为什么呢?

我们打个比方,假设你不幸正在准备中考,每天晚上需要做语文、数学、英语、物理、化学这5科的作业,每项作业耗时1小时。

如果你先花1小时做语文作业,做完了,再花1小时做数学作业,这样,依次全部做完,一共花5小时,这种方式称为单任务模型,或者批处理任务模型。

假设你打算切换到多任务模型,可以先做1分钟语文,再切换到数学作业,做1分钟,再切换到英语,以此类推,只要切换速度足够快,这种方式就和单核CPU执行多任务是一样的了,以幼儿园小朋友的眼光来看,你就正在同时写5科作业。

但是,切换作业是有代价的,比如从语文切到数学,要先收拾桌子上的语文书本、钢笔(这叫保存现场),然后,打开数学课本、找出圆规直尺(这叫准备新环境),才能开始做数学作业。操作系统在切换进程或者线程时也是一样的,它需要先保存当前执行的现场环境(CPU寄存器状态、内存页等),然后,把新任务的执行环境准备好(恢复上次的寄存器状态,切换内存页等),才能开始执行。这个切换过程虽然很快,但是也需要耗费时间。如果有几千个任务同时进行,操作系统可能就主要忙着切换任务,根本没有多少时间去执行任务了,这种情况最常见的就是硬盘狂响,点窗口无反应,系统处于假死状态。

所以,多任务一旦多到一个限度,就会消耗掉系统所有的资源,结果效率急剧下降,所有任务都做不好。

计算密集型 vs. IO密集型

是否采用多任务的第二个考虑是任务的类型。我们可以把任务分为计算密集型和IO密集型。

计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。

计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。

第二种任务的类型是IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。

IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。

异步IO

考虑到CPUIO之间巨大的速度差异,一个任务在执行的过程中大部分时间都在等待IO操作,单进程单线程模型会导致别的任务无法并行执行,因此,我们才需要多进程模型或者多线程模型来支持多任务并发执行。

现代操作系统对IO操作已经做了巨大的改进,最大的特点就是支持异步IO。如果充分利用操作系统提供的异步IO支持,就可以用单进程单线程模型来执行多任务,这种全新的模型称为事件驱动模型,Nginx就是支持异步IOWeb服务器,它在单核CPU上采用单进程模型就可以高效地支持多任务。在多核CPU上,可以运行多个进程(数量与CPU核心数相同),充分利用多核CPU。由于系统总的进程数量十分有限,因此操作系统调度非常高效。用异步IO编程模型来实现多任务是一个主要的趋势。

对应到Python语言,单进程的异步编程模型称为协程,有了协程的支持,就可以基于事件驱动编写高效的多任务程序。我们会在后面讨论如何编写协程。

进程线程区别

2017523

8:50

线程和进程的区别

·       [x] 进程:对各种资源管理的集合

·       [x] 线程:操作系统最小的调度单位,是一串指令的集合

进程不能单独执行,只是资源的集合 

进程要操作CPU,必须要先创建一个线程。 

所有在同一个进程里的线程,是同享同一块内存空间的

< 关系

进程中第一个线程是主线程,主线程创建其他线程,其他线程也可以创建线程,线程之间是平等的 

进程有父进程、子进程,独立的内存空间,唯一的进程标识符、pid

< 速度

启动线程比启动进程快。运行进程和运行线程速度上是一样的,没有可比性。 

线程共享内存空间,进程的内存是独立的

< 创建

父进程生成子进程,相当于克隆一份内存空间。进程直接不能直接访问 

创建新线程很简单,创建新进程需要对其父进程进行一次克隆 

一个线程可以控制和操作同一线程里的其他线程,但是进程只能操作子进程

< 交互

同一个进程之间的线程之间可以直接交流 

两个进程想通信必须通过一个中间代理来实现。

Poll进程池

2017518

21:08

Poll

如果要启动大量的子进程,可以用进程池的方式批量创建子进程:

from multiprocessing import Pool
import os, time, random

def long_time_task(name):
   
print('Run task %s (%s)...' % (name,os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print('Task %s runs %0.2f seconds.' %(name, (end - start)))

if __name__=='__main__':
   
print('Parent process %s.' %os.getpid())
    p = Pool(4)
    for i in range(5):
        p.apply_async(long_time_task,args=(i,))
    print('Waiting for all subprocessesdone...')
    p.close()
    p.join()
    print('All subprocesses done.')

运行结果:

Parent process 669.
Waiting for all subprocesses done...
Run task 0 (671)...
Run task 1 (672)...
Run task 2 (673)...
Run task 3 (674)...
Task 2 runs 0.14 seconds.
Run task 4 (673)...
Task 1 runs 0.27 seconds.
Task 3 runs 0.86 seconds.
Task 0 runs 1.41 seconds.
Task 4 runs 1.91 seconds.
All subprocesses done.

代码解读:

Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。

请注意输出的结果,task 0123是立刻执行的,而task 4要等待前面某个task完成后才执行,这是因为Pool的默认大小在我的电脑上是4,因此,最多同时执行4个进程。这是Pool有意设计的限制,并不是操作系统的限制。如果改成:

p = Pool(5)

就可以同时跑5个进程。

由于Pool的默认大小是CPU的核数,如果你不幸拥有8核CPU,你要提交至少9个子进程才能看到上面的等待效果。

多进程multiprocessing小结

2017522

23:41

多进程multiprocessing

multiprocessing is a package thatsupports spawning processes using an API similar to the threading module. Themultiprocessing package offers both local and remote concurrency, effectivelyside-stepping the Global Interpreter Lock by using subprocesses instead ofthreads. Due to this, the multiprocessing module allows the programmer to fullyleverage multiple processors on a given machine. It runs on both Unix andWindows.

from multiprocessing import Process

import time

def f(name):

   time.sleep(2)

   print('hello', name)

if __name__ == '__main__':

   p = Process(target=f, args=('bob',))

   p.start()

   p.join()

 注意:由于进程之间的数据需要各自持有一份,所以创建进程需要的非常大的开销。

To show the individual process IDsinvolved, here is an expanded example:

from multiprocessing import Process

import os

def info(title):

   print(title)

   print('module name:', __name__)

   print('parent process:', os.getppid())

   print('process id:', os.getpid())

   print("\n\n")

def f(name):

   info('\033[31;1mfunction f\033[0m')

   print('hello', name)

if __name__ == '__main__':

   info('\033[32;1mmain process line\033[0m')

    p =Process(target=f, args=('bob',))

   p.start()

   p.join()

进程间通讯  

不同进程间内存是不共享的,要想实现两个进程间的数据交换,可以用以下方法:

Queues

使用方法跟threading里的queue差不多

from multiprocessing import Process,Queue

def f(q):

   q.put([42, None, 'hello'])

if __name__ == '__main__':

   q = Queue()

   p = Process(target=f, args=(q,))

   p.start()

   print(q.get())    # prints"[42, None, 'hello']"

   p.join()

Pipes

The Pipe() function returns a pair ofconnection objects connected by a pipe which by default is duplex (two-way).For example:

from multiprocessing import Process,Pipe

def f(conn):

   conn.send([42, None, 'hello'])

   conn.close()

if __name__ == '__main__':

   parent_conn, child_conn = Pipe()

   p = Process(target=f, args=(child_conn,))

   p.start()

   print(parent_conn.recv())   #prints "[42, None, 'hello']"

   p.join()

The two connection objects returned byPipe() represent the two ends of the pipe. Each connection object has send()and recv() methods (among others). Note that data in a pipe may becomecorrupted if two processes (or threads) try to read from or write to the sameend of the pipe at the same time. Of course there is no risk of corruption fromprocesses using different ends of the pipe at the same time.

Managers

A manager object returned by Manager()controls a server process which holds Python objects and allows other processesto manipulate them using proxies.

A manager returned by Manager() willsupport types list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore,Condition, Event, Barrier, Queue, Value and Array. For example,

#没有共享数据

from multiprocessing import Process

import time

li = []

def foo(i):

   li.append(i)

   print(''say hi",li)

if __name__=='__main__':

   for i in range(10):

       p=Process(target=foo,args=(i,))

       p.start()

print('ending',li)

#方法一:Array

from multiprocessing importProcess,Array

temp = Array('i', [11,22,33,44])

def Foo(i):

   temp[i] = 100+i

   for item in temp:

       print i,'----->',item

if __name__=='__main__':

   for i in range(2):

       p = Process(target=Foo,args=(i,))

       p.start()

#方法二:manage.dict()共享数据

from multiprocessing import Process,Manager

def f(d, l):

   d[1] = '1'

   d['2'] = 2

   d[0.25] = None

   l.append(1)

   print(l)

if __name__ == '__main__':

   with Manager() as manager:

       d = manager.dict()

       l = manager.list(range(5))

       p_list = []

       for i in range(10):

            p = Process(target=f, args=(d, l))

            p.start()

            p_list.append(p)

       for res in p_list:

            res.join()

       print(d)

 当创建进程时(非使用时),共享数据会被拿到子进程中,当进程中执行完毕后,再赋值给原值。

进程锁实例

from multiprocessing import Process,Array, RLock

def Foo(lock,temp,i):

   """

   将第0个数加100

   """

   lock.acquire()

   temp[0] = 100+i

   for item in temp:

       print(i,'----->',item)

   lock.release()

lock = RLock()

temp = Array('i', [11, 22, 33, 44])

if __name__=='__main__':

   for i in range(20):

       p = Process(target=Foo,args=(lock,temp,i,))

       p.start()

进程同步

Without using the lock output from thedifferent processes is liable to get all mixed up.

from multiprocessing import Process,Lock

def f(l, i):

   l.acquire()

   try:

       print('hello world', i)

   finally:

       l.release()

if __name__ == '__main__':

   lock = Lock()

   for num in range(10):

       Process(target=f, args=(lock, num)).start()

  

进程池  

进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。

进程池中有两个方法:

apply

apply_async

#!/usr/bin/env python

# -*- coding:utf-8 -*-

from multiprocessing import Process,Pool

import time

def Foo(i):

   time.sleep(5)

   print('第%s次'%i)

   return i+100

def Bar(arg):

   print('what-->',arg)

#print pool.apply(Foo,(1,))

#print pool.apply_async(func =Foo,args=(1,)).get()

if __name__=='__main__':

   pool = Pool(5)  #创建5个有进程的进程池

   for i in range(10):

       pool.apply_async(func=Foo, args=(i,),callback=Bar) #callback是回调

   print('end')

   pool.close() #先写close,再写join

   pool.join()#进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。

Linux进程指令大全

2017523

8:37

 linux 查进程、杀进程、起进程

1.查进程

   ps命令查找与进程相关的PID号:

   ps a 显示现行终端机下的所有程序,包括其他用户的程序。

   ps -A 显示所有程序。

   ps c 列出程序时,显示每个程序真正的指令名称,而不包含路径,参数或常驻服务的标示。

   ps -e 此参数的效果和指定"A"参数相同。

   ps e 列出程序时,显示每个程序所使用的环境变量。

   ps f 用ASCII字符显示树状结构,表达程序间的相互关系。

   ps -H 显示树状结构,表示程序间的相互关系。

   ps -N 显示所有的程序,除了执行ps指令终端机下的程序之外。

   ps s 采用程序信号的格式显示程序状况。

   ps S 列出程序时,包括已中断的子程序资料。

   ps -t<终端机编号> 指定终端机编号,并列出属于该终端机的程序的状况。

   ps u 以用户为主的格式来显示程序状况。

   ps x 显示所有程序,不以终端机来区分。

   

   最常用的方法是ps aux,然后再通过管道使用grep命令过滤查找特定的进程,然后再对特定的进程进行操作。

   ps aux | grep program_filter_word,ps -ef |grep tomcat

ps -ef|grepjava|grep -v grep 显示出所有的java进程,去处掉当前的grep进程。

   

2.杀进程

  使用kill命令结束进程:kill xxx

  常用:kill -9 324

  Linux下还提供了一个killall命令,可以直接使用进程的名字而不是进程标识号,例如:# killall -9 NAME

3.进入到进程的执行文件所在的路径下,执行文件 ./文件名

Linux进程状态

2017523

8:42

Linux中的ps命令是ProcessStatus的缩写。ps命令用来列出系统中当前运行的那些进程。ps命令列出的是当前那些进程的快照,就是执行ps命令的那个时刻的那些进程,如果想要动态的显示进程信息,就可以使用top命令。要对进程进行监测和控制,首先必须要了解当前进程的情况,也就是需要查看当前进程,而 ps 命令就是最基本同时也是非常强大的进程查看命令。使用该命令可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等。总之大部分信息都是可以通过执行该命令得到的。

ps 为我们提供了进程的一次性的查看,它所提供的查看结果并不动态连续的;如果想对进程时间监控,应该用 top 工具。

kill 命令用于杀死进程。

linux上进程有5种状态:

1. 运行(正在运行或在运行队列中等待)

2. 中断(休眠中,受阻,在等待某个条件的形成或接受到信号)

3. 不可中断(收到信号不唤醒和不可运行, 进程必须等待直到有中断发生)

4. 僵死(进程已终止,但进程描述符存在, 直到父进程调用wait4()系统调用后释放)

5. 停止(进程收到SIGSTOP,SIGSTP, SIGTIN, SIGTOU信号后停止运行运行)

ps工具标识进程的5种状态码:

D 不可中断uninterruptible sleep (usually IO)

R 运行 runnable(on run queue)

S 中断 sleeping

T 停止 traced orstopped

Z 僵死 a defunct(”zombie”) process

1.命令格式:

ps[参数]

2.命令功能:

用来显示当前进程的状态

3.命令参数:

a  显示所有进程

-a 显示同一终端下的所有程序

-A 显示所有进程

c  显示进程的真实名称

-N 反向选择

-e 等于“-A”

e  显示环境变量

f  显示程序间的关系

-H 显示树状结构

r  显示当前终端的进程

T  显示当前终端的所有程序

u  指定用户的所有进程

-au 显示较详细的资讯

-aux 显示所有包含其他使用者的行程

-C<命令> 列出指定命令的状况

--lines<行数>每页显示的行数

--width<字符数>每页显示的字符数

--help 显示帮助信息

--version 显示版本显示

4.使用实例

实例1:显示所有进程信息

命令:ps -A

输出:

代码如下:

[root@localhost test6]# ps -A

PID TTY TIME CMD

1 ? 00:00:00 init

2 ? 00:00:01 migration/0

3 ? 00:00:00 ksoftirqd/0

4 ? 00:00:01 migration/1

5 ? 00:00:00 ksoftirqd/1

6 ? 00:29:57 events/0

7 ? 00:00:00 events/1

8 ? 00:00:00 khelper

49 ? 00:00:00 kthread

54 ? 00:00:00 kblockd/0

55 ? 00:00:00 kblockd/1

56 ? 00:00:00 kacpid

217 ? 00:00:00 cqueue/0

说明:

实例2:显示指定用户信息

命令:ps -u root

输出:

代码如下:

[root@localhost test6]# ps -u root

PID TTY TIME CMD

1 ? 00:00:00 init

2 ? 00:00:01 migration/0

3 ? 00:00:00 ksoftirqd/0

4 ? 00:00:01 migration/1

5 ? 00:00:00 ksoftirqd/1

6 ? 00:29:57 events/0

7 ? 00:00:00 events/1

8 ? 00:00:00 khelper

49 ? 00:00:00 kthread

54 ? 00:00:00 kblockd/0

55 ? 00:00:00 kblockd/1

56 ? 00:00:00 kacpid

说明:

实例3:显示所有进程信息,连同命令行

命令:ps -ef

输出:

代码如下:

[root@localhost test6]# ps -ef

UID PID PPID C STIME TTY TIME CMD

root 1 0 0 Nov02 ? 00:00:00 init [3]

root 2 1 0 Nov02 ? 00:00:01[migration/0]

root 3 1 0 Nov02 ? 00:00:00[ksoftirqd/0]

root 4 1 0 Nov02 ? 00:00:01[migration/1]

root 5 1 0 Nov02 ? 00:00:00[ksoftirqd/1]

root 6 1 0 Nov02 ? 00:29:57 [events/0]

root 7 1 0 Nov02 ? 00:00:00 [events/1]

root 8 1 0 Nov02 ? 00:00:00 [khelper]

root 49 1 0 Nov02 ? 00:00:00 [kthread]

root 54 49 0 Nov02 ? 00:00:00[kblockd/0]

root 55 49 0 Nov02 ? 00:00:00[kblockd/1]

root 56 49 0 Nov02 ? 00:00:00 [kacpid]

说明:

实例4:ps 与grep 常用组合用法,查找特定进程

命令:ps -ef|grepssh

输出:

代码如下:

[root@localhost test6]# ps -ef|grep ssh

root 2720 1 0 Nov02 ? 00:00:00/usr/sbin/sshd

root 17394 2720 0 14:58 ? 00:00:00sshd: <a href="mailto:root@pts/0">root@pts/0</a>

root 17465 17398 0 15:57 pts/0 00:00:00grep ssh

说明:

实例5:将目前属于您自己这次登入的 PID 与相关信息列示出来

命令:ps -l

输出:

代码如下:

[root@localhost test6]# ps -l

F S UID PID PPID C PRI NI ADDR SZ WCHANTTY TIME CMD

4 S 0 17398 17394 0 75 0 - 16543 waitpts/0 00:00:00 bash

4 R 0 17469 17398 0 77 0 - 15877 -pts/0 00:00:00 ps

说明:

各相关信息的意义:

F 代表这个程序的旗标 (flag),4 代表使用者为 super user

S 代表这个程序的状态 (STAT),关于各STAT 的意义将在内文介绍

UID 程序被该 UID 所拥有

PID 就是这个程序的 ID !

PPID 则是其上级父程序的ID

C CPU 使用的资源百分比

PRI 这个是 Priority (优先执行序)的缩写,详细后面介绍

NI 这个是 Nice 值,在下一小节我们会持续介绍

ADDR 这个是 kernelfunction,指出该程序在内存的那个部分。如果是个 running的程序,一般就是 "-"

SZ 使用掉的内存大小

WCHAN 目前这个程序是否正在运作当中,若为 - 表示正在运作

TTY 登入者的终端机位置

TIME 使用掉的 CPU 时间。

CMD 所下达的指令为何

在预设的情况下, ps 仅会列出与目前所在的 bash shell 有关的 PID 而已,所以, 当我使用 ps -l 的时候,只有三个 PID。

实例6:列出目前所有的正在内存当中的程序

命令:ps aux

输出:

代码如下:

[root@localhost test6]# ps aux

USER PID %CPU %MEM VSZ RSS TTY STATSTART TIME COMMAND

root 1 0.0 0.0 10368 676 ? Ss Nov020:00 init [3]

root 2 0.0 0.0 0 0 ? S< Nov02 0:01[migration/0]

root 3 0.0 0.0 0 0 ? SN Nov02 0:00[ksoftirqd/0]

root 4 0.0 0.0 0 0 ? S< Nov02 0:01[migration/1]

root 5 0.0 0.0 0 0 ? SN Nov02 0:00[ksoftirqd/1]

root 6 0.0 0.0 0 0 ? S< Nov02 29:57[events/0]

root 7 0.0 0.0 0 0 ? S< Nov02 0:00[events/1]

root 8 0.0 0.0 0 0 ? S< Nov02 0:00[khelper]

root 49 0.0 0.0 0 0 ? S< Nov02 0:00[kthread]

root 54 0.0 0.0 0 0 ? S< Nov02 0:00[kblockd/0]

root 55 0.0 0.0 0 0 ? S< Nov02 0:00[kblockd/1]

root 56 0.0 0.0 0 0 ? S< Nov02 0:00[kacpid]

说明:

USER:该 process 属于那个使用者账号的

PID :该 process 的号码

%CPU:该 process 使用掉的CPU 资源百分比

%MEM:该 process 所占用的物理内存百分比

VSZ :该 process 使用掉的虚拟内存量 (Kbytes)

RSS :该 process 占用的固定的内存量 (Kbytes)

TTY :该 process 是在那个终端机上面运作,若与终端机无关,则显示 ?,另外, tty1-tty6 是本机上面的登入者程序,若为 pts/0 等等的,则表示为由网络连接进主机的程序。

STAT:该程序目前的状态,主要的状态有

R :该程序目前正在运作,或者是可被运作

S :该程序目前正在睡眠当中 (可说是idle 状态),但可被某些讯号 (signal)唤醒。

T :该程序目前正在侦测或者是停止了

Z :该程序应该已经终止,但是其父程序却无法正常的终止他,造成 zombie (疆尸)程序的状态

START:该 process 被触发启动的时间

TIME :该 process 实际使用CPU 运作的时间

COMMAND:该程序的实际指令

实例7:列出类似程序树的程序显示

命令:ps -axjf

输出:

代码如下:

[root@localhost test6]# ps -axjf

Warning: bad syntax, perhaps a bogus'-'? See /usr/share/doc/procps-3.2.7/FAQ

PPID PID PGID SID TTY TPGID STAT UIDTIME COMMAND

0 1 1 1 ? -1 Ss 0 0:00 init [3]

1 2 1 1 ? -1 S< 0 0:01 [migration/0]

1 3 1 1 ? -1 SN 0 0:00 [ksoftirqd/0]

1 4 1 1 ? -1 S< 0 0:01 [migration/1]

1 5 1 1 ? -1 SN 0 0:00 [ksoftirqd/1]

1 6 1 1 ? -1 S< 0 29:58 [events/0]

1 7 1 1 ? -1 S< 0 0:00 [events/1]

1 8 1 1 ? -1 S< 0 0:00 [khelper]

1 49 1 1 ? -1 S< 0 0:00 [kthread]

49 54 1 1 ? -1 S< 0 0:00 \_[kblockd/0]

49 55 1 1 ? -1 S< 0 0:00 \_[kblockd/1]

49 56 1 1 ? -1 S< 0 0:00 \_ [kacpid]

说明:

实例8:找出与cron 与 syslog 这两个服务有关的 PID 号码

命令:ps aux |egrep '(cron|syslog)'

输出:

代码如下:

[root@localhost test6]# ps aux | egrep'(cron|syslog)'

root 2682 0.0 0.0 83384 2000 ? Sl Nov020:00 /sbin/rsyslogd -i /var/run/syslogd.pid -c 5

root 2735 0.0 0.0 74812 1140 ? Ss Nov020:00 crond

root 17475 0.0 0.0 61180 832 pts/0 S+16:27 0:00 egrep (cron|syslog)

[root@localhost test6]#

其他实例:

1. 可以用 | 管道和more 连接起来分页查看

命令:ps -aux|more

2. 把所有进程显示出来,并输出到ps001.txt文件

命令:ps -aux> ps001.txt

3. 输出指定的字段

命令:ps -opid,ppid,pgrp,session,tpgid,comm

输出:

复制代码

代码如下:

[root@localhost test6]# ps -opid,ppid,pgrp,session,tpgid,comm

PID PPID PGRP SESS TPGID COMMAND

17398 17394 17398 17398 17478 bash

17478 17398 17478 17398 17478 ps

[root@localhost test6]#

Python3 网络编程

2017413

11:20

 

Python 提供了两个级别访问的网络服务。:

·       低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口的全部方法。

·       高级别的网络服务模块 SocketServer它提供了服务器中心类,可以简化网络服务器的开发。

什么是 Socket?

Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。

socket()函数

Python 中,我们用 socket()函数来创建套接字,语法格式如下:

socket.socket([family[, type[, proto]]])

参数

·       family: 套接字家族可以使AF_UNIX或者AF_INET

·       type: 套接字类型可以根据是面向连接的还是非连接分为SOCK_STREAMSOCK_DGRAM

·       protocol: 一般不填默认为0.

Socket 对象(内建)方法

函数

描述

服务器端套接字

s.bind()

绑定地址(host,port)到套接字,AF_INET,以元组(host,port)的形式表示地址。

s.listen()

开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。

s.accept()

被动接受TCP客户端连接,(阻塞式)等待连接的到来

客户端套接字

s.connect()

主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。

s.connect_ex()

connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

公共用途的套接字函数

s.recv()

接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。

s.send()

发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。

s.sendall()

完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。

s.recvform()

接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。

s.sendto()

发送UDP数据,将数据发送到套接字,address是形式为(ipaddrport)的元组,指定远程地址。返回值是发送的字节数。

s.close()

关闭套接字

s.getpeername()

返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。

s.getsockname()

返回套接字自己的地址。通常是一个元组(ipaddr,port)

s.setsockopt(level,optname,value)

设置给定套接字选项的值。

s.getsockopt(level,optname[.buflen])

返回套接字选项的值。

s.settimeout(timeout)

设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect()

s.gettimeout()

返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None

s.fileno()

返回套接字的文件描述符。

s.setblocking(flag)

如果flag0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。

s.makefile()

创建一个与该套接字相关连的文件

简单实例

服务端

我们使用 socket 模块的 socket 函数来创建一个 socket 对象。socket 对象可以通过调用其他函数来设置一个 socket 服务。

现在我们可以通过调用 bind(hostname, port) 函数来指定服务的 port(端口)

接着,我们调用 socket 对象的 accept 方法。该方法等待客户端的连接,并返回 connection 对象,表示已连接到客户端。

完整代码如下:

#!/usr/bin/python3
# 文件名:server.py

# 导入 socketsys 模块
importsocket
importsys

# 创建 socket 对象
serversocket = socket.socket(
            socket.AF_INET, socket.SOCK_STREAM)

# 获取本地主机名
host = socket.gethostname()

port =9999

# 绑定端口
serversocket.bind((host, port))

# 设置最大连接数,超过后排队
serversocket.listen(5)

while True:
    #建立客户端连接
    clientsocket,addr = serversocket.accept()     

print("连接地址: %s" % str(addr))
   
    msg='欢迎访问菜鸟教程!'+ "\r\n"
    clientsocket.send(msg.encode('utf-8'))
    clientsocket.close()

客户端

接下来我们写一个简单的客户端实例连接到以上创建的服务。端口号为 12345

socket.connect(hosname, port ) 方法打开一个 TCP 连接到主机为 hostname 端口为 port 的服务商。连接后我们就可以从服务端后期数据,记住,操作完成后需要关闭连接。

完整代码如下:

#!/usr/bin/python3
# 文件名:client.py

# 导入 socketsys 模块
importsocket
importsys

# 创建 socket 对象
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 获取本地主机名
host = socket.gethostname()

# 设置端口好
port = 9999

# 连接服务,指定主机和端口
s.connect((host, port))

# 接收小于 1024 字节的数据
msg = s.recv(1024)

s.close()

print (msg.decode('utf-8'))

现在我们打开两个终端,第一个终端执行 server.py 文件:

$ python3 server.py

第二个终端执行 client.py 文件:

$ python3 client.py
欢迎访问菜鸟教程!

这是我们再打开第一个终端,就会看到有以下信息输出:

连接地址: ('192.168.0.118', 33397)

Python Internet 模块

以下列出了 Python 网络编程的一些重要模块:

协议

功能用处

端口号

Python 模块

HTTP

网页访问

80

httplib, urllib, xmlrpclib

NNTP

阅读和张贴新闻文章,俗称为"帖子"

119

nntplib

FTP

文件传输

20

ftplib, urllib

SMTP

发送邮件

25

smtplib

POP3

接收邮件

110

poplib

IMAP4

获取邮件

143

imaplib

Telnet

命令行

23

telnetlib

Gopher

信息查找

70

gopherlib, urllib

 

SocketServer实现网络并发

2017524

4:51

1.多线程模块

    主要是socketserver模块,如下图示:

2.多线程原理

    如下图示说明:

3.SockteServer例子说明

服务器端:

客户端:

4.演示

    还是以前面例子,对代码进行修改,作如下的演示。

Server端:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

import SocketServer            #导入SocketServer,多线程并发由此类实现

class MySockServer(SocketServer.BaseRequestHandler):    #定义一个类

    def handle(self):      #handle(self)方法是必须要定义的,可以看上面的说明

        print 'Got a new connection from', self.client_address

        while True:

            data = self.request.recv(1024)    #需要通过self的方法调用数据接收函数

            if not data:break

            print 'recv:', data

            self.request.send(data.upper())   #需要通过self的方法调用数据接收函数

if __name__ == '__main__':    #并非一定要用这样的方式,只是建议这样使用

    HOST = ''             #定义侦听本地地址口(多个IP地址情况下),这里表示侦听所有

    PORT = 50007          #Server端开放的服务端口

    s = SocketServer.ThreadingTCPServer((HOST, PORT), MySockServer)

                              #调用SocketServer模块的多线程并发函数

    s.serve_forever()     #持续接受客户端的连接

Client端:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

import socket

HOST = '192.168.1.13'

PORT = 50007

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect((HOST, PORT))

while True:

    user_input = raw_input('msg to send:').strip()

    s.sendall(user_input)

    data = s.recv(1024)

    print 'Received', repr(data)

s.close()

演示:

步骤1:Server端运行服务端程序

1

2

xpleaf@xpleaf-machine:/mnt/hgfs/Python/day5$ python Thread_socket_server4.py 

===>光标在此处处于等待状态

步骤2:ClientA端运行客户端程序

1

2

3

4

5

6

xpleaf@xpleaf-machine:/mnt/hgfs/Python/day5$ python client4.py 

msg to send:Hello!    ===>User输入数据

Received 'HELLO!'     ===>Server端返回的数据

msg to send:I'm Client A.

Received "I'M CLIENT A."

msg to send:          ===>继续等待User输入数据

步骤3:在Server端中观察现象

1

2

3

4

5

xpleaf@xpleaf-machine:/mnt/hgfs/Python/day5$ python Thread_socket_server4.py 

Got a new connection from ('192.168.1.13', 52650)

recv: Hello!        

recv: I'm Client A.    ===>接收到Client A端发送的数据

===>光标在此处处于等待状态

步骤4:Client B端运行客户端程序

1

2

3

4

xpleaf@xpleaf-machine:/mnt/hgfs/Python/day5$ python client4.py 

msg to send:I'm Client B.    ===>User输入数据

Received "I'M CLIENT B."     ===>Server端返回的数据

msg to send:                 ===>继续等待User输入数据

步骤5:在Server端中观察现象

1

2

3

4

5

6

7

xpleaf@xpleaf-machine:/mnt/hgfs/Python/day5$ python Thread_socket_server4.py 

Got a new connection from ('192.168.1.13', 52650)

recv: Hello!

recv: I'm Client A.

Got a new connection from ('192.168.1.13', 52651)

recv: I'm Client B.    ===>接收到Client A端发送的数据

===>光标在此处处于等待状态

    通过上面的演示,使用SocketServer便可以实现PythonSocket的多线程并发。

 

股票数据抓取

2017524

5:30

一、网页源码的获取

     

import urllib.request
url='http://quote.stockstar.com/stock/ranklist_a_3_1_1.html'  #目标网址
headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0;WOW64)"}  #伪装浏览器请求报头
request=urllib.request.Request(url=url,headers=headers)  #请求服务器
response=urllib.request.urlopen(request) #服务器应答
content=response.read().decode('gbk')   #以一定的编码方式查看源码
print(content)  #打印页面源码

     虽说抓一页的源码容易,不过在一个网站内大量抓取网页源码却经常遭到服务器拦截,顿时感觉世界充满了恶意。于是我开始研习突破反爬虫限制的功法。

     1.伪装流浪器报头

     很多服务器通过浏览器发给它的报头来确认是否是人类用户,所以我们可以通过模仿浏览器的行为构造请求报头给服务器发送请求。服务器会识别其中的一些参数来识别你是否是人类用户,很多网站都会识别User-Agent这个参数,所以请求头最好带上。有一些警觉性比较高的网站可能还会通过其他参数识别,比如通过Accept-Language来辨别你是否是人类用户,一些有防盗链功能的网站还得带上referer这个参数等等。

     2.随机生成UA

     证券之星只需带User-Agent这个参数就可以抓取页面信息了,不过连续抓取几页就被服务器阻止了。于是我决定每次抓取数据时模拟不同的浏览器发送请求,而服务器通过User-Agent来识别不同浏览器,所以每次爬取页面可以通过随机生成不同的UA构造报头去请求服务器,

     3.减慢爬取速度

     虽然模拟了不同浏览器爬取数据,但发现有的时间段可以爬取上百页的数据,有时候却只能爬取十来页,看来服务器还会根据你的访问的频率来识别你是人类用户还是网络爬虫。所以我每抓取一页都让它随机休息几秒,加入此句代码后,每个时间段都能爬取大量股票数据了。

     4.使用代理IP

     天有不测风云,程序在公司时顺利测试成功,回寝室后发现又只能抓取几页就被服务器阻止了。惊慌失措的我赶紧询问度娘,获知服务器可以识别你的IP,并记录此IP访问的次数,可以使用高匿的代理IP,并在抓取的过程中不断的更换,让服务器无法找出谁是真凶。此功还未修成,欲知后事如何,请听下回分解。

     5.其他突破反爬虫限制的方法

     很多服务器在接受浏览器请求时会发送一个cookie文件给浏览器,然后通过cookie来跟踪你的访问过程,为了不让服务器识别出你是爬虫,建议最好带上cookie一起去爬取数据;如果遇上要模拟登陆的网站,为了不让自己的账号被拉黑,可以申请大量的账号,然后再爬入,此处涉及模拟登陆、验证码识别等知识,暂时不再深究...总之,对于网站主人来说,有些爬虫确实是令人讨厌的,所以会想出很多方法限制爬虫的进入,所以我们在强行进入之后也得注意些礼仪,别把人家的网站给拖垮了。

二、所需内容的提取

     获取网页源码后,我们就可以从中提取我们所需要的数据了。从源码中获取所需信息的方法有很多,使用正则表达式就是比较经典的方法之一。我们先来看所采集网页源码的部分内容。

     为了减少干扰,我先用正则表达式从整个页面源码中匹配出以上的主体部分,然后从主体部分中匹配出每只股票的信息。代码如下。

pattern=re.compile('<tbody[\s\S]*</tbody>') 
body=re.findall(pattern,str(content))  #匹配<tbody和</tbody>之间的所有代码
pattern=re.compile('>(.*?)<')
stock_page=re.findall(pattern,body[0])  #匹配>和<之间的所有信息

      其中compile方法为编译匹配模式,findall方法用此匹配模式去匹配出所需信息,并以列表的方式返回。正则表达式的语法还挺多的,下面我只罗列所用到符号的含义。

语法

说明

.

匹配任意除换行符“\n”外的字符

*

匹配前一个字符0次或无限次

匹配前一个字符0次或一次

\s

空白字符:[<空格>\t\r\n\f\v]

\S

非空白字符:[^\s]

[...]

字符集,对应的位置可以是字符集中任意字符

(...)

被括起来的表达式将作为分组,里面一般为我们所需提取的内容

     正则表达式的语法挺多的,也许有大牛只要一句正则表达式就可提取我想提取的内容。在提取股票主体部分代码时发现有人用xpath表达式提取显得更简洁一些,看来页面解析也有很长的一段路要走。

三、所得结果的整理

     通过非贪婪模式(.*?)匹配><之间的所有数据,会匹配出一些空白字符出来,所以我们采用如下代码把空白字符移除。

stock_last=stock_total[:] #stock_total:匹配出的股票数据
for data in stock_total:  #stock_last:整理后的股票数据
    if data=='':
        stock_last.remove('')

     最后,我们可以打印几列数据看下效果,代码如下

print('代码','\t','简称','   ','\t','最新价','\t','涨跌幅','\t','涨跌额','\t','5分钟涨幅')
for i in range(0,len(stock_last),13):       #网页总共有13列数据
   print(stock_last[i],'\t',stock_last[i+1],' ','\t',stock_last[i+2],'  ','\t',stock_last[i+3],'  ','\t',stock_last[i+4],'  ','\t',stock_last[i+5])

     打印的部分结果如下

     抓取证券之星上当天所有A股数据的最终程序如下

import urllib
import urllib.request
import re
import random
import time
#抓取所需内容
user_agent = ["Mozilla/5.0 (Windows NT 10.0; WOW64)", 'Mozilla/5.0(Windows NT 6.3; WOW64)',
              'Mozilla/5.0 (Windows NT6.1) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',
              'Mozilla/5.0 (Windows NT6.3; WOW64; Trident/7.0; rv:11.0) like Gecko',
              'Mozilla/5.0 (Windows NT5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36',
              'Mozilla/5.0 (Windows NT6.1; WOW64; Trident/7.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NETCLR 3.0.30729; Media Center PC 6.0; .NET4.0C; rv:11.0) like Gecko)',
              'Mozilla/5.0 (Windows; U;Windows NT 5.2) Gecko/2008070208 Firefox/3.0.1',
              'Mozilla/5.0 (Windows; U;Windows NT 5.1) Gecko/20070309 Firefox/2.0.0.3',
              'Mozilla/5.0 (Windows; U;Windows NT 5.1) Gecko/20070803 Firefox/1.5.0.12',
              'Opera/9.27 (Windows NT 5.2;U; zh-cn)',
              'Mozilla/5.0 (Macintosh;PPC Mac OS X; U; en) Opera 8.0',
              'Opera/8.0 (Macintosh; PPCMac OS X; U; en)',
              'Mozilla/5.0 (Windows; U;Windows NT 5.1; en-US; rv:1.8.1.12) Gecko/20080219 Firefox/2.0.0.12 Navigator/9.0.0.6',
              'Mozilla/4.0 (compatible;MSIE 8.0; Windows NT 6.1; Win64; x64; Trident/4.0)',
              'Mozilla/4.0 (compatible;MSIE 8.0; Windows NT 6.1; Trident/4.0)',
              'Mozilla/5.0 (compatible;MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0; SLCC2; .NET CLR 2.0.50727; .NETCLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; .NET4.0C;.NET4.0E)',
              'Mozilla/5.0 (Windows NT6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Maxthon/4.0.6.2000 Chrome/26.0.1410.43Safari/537.1 ',
              'Mozilla/5.0 (compatible;MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0; SLCC2; .NET CLR 2.0.50727; .NETCLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; .NET4.0C;.NET4.0E; QQBrowser/7.3.9825.400)',
              'Mozilla/5.0 (Windows NT6.1; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0 ',
              'Mozilla/5.0 (Windows NT6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.92Safari/537.1 LBBROWSER',
              'Mozilla/5.0 (compatible;MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0; BIDUBrowser 2.x)',
              'Mozilla/5.0 (Windows NT6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11TaoBrowser/3.0 Safari/536.11']
stock_total=[]   #stock_total:所有页面的股票数据   stock_page:某页的股票数据
for page in range(1,8):
   url='http://quote.stockstar.com/stock/ranklist_a_3_1_'+str(page)+'.html'
   request=urllib.request.Request(url=url,headers={"User-Agent":random.choice(user_agent)})#随机从user_agent列表中抽取一个元素
    try:      
       response=urllib.request.urlopen(request)
    except urllib.error.HTTPError ase:            #异常检测
        print('page=',page,'',e.code)
    except urllib.error.URLError as e:
        print('page=',page,'',e.reason)
   content=response.read().decode('gbk')       #读取网页内容
    print('get page',page)                  #打印成功获取的页码
   pattern=re.compile('<tbody[\s\S]*</tbody>')
    body=re.findall(pattern,str(content))
    pattern=re.compile('>(.*?)<')
    stock_page=re.findall(pattern,body[0])      #正则匹配
    stock_total.extend(stock_page)
   time.sleep(random.randrange(1,4))       #每抓一页随机休眠几秒,数值可根据实际情况改动
#删除空白字符
stock_last=stock_total[:]  #stock_last为最终所要得到的股票数据
for data in stock_total:
    if data=='':
        stock_last.remove('')
#打印部分结果
print('代码','\t','简称','   ','\t','最新价','\t','涨跌幅','\t','涨跌额','\t','5分钟涨幅')

python自定义排序

2017524

6:37

Python内置的 sorted()函数可对list进行排序:

>>>sorted([36, 5, 12, 9, 21])
[5, 9, 12, 21, 36]

 sorted()也是一个高阶函数,它可以接收一个比较函数来实现自定义排序,比较函数的定义是,传入两个待比较的元素 x, y,如果 x 应该排在 y 的前面,返回 -1,如果 x 应该排在 y 的后面,返回 1。如果 x y 相等,返回 0

因此,如果我们要实现倒序排序,只需要编写一个reversed_cmp函数:

def reversed_cmp(x, y):
    if x > y:
        return -1
    if x < y:
        return 1
    return 0

这样,调用 sorted() 并传入 reversed_cmp 就可以实现倒序排序:

>>> sorted([36, 5, 12, 9, 21],reversed_cmp)
[36, 21, 12, 9, 5]

sorted()也可以对字符串进行排序,字符串默认按照ASCII大小来比较:

>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']

'Zoo'排在'about'之前是因为'Z'ASCII码比'a'小。

练习:

对字符串排序时,有时候忽略大小写排序更符合习惯。请利用sorted()高阶函数,实现忽略大小写排序的算法。

输入:['bob', 'about', 'Zoo', 'Credit']

>>> a = ['bob', 'about', 'Zoo', 'Credit']

>>> print(sorted(a, key=str.lower))

结果:

['about', 'bob', 'Credit', 'Zoo']

Python自定义排序实战

2017524

6:37

data_list = [] 

data_list.append({'softname':'1','version':'1.2.2.2'}) 

data_list.append({'softname':'7','version':'1.2.2.2'}) 

data_list.append({'softname':'5','version':'1.2.2.2'}) 

data_list.append({'softname':'2','version':'1.2.2.2'}) 

data_list.append({'softname':'3','version':'1.2.2.2'}) 

data_list.append({'softname':'9','version':'1.2.2.2'}) 

#升序 

data_list.sort(key=lambdaobj:obj.get('softname'), reverse=False) 

print data_list 

#[{'softname': '1'}, {'softname':'2'}, {'softname': '3'}, {'softname': '5'}, {'softname': '7'}, {'softname':'9'}] 

#降序 

data_list.sort(key=lambdaobj:obj.get('softname'), reverse=True) 

print data_list 

#[{'softname': '9'}, {'softname':'7'}, {'softname': '5'}, {'softname': '3'}, {'softname': '2'}, {'softname':'1’}] 

对List进行排序,Python提供了两个方法 

方法1.用List的内建函数list.sort进行排序 

list.sort(func=None, key=None,reverse=False)  

Python实例: 

>>> list = [2,5,8,9,3]   

>>> list   

[2,5,8,9,3]   

>>> list.sort()   

>>> list   

[2, 3, 5, 8, 9] 

方法2.用序列类型函数sorted(list)进行排序(从2.4开始) 

Python实例: 

>>> list = [2,5,8,9,3]   

>>> list   

[2,5,8,9,3]   

>>> sorted(list)   

[2, 3, 5, 8, 9] 

两种方法的区别: 

sorted(list)返回一个对象,可以用作表达式。原来的list不变,生成一个新的排好序的list对象。 

list.sort() 不会返回对象,改变原有的list。 

其他sort的实例: 

实例1:正向排序 

>>>L = [2,3,1,4] 

>>>L.sort() 

>>>L 

>>>[1,2,3,4] 

实例2:反向排序 

>>>L = [2,3,1,4] 

>>>L.sort(reverse=True) 

>>>L 

>>>[4,3,2,1] 

实例3:对第二个关键字排序  

>>>L =[('b',6),('a',1),('c',3),('d',4)] 

>>>L.sort(lambdax,y:cmp(x[1],y[1]))  

>>>L 

>>>[('a', 1), ('c', 3), ('d',4), ('b', 6)] 

实例4: 对第二个关键字排序  

>>>L =[('b',6),('a',1),('c',3),('d',4)] 

>>>L.sort(key=lambdax:x[1])  

>>>L 

>>>[('a', 1), ('c', 3), ('d',4), ('b', 6)] 

实例5: 对第二个关键字排序  

>>>L =[('b',2),('a',1),('c',3),('d',4)] 

>>>import operator 

>>>L.sort(key=operator.itemgetter(1))  

>>>L 

>>>[('a', 1), ('b', 2), ('c',3), ('d', 4)] 

实例6:(DSU方法:Decorate-Sort-Undercorate) 

>>>L =[('b',2),('a',1),('c',3),('d',4)] 

>>>A = [(x[1],i,x) for i,x inenumerate(L)] #i can confirm the stable sort 

>>>A.sort() 

>>>L = [s[2] for s in A] 

>>>L 

>>>[('a', 1), ('b', 2), ('c',3), ('d', 4)] 

以上给出了6中对List排序的方法,其中实例3.4.5.6能起到对以List item中的某一项 

为比较关键字进行排序. 

效率比较: 

cmp < DSU < key 

通过实验比较,方法3比方法6要慢,方法6比方法4要慢,方法4和方法5基本相当  

多关键字比较排序: 

实例7: 

>>>L =[('d',2),('a',4),('b',3),('c',2)] 

>>> L.sort(key=lambdax:x[1]) 

>>> L 

>>>[('d', 2), ('c', 2), ('b',3), ('a', 4)] 

我们看到,此时排序过的L是仅仅按照第二个关键字来排的, 

如果我们想用第二个关键字排过序后再用第一个关键字进行排序呢?有两种方法  

实例8: 

>>> L =[('d',2),('a',4),('b',3),('c',2)] 

>>> L.sort(key=lambdax:(x[1],x[0])) 

>>> L 

>>>[('c', 2), ('d', 2), ('b',3), ('a', 4)] 

实例9: 

>>> L =[('d',2),('a',4),('b',3),('c',2)] 

>>>L.sort(key=operator.itemgetter(1,0)) 

>>> L 

>>>[('c', 2), ('d', 2), ('b',3), ('a', 4)] 

为什么实例8能够工作呢?原因在于tuple是的比较从左到右比较的,比较完第一个,如果相等,比较第二个 

Python-新浪财经

2017525

8:12

1.1Sina股票数据接口

以大秦铁路(股票代码:601006)为例,如果要获取它的最新行情,只需访问新浪的股票数据接口:

http://hq.sinajs.cn/list=sh601006

这个url会返回一串文本,例如:

var hq_str_sh601006="大秦铁路, 27.55, 27.25,26.91, 27.55, 26.20,26.91, 26.92,

22114263, 589824680, 4695, 26.91, 57590, 26.90, 14700,26.89,14300,

26.88, 15100, 26.87, 3100, 26.92, 8900, 26.93, 14230, 26.94,25150,26.95, 15220, 26.96, 2008-01-11, 15:05:32";

这个字符串由许多数据拼接在一起,不同含义的数据用逗号隔开了,按照程序员的思路,顺序号从0开始。

0大秦铁路,股票名字;

1”27.55″,今日开盘价;

2”27.25″,昨日收盘价;

3”26.91″,当前价格;

4”27.55″,今日最高价;

5”26.20″,今日最低价;

6”26.91″,竞买价,即买一报价;

7”26.92″,竞卖价,即卖一报价;

8”22114263″,成交的股票数,由于股票交易以一百股为基本单位,所以在使用时,通常把该值除以一百;

9”589824680″,成交金额,单位为,为了一目了然,通常以万元为成交金额的单位,所以通常把该值除以一万;

10”4695″买一申请4695股,即47手;

11”26.91″买一报价;

12”57590″买二

13”26.90″买二

14”14700″买三

15”26.89″买三

16”14300″买四

17”26.88″买四

18”15100″买五

19”26.87″买五

20”3100″卖一申报3100股,即31手;

21”26.92″卖一报价

(22, 23), (24, 25), (26,27), (28, 29)分别为卖二卖四的情况

30”2008-01-11″,日期;

31”15:05:32″,时间;

一个简单的JavaScript应用例子:

这段代码输出大秦铁路(股票代码:601006)的当前股价

current price:14.20

如果你要同时查询多个股票,那么在URL最后加上一个逗号,再加上股票代码就可以了;比如你要一次查询大秦铁路(601006)和大同煤业(601001)的行情,就这样使用URL

http://hq.sinajs.cn/list=sh601003,sh601001

查询大盘指数,比如查询上证综合指数(000001):

http://hq.sinajs.cn/list=s_sh000001

服务器返回的数据为:

varhq_str_s_sh000001="上证指数,3094.668,-128.073,-3.97,436653,5458126";

数据含义分别为:指数名称,当前点数,当前价格,涨跌率,成交量(手),成交额(万元);

查询深圳成指数:

http://hq.sinajs.cn/list=s_sz399001

对于股票的K线图,日线图等的获取可以通过请求http://image.sinajs.cn/…./…/*.gifURL获取,其中*代表股票代码,详见如下:

查看日K线图:

http://image.sinajs.cn/newchart/daily/n/sh601006.gif

分时线的查询:

http://image.sinajs.cn/newchart/min/n/sh000001.gif

K线查询:

http://image.sinajs.cn/newchart/daily/n/sh000001.gif

K线查询:

http://image.sinajs.cn/newchart/weekly/n/sh000001.gif

K线查询:

http://image.sinajs.cn/newchart/monthly/n/sh000001.gif

 

 

 

 

 

今日头条美女

2017524

22:16

1、工具

Python3.6Windows 10

2、分析(第三步有完整代码)

可以看到搜索结果默认返回了 20 篇文章,当页面滚动到底部时头条通过 ajax 加载更多文章,浏览器按下 F12 打开调试工具(我的是 Chrome),点击 Network 选项,尝试加载更多的文章,可以看到相关的 http 请求:

此次返回Request URL:

http://www.toutiao.com/search_content/?offset=20&format=json&keyword=%E8%A1%97%E6%8B%8D&autoload=true&count=20&cur_tab=1

来试试返回了什么

importjson

fromurllib import request

url= "http://www.toutiao.com/search_content/?offset=20&format=json&keyword=%E8%A1%97%E6%8B%8D&autoload=true&count=20&cur_tab=1"

withrequest.urlopen(url) as res:

d= json.loads(res.read().decode())

print(d)

发现我们需要的东西在'data'里,打开一篇文章,来试试如何下载单篇图片。

import json

from urllib import request

url= 'http://www.toutiao.com/a6314996711535444226/#p=1'

with request.urlopen(url) as res:

soup= BeautifulSoup(res.read().decode(errors='ignore'), 'html.parser')

article_main= soup.find('div', id='article-main')

photo_list= [photo.get('src') for photo in article_main.find_all('img') if photo.get('src')]

print(photo_list)

输出

['http://p3.pstatp.com/large/159f00010b30d6736512', 'http://p1.pstatp.com/large/1534000488c40143b9ce', 'http://p3.pstatp.com/large/159d0001834ff61ccb8c', 'http://p1.pstatp.com/large/1534000488c1cd02b5ed']

首先用BeautifulSoup解析网页,通过 find 方法找到 article-main 对应的 div 块,在该 div 块下继续使用 find_all 方法搜寻全部的 img 标签,并提取其 src 属性对应的值,于是我们便获得了该文章下全部图片的 URL 列表。

接下来就是保存图片。

photo_url= "http://p3.pstatp.com/large/159f00010b30d6736512"

photo_name= photo_url.rsplit('/', 1)[-1] + '.jpg'

with request.urlopen(photo_url) as res, open(photo_name, 'wb') as f:

f.write(res.read())

基本步骤就是这么多了,整理下爬取流程:

1.   指定查询参数,向 http://www.toutiao.com/search_content/ 提交我们的查询请求。

2.   从返回的数据(JSON 格式)中解析出全部文章的 URL,分别向这些文章发送请求。

3.   从返回的数据(HTML 格式)提取出文章的标题和全部图片链接。

4.   再分别向这些图片链接发送请求,将返回的图片输入保存到本地(E:\jiepai)。

5.   修改查询参数,以使服务器返回新的文章数据,继续第一步。

3、完整代码

import re

import json

import time

import random

from pathlib import Path

from urllib import parse

from urllib import error

from urllib import request

from datetime import datetime

from http.client import IncompleteRead

from socket import timeout as socket_timeout

from bs4 import BeautifulSoup

def_get_timestamp():

"""

http://www.toutiao.com/search_content/发送的请求的参数包含一个时间戳,

该函数获取当前时间戳,并格式化成头条接收的格式。格式为 datetime.today() 返回

的值去掉小数点后取第一位到倒数第三位的数字。

"""

row_timestamp= str(datetime.timestamp(datetime.today()))

return row_timestamp.replace('.', '')[:-3]

def_create_dir(name):

"""

根据传入的目录名创建一个目录,这里用到了 python3.4 引入的 pathlib 库。

"""

directory= Path(name)

ifnotdirectory.exists():

directory.mkdir()

return directory

def_get_query_string(data):

"""

将查询参数编码为 url,例如:

data= {

'offset':offset,

'format':'json',

'keyword':'街拍',

'autoload':'true',

'count':20,

'_':1480675595492

}

则返回的值为:

?offset=20&format=json&keyword=%E8%A1%97%E6%8B%8D&autoload=true&count=20&_=1480675595492"

"""

return parse.urlencode(data)

defget_article_urls(req, timeout=10):

with request.urlopen(req,timeout=timeout) as res:

d= json.loads(res.read().decode()).get('data')

if d is None:

print("数据全部请求完毕...")

return

urls= [article.get('article_url') for article in d if article.get('article_url')]

return urls

defget_photo_urls(req, timeout=10):

with request.urlopen(req,timeout=timeout) as res:

#这里 decode 默认为 utf-8 编码,但返回的内容中含有部分非 utf-8 的内容,会导致解码失败

#所以我们使用 ignore 忽略这部分内容

soup= BeautifulSoup(res.read().decode(errors='ignore'), 'html.parser')

article_main= soup.find('div', id='article-main')

ifnot article_main:

print("无法定位到文章主体...")

return

heading= article_main.h1.string

if '街拍' not in heading:

print("这不是街拍的文章!!!")

return

img_list= [img.get('src') for img in article_main.find_all('img') if img.get('src')]

return heading, img_list

defsave_photo(photo_url, save_dir, timeout=10):

photo_name= photo_url.rsplit('/', 1)[-1] + '.jpg'

#这是 pathlib 的特殊操作,其作用是将 save_dir photo_name 拼成一个完整的路径。例如:

#save_dir = 'E\jiepai'

#photo_name = '11125841455748.jpg'

# save_path = 'E\jiepai\11125841455748.jpg'

save_path= save_dir / photo_name

with request.urlopen(photo_url,timeout=timeout) as res, save_path.open('wb') as f:

f.write(res.read())

print('已下载图片:{dir_name}/{photo_name},请求的 URL 为:{url}'

.format(dir_name=dir_name,photo_name=photo_name, url=a_url))

if __name__ == '__main__':

ongoing= True

offset= 0 # 请求的偏移量,每次累加 20

root_dir= _create_dir('E:\jiepai'# 保存图片的根目录

request_headers= {

'Referer': 'http://www.toutiao.com/search/?keyword=%E8%A1%97%E6%8B%8D',

'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36'

}

while ongoing:

timestamp= _get_timestamp()

query_data= {

'offset': offset,

'format': 'json',

'keyword': '街拍',

'autoload': 'true',

'count': 20# 每次返回 20 篇文章

'_': timestamp

}

query_url= 'http://www.toutiao.com/search_content/' + '?' + _get_query_string(query_data)

article_req= request.Request(query_url, headers=request_headers)

article_urls= get_article_urls(article_req)

#如果不再返回数据,说明全部数据已经请求完毕,跳出循环

if article_urls is None:

break

#开始向每篇文章发送请求

for a_url in article_urls:

#请求文章时可能返回两个异常,一个是连接超时 socket_timeout

#另一个是 HTTPError,例如页面不存在

#连接超时我们便休息一下,HTTPError 便直接跳过。

try:

photo_req= request.Request(a_url, headers=request_headers)

photo_urls= get_photo_urls(photo_req)

#文章中没有图片?跳到下一篇文章

if photo_urls is None:

continue

article_heading,photo_urls = photo_urls

#这里使用文章的标题作为保存这篇文章全部图片的目录。

#过滤掉了标题中在 windows 下无法作为目录名的特殊字符。

dir_name= re.sub(r'[\\/:*?"<>|]', '', article_heading)

download_dir= _create_dir(root_dir / dir_name)

#开始下载文章中的图片

for p_url in photo_urls:

#由于图片数据以分段形式返回,在接收数据时可能抛出 IncompleteRead 异常

try:

save_photo(p_url,save_dir=download_dir)

except IncompleteRead as e:

print(e)

continue

except socket_timeout:

print("连接超时了,休息一下...")

time.sleep(random.randint(15, 25))

continue

except error.HTTPError:

continue

#一次请求处理完毕,将偏移量加 20,继续获取新的 20 篇文章。

offset+= 20

文章参考Python 福利小爬虫,爬取今日头条街拍美女图

同理,只需修改代码,就可以下载想要的关键词,自己动手,想啥有啥。

打个广告,寻找喜欢爬虫的小伙伴。

打算爬取头条的评论,因为头条评论比正文好看。

既然有人要图,好吧。。只有下载的一部分图片( . )

2017524

22:27

目标:获取BOSS直聘上武汉Python工资情况

url=“【武汉Python招聘】2017年武汉Python最新人才招聘信息-BOSS直聘

环境:Python3.5Pycharm

需要提前安装的库:requestsBeautifulSoup4lxml

1、首先打开目标网页,寻找我们需要的headers

F12——F5——Network,招聘网站反爬还是有的,有次爬拉钩就被封了。。

2、寻找我们需要的信息

可以发现工资“9K-16K”“<spanclass="red">9K-16K</span>”下,待会使用BeautifulSoup4就会很简单。

现在准备工作都已经做完了,下面开始写代码了。

3、写代码

import requests

from bs4 import BeautifulSoup

首先引入模块

url= 'http://www.zhipin.com/job_detail/?query=Python&scity=101200100&source=2'

headers= {

'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',

'Accept-Language':'zh-CN,zh;q=0.8',

'Host':'www.zhipin.com',

'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36',

}

在请求加上头信息,伪装成浏览器访问

a= requests.get(url,headers=headers)

soup= BeautifulSoup(a.text,'lxml')

下一步使用requests获取网页信息,定制请求头。关于 BeautifulSoup的解析器,官方这样说

BeautifulSoup 第一个参数应该是要被解析的文档字符串或是文件句柄,第二个参数用来标识怎样解析文档.如果第二个参数为空,那么Beautiful Soup根据当前系统安装的库自动选择解析器,解析器的优先数序: lxml, html5lib,Python标准库.在下面两种条件下解析器优先顺序会变化:

·       要解析的文档是什么类型: 目前支持, “html”, “xml”, “html5”

·       指定使用哪种解析器: 目前支持, “lxml”,“html5lib”, “html.parser”

现在我们已经获得了我们想要的网页数据,下一步就是从中找出工资情况。

先前一经发现工资的位置<spanclass="red">9K-16K</span>

b= soup.find_all("span",class_="red")

print(b)

[<span class="red">10K-15K</span>,<span class="red">9K-16K</span>, <spanclass="red">10K-20K</span>, <spanclass="red">10K-20K</span>, <spanclass="red">6K-12K</span>, <spanclass="red">8K-16K</span>, <spanclass="red">15K-30K</span>, <spanclass="red">10K-15K</span>, <spanclass="red">8K-11K</span>, <spanclass="red">8K-16K</span>, <spanclass="red">15K-16K</span>, <spanclass="red">12K-18K</span>, <spanclass="red">10K-15K</span>, <span class="red">8K-15K</span>,<span class="red">8K-15K</span>]

结果中包含有标签,BeautifulSoup提供一种很简洁的办法 get_text()

C:\Python35\python.exeD:/Backup/桌面/77/爬虫.py

Traceback(most recent call last):

File"D:/Backup/桌面/77/爬虫.py", line 18, in <module>

print(b.get_text())

AttributeError:'ResultSet' object has no attribute 'get_text'

可是直接使用却出错,提示“'ResultSet' object has no attribute 'get_text'”

只好换一种方法

b= soup.find_all("span",class_="red")

#print(b)

for i in b:

c= i.get_text("|", strip=True)

print(c)

得到结果

这只是抓取了一页的数据,有15个,其实数据太少,但是该如何进行多页爬取?粗略看看工资基本在10K左右,那么运营岗位工资如何。

只需修改url就可以,结果为:

Python爬虫之九派新闻

2017524

22:30

1、寻找URL

进入九派新闻内容页九派号“F12”——“Network”——“F5”,勾选“XHR”,发现多个文件。

经过一个一个寻找,在Preview中先测试,发现就是内容页的新闻,打开Headers,寻找我们要的URLGET请求。

2、爬取内容

先上代码

import requests

import json

url = 'http://appjph.jiupaicn.com/app/content/recommend_pc/list?deviceId=888&type=1&page=1&pageSize=15&_=1485163767294'

webdata = requests.get(url).text

data = json.loads(webdata)

news = data['resultData']

for n in news:

title = n['title']

name = n['memberName']

News_url = 'http://jphao.jiupaicn.com/index.php?m=content&c=jiupaihao&a=article&' + '&id=' + n['id'] + '&memberId=' + n['memberId']

print(title,name,News_url)

OK,大功告成,和头条不一样的是,九派新闻URL有所改变,是id + numberId 组成,只需要在最后修改下就可以了,但是只能爬取十多条。慢慢学,慢慢来吧。

至于为什么要爬取九派,哼哼,,,

Pip安装卸载

2017523

20:59

pip是一个很方便的工具, 可以方便安装, 列出, 卸载python的模块/库/包等

常见使用, 例如:

cmd下:

安装pycurl

pip install pycurl

列出已经安装的python

pip list

输出pycurl包的信息

pip show pycurl

                   

卸载pycurl

pip uninstall pycurl

以下是pip全部命令参数 :

Usage:                                                                      

  pip <command> [options]                                                   

                                                                            

Commands:                                                                   

  install                    Install packages.                              

  uninstall                  Uninstall packages.                             

  freeze                     Output installed packages in requirementsformat.

  list                       List installed packages.                        

  show                       Show information about installedpackages.       

  search                     Search PyPI for packages.                       

  wheel                      Build wheels from your requirements.            

  help                       Show help for commands.                         

                                                                            

General Options:                                                            

  -h, --help                 Show help.                                     

  --isolated                 Run pip in an isolated mode, ignoring           

                             environment variables and userconfiguration.    

  -v, --verbose              Give more output. Option is additive, and can be 

                             used up to 3 times.                            

  -V, --version              Show version and exit.                          

  -q, --quiet                Give less output.                               

  --log <path>               Path to a verbose appending log.                

  --proxy <proxy>            Specify a proxy in the form                     

                            [user:passwd@]proxy.server:port.                

  --retries <retries>        Maximum number of retries each connection should 

                             attempt (default 5 times).                     

  --timeout <sec>            Set the socket timeout (default 15 seconds).     

  --exists-action <action>   Default action when a path already exists:       

                             (s)witch, (i)gnore, (w)ipe,(b)ackup.            

  --trusted-host <hostname>   Mark thishost as trusted, even though it does   

                             not have valid or any HTTPS.                    

  --cert <path>              Path to alternate CA bundle.                    

  --client-cert <path>       Path to SSL client certificate, a single file    

                             containing the private key andthe certificate   

                             in PEM format.                                 

  --cache-dir <dir>          Store the cache data in <dir>.                  

  --no-cache-dir             Disable the cache.                              

  --disable-pip-version-check                                                

                             Don't periodically check PyPIto determine       

                             whether a new version of pipis available for    

                             download. Implied with--no-index.               

Python3 JSON 数据解析

2017413

11:20

 

JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式。它基于ECMAScript的一个子集。

Python3 中可以使用 json 模块来对 JSON 数据进行编解码,它包含了两个函数:

·       json.dumps(): 对数据进行编码。

·       json.loads(): 对数据进行解码。

json的编解码过程中,python 的原始类型与json类型会相互转换,具体的转化对照如下:

Python 编码为 JSON 类型转换对应表:

Python

JSON

dict

object

list, tuple

array

str

string

int, float, int- & float-derived Enums

number

True

true

False

false

None

null

JSON 解码为 Python 类型转换对应表:

JSON

Python

object

dict

array

list

string

str

number (int)

int

number (real)

float

true

True

false

False

null

None

json.dumps json.loads 实例

以下实例演示了 Python 数据结构转换为JSON

#!/usr/bin/python3

importjson

# Python 字典类型转换为JSON 对象
data = {
    'no': 1,
    'name': 'Runoob',
    'url': 'http://www.runoob.com'
}

json_str = json.dumps(data)
print ("Python 原始数据:", repr(data))
print ("JSON 对象:", json_str)

执行以上代码输出结果为:

Python 原始数据: {'url': 'http://www.runoob.com', 'no': 1, 'name': 'Runoob'}
JSON 对象: {"url": "http://www.runoob.com", "no": 1, "name": "Runoob"}

通过输出的结果可以看出,简单类型通过编码后跟其原始的repr()输出结果非常相似。

接着以上实例,我们可以将一个JSON编码的字符串转换回一个Python数据结构:

#!/usr/bin/python3

importjson

# Python 字典类型转换为JSON 对象
data1 = {
    'no': 1,
    'name': 'Runoob',
    'url': 'http://www.runoob.com'
}

json_str = json.dumps(data1)
print ("Python 原始数据:", repr(data1))
print ("JSON 对象:", json_str)

# JSON 对象转换为Python 字典
data2 = json.loads(json_str)
print ("data2['name']: ", data2['name'])
print ("data2['url']: ", data2['url'])

执行以上代码输出结果为:

Python 原始数据: {'name': 'Runoob', 'no': 1, 'url': 'http://www.runoob.com'}
JSON 对象: {"name": "Runoob", "no": 1, "url": "http://www.runoob.com"}
data2['name']:  Runoob
data2['url']:  http://www.runoob.com

如果你要处理的是文件而不是字符串,你可以使用 json.dump()  json.load() 来编码和解码JSON数据。例如:

# 写入 JSON 数据
with open('data.json', 'w') as f:
    json.dump(data, f)

# 读取数据
with open('data.json', 'r') as f:
    data = json.load(f)

更多资料请参考:https://docs.python.org/3/library/json.html

Python3 XML解析

2017413

11:20

什么是XML

XML 指可扩展标记语言(eXtensible Markup Language),标准通用标记语言的子集,是一种用于标记电子文件使其具有结构性的标记语言。

XML 被设计用来传输和存储数据。

XML是一套定义语义标记的规则,这些标记将文档分成许多部件并对这些部件加以标识。

它也是元标记语言,即定义了用于定义其他与特定领域有关的、语义的、结构化的标记语言的句法语言。

pythonXML的解析

常见的XML编程接口有DOMSAX,这两种接口处理XML文件的方式不同,当然使用场合也不同。

python有三种方法解析XMLSAXDOM,以及ElementTree:

1.SAX (simple APIfor XML )

python 标准库包含SAX解析器,SAX用事件驱动模型,通过在解析XML的过程中触发一个个的事件并调用用户定义的回调函数来处理XML文件。

2.DOM(DocumentObject Model)

XML数据在内存中解析成一个树,通过对树的操作来操作XML

本章节使用到的XML实例文件movies.xml内容如下:

<collection shelf="New Arrivals">
<movie title="Enemy Behind">
   <type>War, Thriller</type>
   <format>DVD</format>
   <year>2003</year>
   <rating>PG</rating>
   <stars>10</stars>
   <description>Talk about a US-Japan war</description>
</movie>
<movie title="Transformers">
   <type>Anime, Science Fiction</type>
   <format>DVD</format>
   <year>1989</year>
   <rating>R</rating>
   <stars>8</stars>
   <description>A schientific fiction</description>
</movie>
   <movietitle="Trigun">
   <type>Anime, Action</type>
   <format>DVD</format>
   <episodes>4</episodes>
   <rating>PG</rating>
   <stars>10</stars>
   <description>Vash the Stampede!</description>
</movie>
<movie title="Ishtar">
   <type>Comedy</type>
   <format>VHS</format>
   <rating>PG</rating>
   <stars>2</stars>
   <description>Viewable boredom</description>
</movie>
</collection>

python使用SAX解析xml

SAX是一种基于事件驱动的API

利用SAX解析XML文档牵涉到两个部分:解析器和事件处理器。

解析器负责读取XML文档,并向事件处理器发送事件,如元素开始跟元素结束事件;

而事件处理器则负责对事件作出相应,对传递的XML数据进行处理。

<psax适于处理下面的问题:<p="" style="color: rgb(51, 51, 51); font-family: "OpenSans", "Helvetica Neue", Helvetica, Arial, STHeiti,"Microsoft Yahei", sans-serif; font-size: 12px; font-style: normal;font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal;letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px;text-transform: none; white-space: normal; widows: 2; word-spacing: 0px;-webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255);">

·       1、对大型文件进行处理;

·       2、只需要文件的部分内容,或者只需从文件中得到特定信息。

·       3、想建立自己的对象模型的时候。

在python中使用sax方式处理xml要先引入xml.sax中的parse函数,还有xml.sax.handler中的ContentHandler。

ContentHandler类方法介绍

characters(content)方法

调用时机:

从行开始,遇到标签之前,存在字符,content的值为这些字符串。

从一个标签,遇到下一个标签之前, 存在字符,content的值为这些字符串。

从一个标签,遇到行结束符之前,存在字符,content的值为这些字符串。

标签可以是开始标签,也可以是结束标签。

startDocument()方法

文档启动的时候调用。

endDocument()方法

解析器到达文档结尾时调用。

startElement(name, attrs)方法

遇到XML开始标签时调用,name是标签的名字,attrs是标签的属性值字典。

endElement(name)方法

遇到XML结束标签时调用。

make_parser方法

以下方法创建一个新的解析器对象并返回。

xml.sax.make_parser( [parser_list] )

参数说明:

·       parser_list - 可选参数,解析器列表

parser方法

以下方法创建一个 SAX 解析器并解析xml文档:

xml.sax.parse( xmlfile, contenthandler[, errorhandler])

参数说明:

·       xmlfile - xml文件名

·       contenthandler - 必须是一个ContentHandler的对象

·       errorhandler - 如果指定该参数,errorhandler必须是一个SAXErrorHandler对象

parseString方法

parseString方法创建一个XML解析器并解析xml字符串:

xml.sax.parseString(xmlstring, contenthandler[, errorhandler])

参数说明:

·       xmlstring - xml字符串

·       contenthandler - 必须是一个ContentHandler的对象

·       errorhandler - 如果指定该参数,errorhandler必须是一个SAXErrorHandler对象

Python解析XML实例

#!/usr/bin/python3

importxml.sax

class MovieHandler( xml.sax.ContentHandler ):
   def __init__(self):
      self.CurrentData = ""
      self.type =""
      self.format =""
      self.year =""
      self.rating =""
      self.stars =""
      self.description = ""

# 元素开始调用
   def startElement(self, tag, attributes):
      self.CurrentData = tag
     
if tag == "movie":
         print("*****Movie*****")
         title = attributes["title"]
         print("Title:", title)

# 元素结束调用
   def endElement(self, tag):
      ifself.CurrentData == "type":
         print("Type:", self.type)
      elifself.CurrentData == "format":
         print("Format:", self.format)
      elifself.CurrentData == "year":
         print("Year:", self.year)
      elifself.CurrentData == "rating":
         print("Rating:", self.rating)
      elifself.CurrentData == "stars":
         print("Stars:", self.stars)
      elifself.CurrentData == "description":
         print("Description:", self.description)
      self.CurrentData = ""

# 读取字符时调用
   def characters(self, content):
      ifself.CurrentData == "type":
         self.type = content
     
elifself.CurrentData == "format":
         self.format = content
     
elifself.CurrentData == "year":
         self.year = content
     
elifself.CurrentData == "rating":
         self.rating = content
     
elifself.CurrentData == "stars":
         self.stars = content
     
elifself.CurrentData == "description":
         self.description = content
 

if ( __name__ == "__main__"):
  
   #创建一个XMLReader
   parser = xml.sax.make_parser()
   #turn off namepsaces
   parser.setFeature(xml.sax.handler.feature_namespaces, 0)

# 重写ContextHandler
   Handler= MovieHandler()
   parser.setContentHandler( Handler )
  
   parser.parse("movies.xml")

以上代码执行结果如下:

*****Movie*****
Title:Enemy Behind
Type: War, Thriller
Format: DVD
Year: 2003
Rating: PG
Stars:10
Description:Talk about a US-Japanwar
*****Movie*****
Title:Transformers
Type: Anime, Science Fiction
Format: DVD
Year: 1989
Rating: R
Stars:8
Description: A schientific fiction
*****Movie*****
Title:Trigun
Type: Anime, Action
Format: DVD
Rating: PG
Stars:10
Description:Vash the Stampede!
*****Movie*****
Title:Ishtar
Type: Comedy
Format: VHS
Rating: PG
Stars:2
Description:Viewableboredom

完整的 SAX API 文档请查阅Python SAXAPIs

使用xml.dom解析xml

文件对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展置标语言的标准编程接口。

一个 DOM 的解析器在解析一个 XML 文档时,一次性读取整个文档,把文档中所有元素保存在内存中的一个树结构里,之后你可以利用DOM 提供的不同的函数来读取或修改文档的内容和结构,也可以把修改过的内容写入xml文件。

python中用xml.dom.minidom来解析xml文件,实例如下:

#!/usr/bin/python3

fromxml.dom.minidom import parse
importxml.dom.minidom

# 使用minidom解析器打开XML 文档
DOMTree = xml.dom.minidom.parse("movies.xml")
collection = DOMTree.documentElement
if collection.hasAttribute("shelf"):
   print("Rootelement : %s" %collection.getAttribute("shelf"))

# 在集合中获取所有电影
movies = collection.getElementsByTagName("movie")

# 打印每部电影的详细信息
for movie in movies:
   print("*****Movie*****")
   if movie.hasAttribute("title"):
      print("Title:%s" % movie.getAttribute("title"))

type = movie.getElementsByTagName('type')[0]
   print("Type:%s" % type.childNodes[0].data)
   format = movie.getElementsByTagName('format')[0]
   print("Format:%s" % format.childNodes[0].data)
   rating = movie.getElementsByTagName('rating')[0]
   print("Rating:%s" % rating.childNodes[0].data)
   description = movie.getElementsByTagName('description')[0]
   print("Description:%s" % description.childNodes[0].data)

以上程序执行结果如下:

Rootelement : New Arrivals
*****Movie*****
Title:Enemy Behind
Type: War, Thriller
Format: DVD
Rating: PG
Description:Talk about a US-Japanwar
*****Movie*****
Title:Transformers
Type: Anime, Science Fiction
Format: DVD
Rating: R
Description: A schientific fiction
*****Movie*****
Title:Trigun
Type: Anime, Action
Format: DVD
Rating: PG
Description:Vash the Stampede!
*****Movie*****
Title:Ishtar
Type: Comedy
Format: VHS
Rating: PG
Description:Viewableboredom

完整的 DOM API 文档请查阅Python DOMAPIs

</psax适于处理下面的问题:<>

Python处理PDF

2017524

6:34

读取pdf文本

>>> import PyPDF2

>>> pdfFileObj =open('meetingminutes.pdf', 'rb')

>>> pdfReader =PyPDF2.PdfFileReader(pdfFileObj)

 >>> pdfReader.numPages

 >>> pageObj = pdfReader.getPage(0)

 >>> pageObj.extractText()

Python处理xls

2017524

6:31

需要提前安装openpyxl

读取xlsx

from openpyxl.reader.excel  import  load_workbook 

import MySQLdb 

import time 

#开始时间  

startTime = time.time() 

#读取excel2007文件  

wb = load_workbook(filename =r'empty_book.xlsx' ) 

#显示有多少张表  

print  "Worksheet range(s):" , wb.get_named_ranges() 

print  "Worksheet name(s):" , wb.get_sheet_names() 

#取第一张表  

sheetnames = wb.get_sheet_names() 

ws = wb.get_sheet_by_name(sheetnames[0]) 

#显示表名,表行数,表列数  

print  "Work Sheet Titile:" ,ws.title 

print  "Work Sheet Rows:" ,ws.get_highest_row() 

print  "Work Sheet Cols:" ,ws.get_highest_column() 

# 建立存储数据的字典   

data_dic = {}  

#把数据存到字典中  

for rx  in  range(ws.get_highest_row()): 

     

   temp_list = [] 

   pid = ws.cell(row = rx,column = 0 ).value 

   w1 = ws.cell(row = rx,column = 1 ).value 

   w2 = ws.cell(row = rx,column = 2 ).value 

   w3 = ws.cell(row = rx,column = 3 ).value 

   w4 = ws.cell(row = rx,column = 4 ).value 

   temp_list = [w1,w2,w3,w4] 

    

   data_dic[pid] = temp_list 

#打印字典数据个数  

print (  'Total:%d' %len(data_dic)  )

 注意的是ws.cell()方法,支持的参数有两种,cell coordinate=None , row=None , column=None

coordinate坐标,eg  ws.cell("B1")

row column 是行和列,都是从0开始

还有,如果想取得格里的值,得用ws.cell("A1").value 取到,如果用过xlrd,因为写法差不多,可能就会忘记加value了。

import MySQLdb 

import time 

import sys 

#workbook相关  

from openpyxl.workbook  import  Workbook 

#万恶的ExcelWriter,妹的封装好了不早说,封装了很强大的excel写的功能  

from openpyxl.writer.excel  import  ExcelWriter 

#一个eggache的数字转为列字母的方法  

from openpyxl.cell  import  get_column_letter 

#新建一个workbook  

wb = Workbook() 

#新建一个excelWriter  

ew = ExcelWriter(workbook = wb) 

#设置文件输出路径与名称  

dest_filename =r'empty_book.xlsx'  

#第一个sheet是ws  

ws = wb.worksheets[0 ] 

#设置ws的名称  

ws.title = "rangenames"   

#录入数据,注意col是数字转字母,然后需要限定%s(string型)当参数传到ws.cell()方法中去,records可以想象为一个从数据库里查询出来的数据集合  

i=1  

table = {} 

for record  in  records: 

   for  x  in range( 1 ,len(record)+ 1 ): 

       col = get_column_letter(x) 

       ws.cell('%s%s' %(col, i)).value = '%s'  % (record[x- 1 ])       

             

   i+=1  

#又建了一个sheet,ws名字都没变,太省了。。。但是确实是一个新的sheet,不会影响之前那个sheet的东西  

ws = wb.create_sheet() 

ws.title = 'Pi'  

ws.cell('F5' ).value =  3.14  

     

#写文件  

ew.save(filename = dest_filename)

 注意的地方:

# col是用列号x为参数,调用了这个模块的get_column_letter方法算出来的字母,这个比较蛋疼。

   col = get_column_letter(x)

#在为数据格赋值的时候,注意写的格式:要不会有各种不靠谱的问题出现(这个是用坐标的方式写的,其实用row ,col的方式可能没那么麻烦)

   ws.cell( '%s%s'%(col, i) ).value = '%s' % (record[x-1])

关于该模块的API  可以查询官方文档   http://packages.python.org/openpyxl/api.html

总体来说,这个模块还是挺方便的,但是问题就是在对于python的版本有一定要求,如果在centOs上用,可能会有些问题。

Python处理word

2017524

6:34

从.docx 文件中取得完整的文本

如果你只关心 Word 文档中的文本,不关心样式信息,就可以利用 getText()函

数。它接受一个.docx 文件名,返回其中文本的字符串。打开一个新的文件编辑器

窗口,输入以下代码,并保存为readDocx.py:

处理 PDF 和Word 文档

#! python3

import docx

def getText(filename):

doc = docx.Document(filename)

fullText = []

for para in doc.paragraphs:

fullText.append(para.text)

return '\n'.join(fullText)

getText()函数打开了Word 文档,循环遍历 paragraphs 列表中的所有 Paragraph

对象,然后将它们的文本添加到 fullText 列表中。循环结束后,fullText 中的字符串

连接在一起,中间以换行符分隔。

readDocx.py 程序可以像其他模块一样导入。现在如果你只需要 Word 文档中的

文本,就可以输入以下代码:

>>> import readDocx

>>>print(readDocx.getText('demo.docx'))

Document Title

A plain paragraph with some bold andsome italic

Heading, level 1

Intense quote

first item in unordered list

first item in ordered list

也可以调整 getText(),在返回字符串之前进行修改。例如,要让每一段缩进,

就将文件中的 append()调用替换为:

fullText.append(' ' + para.text)

要在段落之间增加空行,就将 join()调用代码改成:

return '\n\n'.join(fullText)

可以看到,只需要几行代码,就可以写出函数,读取.docx 文件,根据需要返

回它的内容字符串。

写入 Word 文档

在交互式环境中输入以下代码:

>>> import docx

>>> doc = docx.Document()

>>> doc.add__paragraph('Hello world!')

<docx.text.Paragraph object at0x0000000003B56F60>

>>>doc.save('helloworld.docx')

要创建自己的.docx 文件,就调用 docx.Document(),返回一个新的、空白的 Word

Document 对象。Document对象的add_paragraph()方法将一段新文本添加到文档中,

并返回添加的 Paragraph对象的引用。在添加完文本之后,向 Document 对象的 save()

方法传入一个文件名字符串,将 Document 对象保存到文件

可以用新的段落文本,再次调用add_paragraph()方法,添加段落。或者,要在

已有段落的末尾添加文本,可以调用 Paragraph对象的add_run()方法,向它传入一

个字符串。在交互式环境中输入以下代码:

>>> import docx

>>> doc = docx.Document()

>>> doc.add__paragraph('Hello world!')

<docx.text.Paragraph object at0x000000000366AD30>

>>> paraObj1 = doc.add__paragraph('This is a second paragraph.')

>>> paraObj2 = doc.add__paragraph('This is a yet another paragraph.')

>>> paraObj1.add_ _run(' Thistext is being added to the second paragraph.')

<docx.text.Run object at0x0000000003A2C860>

>>>doc.save('multipleParagraphs.docx')

Python Office 自动化

2017524

6:36

1. 操作Office文档的两种方法

本系列文章所指『Office文档』,特指Word文档、Excel工作簿和PowerPoint幻灯片。其他Office软件产生的文档不在讨论范围。对一个Office文档进行读写,除了可以直接读写磁盘上的实质文件,还可以通过Office应用程序提供的 COM 接口进行。 COM接口相当于一个『官方中介』。而提供直接读写磁盘文件的库,相当于『小中介』。在Office 2003/2004以及以前,Office文档只采用微软私有的二进制格式,而这些私有格式直到2006年才公开于众。也就是说,小中介们以前都靠逆向工程以及各种猜测才实现了Office文档的读写。时至今日,『官方中介』以及『小中介』都各有优劣。利用COM接口,优点在于能够使用到几乎Office所有的功能,并且由于COM的跨语言特性,知识迁移非常容易;缺点在于仅限于Windows + Office平台,操作系统和软件缺一不可。利用直接读写接口,优点在于跨平台,只要Python能够支持的平台基本上都可以,不需要Windows也不需要Office软件;缺点在于只能够使用一些基本的功能。本系列文章只介绍如何使用直接读写接口操作Office文档。若想了解COM接口的使用,请参考pywin32的文档以及微软MSDN

2. Hello Word!

下面的例子可以运行于任何平台(PCMac、树莓派……)。但是,如果想看到产生的文档,你还是需要一个能打开Office文档的应用。没有安装微软Office的话,其他替代软件比如 AbiWordOpenOfficeWPS之类的都可以。

Python3 SMTP发送邮件

2017413

11:20

 

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

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

Python创建 SMTP 对象语法如下:

importsmtplib

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

参数说明:

·       host: SMTP 服务器主机。你可以指定主机的ip地址或者域名如:w3cschool.cc,这个是可选参数。

·       port: 如果你提供了 host 参数, 你需要指定 SMTP 服务使用的端口号,一般情况下SMTP端口号为25

·       local_hostname: 如果SMTP在你的本机上,你只需要指定服务器地址为 localhost 即可。

Python SMTP对象使用sendmail方法发送邮件,语法如下:

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

参数说明:

·       from_addr: 邮件发送者地址。

·       to_addrs: 字符串列表,邮件发送地址。

·       msg: 发送消息

这里要注意一下第三个参数,msg是字符串,表示邮件。我们知道邮件一般由标题,发信人,收件人,邮件内容,附件等构成,发送邮件的时候,要注意msg的格式。这个格式就是smtp协议中定义的格式。

实例

以下是一个使用Python发送邮件简单的实例:

实例

#!/usr/bin/python3

import smtplib from email.mime.text

import MIMEText from email.header

import Header

sender = '[email protected]'

receivers = ['[email protected]'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱

# 三个参数:第一个为文本内容,第二个plain 设置文本格式,第三个 utf-8 设置编码

message = MIMEText('Python邮件发送测试...', 'plain', 'utf-8')

message['From'] = Header("菜鸟教程", 'utf-8')

message['To'] = Header("测试", 'utf-8')

subject = 'PythonSMTP 邮件测试'

message['Subject'] = Header(subject, 'utf-8')

try: smtpObj = smtplib.SMTP('localhost')

smtpObj.sendmail(sender, receivers, message.as_string())

print ("邮件发送成功")

except smtplib.SMTPException:

print ("Error:无法发送邮件")

我们使用三个引号来设置邮件信息,标准邮件需要三个头部信息: FromTo, Subject ,每个信息直接使用空行分割。

我们通过实例化 smtplib 模块的 SMTP 对象 smtpObj 来连接到 SMTP 访问,并使用 sendmail 方法来发送信息。

执行以上程序,如果你本机安装sendmail,就会输出:

$ python3 test.py
邮件发送成功

查看我们的收件箱(一般在垃圾箱),就可以查看到邮件信息:

如果我们本机没有 sendmail 访问,也可以使用其他服务商的 SMTP 访问(QQ、网易、Google等)。

实例

#!/usr/bin/python3

import smtplib from email.mime.text

import MIMEText from email.header

import Header # 第三方 SMTP 服务

mail_host="smtp.XXX.com"#设置服务器

mail_user="XXXX"#用户名

mail_pass="XXXXXX"#口令

sender = '[email protected]'

receivers = ['[email protected]'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱

message = MIMEText('Python邮件发送测试...', 'plain', 'utf-8')

message['From'] = Header("菜鸟教程", 'utf-8')

message['To'] = Header("测试", 'utf-8')

subject = 'PythonSMTP 邮件测试'

message['Subject'] = Header(subject, 'utf-8')

try: smtpObj = smtplib.SMTP()

smtpObj.connect(mail_host, 25) #25 SMTP 端口号

smtpObj.login(mail_user,mail_pass)

smtpObj.sendmail(sender, receivers, message.as_string())

print ("邮件发送成功")

except smtplib.SMTPException:

print ("Error:无法发送邮件")

使用Python发送HTML格式的邮件

Python发送HTML格式的邮件与发送纯文本消息的邮件不同之处就是将MIMEText_subtype设置为html。具体代码如下:

实例

#!/usr/bin/python3

import smtplib from email.mime.text

import MIMEText from email.header

import Header

sender = '[email protected]'

receivers = ['[email protected]'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱

mail_msg = """<p>Python 邮件发送测试...</p><p><a href="http://www.runoob.com">这是一个链接</a></p> """

message = MIMEText(mail_msg, 'html', 'utf-8')

message['From'] = Header("菜鸟教程", 'utf-8')

message['To'] = Header("测试", 'utf-8')

subject = 'PythonSMTP 邮件测试'

message['Subject'] = Header(subject, 'utf-8')

try: smtpObj = smtplib.SMTP('localhost')

smtpObj.sendmail(sender, receivers, message.as_string())

print ("邮件发送成功")

except smtplib.SMTPException:

print ("Error:无法发送邮件")

执行以上程序,如果你本机安装sendmail,就会输出:

$ python3 test.py
邮件发送成功

查看我们的收件箱(一般在垃圾箱),就可以查看到邮件信息:

Python 发送带附件的邮件

发送带附件的邮件,首先要创建MIMEMultipart()实例,然后构造附件,如果有多个附件,可依次构造,最后利用smtplib.smtp发送。

实例

#!/usr/bin/python3

import smtplib from email.mime.text

import MIMEText from email.mime.multipart

import MIMEMultipart from email.header

import Header

sender = '[email protected]'

receivers = ['[email protected]'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱

#创建一个带附件的实例

message = MIMEMultipart()

message['From'] = Header("菜鸟教程", 'utf-8')

message['To'] = Header("测试", 'utf-8')

subject = 'PythonSMTP 邮件测试'

message['Subject'] = Header(subject, 'utf-8')

#邮件正文内容

message.attach(MIMEText('这是菜鸟教程Python 邮件发送测试……', 'plain', 'utf-8'))

# 构造附件1,传送当前目录下的 test.txt 文件

att1 = MIMEText(open('test.txt', 'rb').read(), 'base64', 'utf-8')

att1["Content-Type"] = 'application/octet-stream'

# 这里的filename可以任意写,写什么名字,邮件中显示什么名字

att1["Content-Disposition"] = 'attachment;filename="test.txt"' message.attach(att1)

# 构造附件2,传送当前目录下的 runoob.txt 文件

att2 = MIMEText(open('runoob.txt', 'rb').read(), 'base64', 'utf-8')

att2["Content-Type"] = 'application/octet-stream'

att2["Content-Disposition"] = 'attachment;filename="runoob.txt"' message.attach(att2)

try: smtpObj = smtplib.SMTP('localhost')

smtpObj.sendmail(sender, receivers, message.as_string())

print ("邮件发送成功")

except smtplib.SMTPException:

print ("Error:无法发送邮件")

$ python3 test.py
邮件发送成功

查看我们的收件箱(一般在垃圾箱),就可以查看到邮件信息:

HTML 文本中添加图片

邮件的 HTML 文本中一般邮件服务商添加外链是无效的,正确添加突破的实例如下所示:

实例

#!/usr/bin/python3

import smtplib from email.mime.image

import MIMEImage from email.mime.multipart

import MIMEMultipart from email.mime.text

import MIMEText from email.header

import Header

sender = '[email protected]'

receivers = ['[email protected]'] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱

msgRoot = MIMEMultipart('related')

msgRoot['From'] = Header("菜鸟教程", 'utf-8')

msgRoot['To'] = Header("测试", 'utf-8')

subject = 'PythonSMTP 邮件测试'

msgRoot['Subject'] = Header(subject, 'utf-8')

msgAlternative = MIMEMultipart('alternative')

msgRoot.attach(msgAlternative)

mail_msg = """<p>Python 邮件发送测试...</p><p><a href="http://www.runoob.com">菜鸟教程链接</a></p> <p>图片演示:</p> <p><imgsrc="cid:image1"></p> """msgAlternative.attach(MIMEText(mail_msg, 'html', 'utf-8')) # 指定图片为当前目录

fp = open('test.png', 'rb')

msgImage = MIMEImage(fp.read())

fp.close()

# 定义图片 ID,在 HTML 文本中引用

msgImage.add_header('Content-ID', '<image1>')

msgRoot.attach(msgImage)

try: smtpObj = smtplib.SMTP('localhost')

smtpObj.sendmail(sender, receivers, msgRoot.as_string())

print ("邮件发送成功")

except smtplib.SMTPException:

print ("Error:无法发送邮件")

$ python3 test.py
邮件发送成功

查看我们的收件箱(如果在垃圾箱可能需要移动到收件箱才可正常显示),就可以查看到邮件信息:

使用第三方 SMTP 服务发送

这里使用了 QQ 邮箱(你也可以使用 163Gmail) SMTP 服务,需要做以下配置:

QQ 邮箱通过生成授权码来设置密码:

QQ 邮箱 SMTP 服务器地址:smtp.qq.com,端口:25

以下实例你需要修改:发件人邮箱(你的QQ邮箱),密码,收件人邮箱(可发给自己)。

QQ SMTP

#!/usr/bin/python3

import smtplib from email.mime.text

import MIMEText from email.utils

import formataddr

my_sender='[email protected]' # 发件人邮箱账号

my_pass = 'xxxxxxxxxx' # 发件人邮箱密码

my_user='[email protected]' # 收件人邮箱账号,我这边发送给自己

def mail():

ret=True

try: msg=MIMEText('填写邮件内容','plain','utf-8')       msg['From']=formataddr(["FromRunoob",my_sender])

# 括号里的对应发件人邮箱昵称、发件人邮箱账号

msg['To']=formataddr(["FK",my_user]) # 括号里的对应收件人邮箱昵称、收件人邮箱账号 msg['Subject']="菜鸟教程发送邮件测试" # 邮件的主题,也可以说是标题 server=smtplib.SMTP("smtp.qq.com", 25) # 发件人邮箱中的SMTP服务器,端口是25 server.login(my_sender, my_pass) # 括号中对应的是发件人邮箱账号、邮箱密码 server.sendmail(my_sender,[my_user,],msg.as_string()) # 括号中对应的是发件人邮箱账号、收件人邮箱账号、发送邮件

server.quit() # 关闭连接

except Exception:

# 如果try 中的语句没有执行,则会执行下面的ret=False

ret=False

return ret

ret=mail()

if ret:

print("邮件发送成功")

else:

print("邮件发送失败")

$ python test.py
邮件发送成功

发送成功后,登陆收件人邮箱即可查看:

更多内容请参阅:https://docs.python.org/3/library/email-examples.html

jython

2017525

22:31

要了解Jython(旧称JPython),首先要了解PythonPython是用C编写的高级的、面向对象的、开放源代码的编程语言。GuidovanRossumPython的原创者,继而在Python的快速发展中产生了一大群高水平的设计者和程序员。使用Python的开发人员增长迅速,并一直在持续增长。然而SunJava编程语言也是深入人心的。随着用Java实现的项目的数量接近了用C/C++实现的项目,PythonJava实现也变得很有必要。Jython,最初叫做JPython,就是:Python语言的Java实现。为避免混淆,本书用CPython来表示PythonC语言实现,而用Jython来表示Java实现,而Python表示实现的中性概念和Python语言规范的设计特征。

JythonPython强调了代码的简明性、方便性和易读性。Jython使用缩排来对代码块定界以避免使用在Java中的大括号。Jython用新的一行来表示一个新的语句的开始,并有几个重要的区别,如允许在每个语句后省略分号。Jython没有像在Java中的publicprivateprotected存取符,这样就给程序员提供了快速开发所需要的灵活性,并将注意力集中在程序逻辑上。正像前面所提到的,Jython不用明显的静态的类型定义,故程序员不需要从程序逻辑转移到类型定义上来。

Jython的历史要追溯到JimHugunin,他是Guidovan Rossum在国家研究动力中心(CNRI)的同事。JimHugunin认识到Python编程语言用Java实现的重要性,并实现了最初名为JPython的语言。由于要开发aspectj(http//aspectjorg/)JimHugunin不能继续致力于JPython了。所以当Python的开发者准备离开CNRI时,由当时也在CNRIBarry Warsaw继续领导开发。

PythonJython项目组从CNRI离开后,在Sourceforge上转变为一种更开放的语言模型。在此期间,一个对Jython(JPython)做了主要贡献的人FinnBock领导了Jython项目小组。正是由于FinnBock所做的杰出贡献使Jython现在成为一个如此有价值的工具。类似Jython这样的开放源代码项目与开发和维护它们的人一样杰出,从这个意义上说Jython因为有FinnBack的贡献和指导而很幸运。另外一个对Jython做了最新有价值贡献的人是SamuelePedroniSamuele的贡献主要在Jython的类装载、导入机制等等。FinnSamuele目前是Jython的两个主要开发者。

特点

函数和函数编程

JythonPython一样有第一类函数。第一类函数是指能像变量一样的可调用的对象。第一类函数在对事件处理和其他情况下有意义,这导致增加了Java内部类的功能。虽然Java的内部类与第一类函数类似,但它在方便性与灵活性方面有很大的不足,这是由于Jython中的第一类函数减少了Jython中的语法开销。

Jython也包括了所有函数编程所需要的工具。这意味着强制的面向对象的函数编程在Jython中得到支持。这显然在教学上很有意义,它使Jython程序员能选择最适合于特定问题的编程语言而不是由语言强加。函数化的工具如列表包含、lambda表单、mapfilterreduce也对减少代码的行数、降低复杂性和名字重绑定数(名字重绑定有很大副作用)起到很大的作用。

学习周期短

任何Java程序员在数日内就能熟悉Jython。由于有很多内容,关键在于细节的学习,但仅仅用几天的时间就能拥有Jython的快速开发功能确实是很有价值的。对于那些从事测试和技术支持的小组通常并没有很多时间去学习复杂的Java代码,但通过对Jython的学习能在开销很少的情况下很快提高公司的技术水平和效率。

写一次,处处可用

由于Jython是用Java编写的且由于其可编译成Java字节码,因此Jython也具有Java写一次,处处可用的特点。Jython能运行在任何可兼容的Java11‘或更高的Java虚拟机(JVM)版本的平台上。另外你可将Jython应用编译成自足的字节码,它能运行在任何兼容的JVM上。在Linux上编译的应用能运行在有兼容JVM的任何其他平台上。

Java安全性

Java的安全性是特别的而且越来越重要。从沙箱到信号,Jython有能力使用Java的特别的安全框架。

代码清晰性

代码的清晰性是Python最大的优点,当然也是Jython最大的优点。不必要的标点和行都避免了。Jython代码在可读性和清晰性方面近似于自然语言。这起源于Python对代码块和语句的简单描绘的承诺。缩排标记代码块,换行符标记新的语句。在此之上,语法通常支持。

效率

计算编程语言的效率是一个很广泛的课题,它要考虑程序员的时间、总体复杂性、代码的行数、可用性、可维护性和运行效率。当然很多人不同意赋予这些变量的权重,经常是在不同的情况下偏重有所不同。然而本书[1]  的前提是Jython除运行时的效率外,其他方面都超过其他语言。Jython的运行速度是可以与其他高级语言相比的,但速度并不是高级语言的目标和特点。区别在于当加速一个需要的应用时,将Jython代码翻译成Java更有效,这是由于JythonJava的无缝集成性。另外对所有有效的Java类的直接访问增加了改进已存在的类的可能性。

动态类型

Jython中你不必像在Java中那样声明类型,因为类型是在运行时决定的。Jython的列表和映射类型是高级的多态的Java类的实例。多态意味着对象能对不同的数据类型工作。例如Jythonlist类型可以是一个数字的序列、字符串的序列、字符的序列或它们的组合。动态和多态性的列是对编程的极大的贡献,从很多已放弃显式的静态类型定义的高级语言中可看出它减少了代码的行数,降低了复杂性,提高了程序的效率。

内省和动态执行

Jython有一些允许方便的对象内省和代码的动态执行的内部函数。内省是发现一个对象信息的能力,而动态执行是执行在运行时产生的代码的能力。该功能很大程度上减少了代码的行数并增加了程序的可靠性,使其更加方便维护。这也能使数据和程序结构或逻辑更好的集成而不影响重用性,因为所有的东西都是在运行时决定的。

优势

Jython由于继承了JavaPython二者的特性而显得很独特。

Java类的无缝存取

Java中实现Python可以看到有趣的Java反射API的作用。反射使Jython能无缝地使用任何Java类。JythonCPython中继承了很多优点,但CPython不像别的专为Python所写的一样,在CPython之间有一些问题限制了C库函数的使用。在Jython中真正解决了这个问题,使其编程的效率和生产力得到了很大的提高。

由于与Java的无缝集成,Jython能使任何部署了Java应用和框架的公司受益而不需要额外的工作。接受任何一种部门的编程语言,对任何一个公司而言都是不容易的,需要深思熟虑,因为这牵涉到整体结构、服务器和外围的工具。Jython作为Java的一个无缝集成的语言,可以在已存在的Java应用上无缝增加而不需要重大抉择。很多公司都花费了很多资金来建立Java的应用,这使采用CPythonPerlRubyPHP和其他不能透明地集成已有Java实现的高级语言的效益降低,吸引力下降。而Jython有能力对已存在的Java框架进行补充,且二者能无缝地结合。

示例

按钮

import java

from java import awt

def exit(e): java.lang.System.exit(0)

frame = awt.Frame('AWT Example', visible=1)

button = awt.Button('Close Me!',actionPerformed=exit)

frame.add(button, 'Center')

frame.pack()

Python2.x与Python3.x的对比

2017318

4:12

1.性能

Py3.0运行 pystone benchmark的速度比Py2.530%Guido认为Py3.0有极大的优化空间,在字符串和整形操作上可 

以取得很好的优化结果。 

Py3.1性能比Py2.515%,还有很大的提升空间。 

2.编码 

Py3.X源码文件默认使用utf-8编码,这就使得以下代码是合法的: 

>>> 中国 ='china' 

>>>print(中国

china 

3. 语法

1)去除了<>,全部改用!= 

2)去除``,全部改用repr() 

3)关键词加入as with,还有True,False,None 

4)整型除法返回浮点数,要得到整型结果,请使用// 

5)加入nonlocal语句。使用noclocal x可以直接指派外围(非全局)变量 

6)去除print语句,加入print()函数实现相同的功能。同样的还有 exec语句,已经改为exec()函数 

例如: 

2.X: print "The answer is",2*2 

3.X: print("The answer is",2*2) 

2.X: print x, # 使用逗号结尾禁止换行 

3.X: print(x, end=" ") # 使用空格代替换行 

2.X: print # 输出新行 

3.X: print() # 输出新行 

2.X: print >>sys.stderr,"fatal error" 

3.X: print("fatal error",file=sys.stderr) 

2.X: print (x, y) # 输出repr((x,y)) 

3.X: print((x, y)) # 不同于print(x,y)! 

7)改变了顺序操作符的行为,例如x<y,当xy类型不匹配时抛出TypeError而不是返回随即的 bool值 

8)输入函数改变了,删除了raw_input,用input代替: 

2.X:guess = int(raw_input('Enter aninteger : ')) # 读取键盘输入的方法 

3.X:guess = int(input('Enter aninteger : '))

9)去除元组参数解包。不能def(a, (b, c)):pass这样定义函数了 

10)新式的8进制字变量,相应地修改了oct()函数。 

2.X的方式如下: 

>>> 0666 

438 

>>> oct(438) 

'0666' 

3.X这样: 

>>> 0666 

SyntaxError: invalid token(<pyshell#63>, line 1) 

>>> 0o666 

438 

>>> oct(438) 

'0o666' 

11)增加了 2进制字面量和bin()函数 

>>> bin(438) 

'0b110110110' 

>>> _438 ='0b110110110' 

>>> _438 

'0b110110110' 

12)扩展的可迭代解包。在Py3.X 里,a, b, *rest = seq *rest, a = seq都是合法的,只要求两点:restlist 

对象和seq是可迭代的。 

13)新的super(),可以不再给super()传参数, 

>>> class C(object): 

def __init__(self, a): 

print('C', a) 

>>> class D(C): 

def __init(self, a): 

super().__init__(a) # 无参数调用super() 

>>> D(8) 

C 8 

<__main__.D object at0x00D7ED90> 

14)新的metaclass语法: 

class Foo(*bases, **kwds): 

pass 

15)支持class decorator。用法与函数decorator一样: 

>>> def foo(cls_a): 

def print_func(self): 

print('Hello, world!') 

cls_a.print = print_func 

return cls_a 

>>> @foo 

class C(object): 

pass 

>>> C().print() 

Hello, world! 

class decorator可以用来玩玩狸猫换太子的大把戏。更多请参阅PEP 3129 

4. 字符串和字节串

1)现在字符串只有str一种类型,但它跟2.x版本的unicode几乎一样。

2)关于字节串,请参阅“数据类型”的第2条目 

5.数据类型

1Py3.X去除了long类型,现在只有一种整型——int,但它的行为就像2.X版本的long 

2)新增了bytes类型,对应于2.X版本的八位串,定义一个bytes字面量的方法如下: 

>>> b = b'china' 

>>> type(b) 

<type 'bytes'> 

str对象和bytes对象可以使用.encode() (str -> bytes) or .decode() (bytes -> str)方法相互转化。 

>>> s = b.decode() 

>>> s 

'china' 

>>> b1 = s.encode() 

>>> b1 

b'china' 

3dict.keys().items .values()方法返回迭代器,而之前的iterkeys()等函数都被废弃。同时去掉的还有 

dict.has_key(),用 in替代它吧 

6.面向对象 

1)引入抽象基类(Abstraact Base ClassesABCs)。 

2)容器类和迭代器类被ABCs化,所以cellections模块里的类型比Py2.5多了很多。 

>>> import collections 

>>>print('\n'.join(dir(collections))) 

Callable 

Container 

Hashable 

ItemsView 

Iterable 

Iterator 

KeysView 

Mapping 

MappingView 

MutableMapping 

MutableSequence 

MutableSet 

NamedTuple 

Sequence 

Set 

Sized 

ValuesView 

__all__ 

__builtins__ 

__doc__ 

__file__ 

__name__ 

_abcoll 

_itemgetter 

_sys 

defaultdict 

deque 

另外,数值类型也被ABCs化。关于这两点,请参阅 PEP3119PEP 3141。 

3)迭代器的next()方法改名为__next__(),并增加内置函数next(),用以调用迭代器的__next__()方法 

4)增加了@abstractmethod @abstractproperty两个 decorator,编写抽象方法(属性)更加方便。 

7.异常

1)所以异常都从 BaseException继承,并删除了StardardError 

2)去除了异常类的序列行为和.message属性 

3)用 raise Exception(args)代替 raise Exception, args语法 

4)捕获异常的语法改变,引入了as关键字来标识异常实例,在Py2.5中: 

>>> try: 

... raiseNotImplementedError('Error') 

... except NotImplementedError,error:

... print error.message 

... 

Error 

Py3.0中: 

>>> try: 

raiseNotImplementedError('Error') 

except NotImplementedError as error:#注意这个as 

print(str(error)) 

Error 

5)异常链,因为__context__3.0a1版本中没有实现 

8.模块变动

1)移除了cPickle模块,可以使用pickle模块代替。最终我们将会有一个透明高效的模块。 

2)移除了imageop模块 

3)移除了 audiodev, Bastion, bsddb185, exceptions, linuxaudiodev,md5, MimeWriter, mimify, popen2, 

rexec, sets, sha, stringold, strop,sunaudiodev, timingxmllib模块 

4)移除了bsddb模块(单独发布,可以从http://www.jcea.es/programacion/pybsddb.htm获取

5)移除了new模块 

6os.tmpnam()os.tmpfile()函数被移动到tmpfile模块下 

7tokenize模块现在使用bytes工作。主要的入口点不再是generate_tokens,而是 tokenize.tokenize() 

9.其它 

1xrange() 改名为range(),要想使用range()获得一个list,必须显式调用: 

>>> list(range(10)) 

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

2bytes对象不能hash,也不支持 b.lower()b.strip()b.split()方法,但对于后两者可以使用 b.strip(b’ 

\n\t\r \f)b.split(b’ ‘)来达到相同目的 

3zip()map()filter()都返回迭代器。而apply() callable()coerce() execfile()reduce()reload 

()函数都被去除了

现在可以使用hasattr()来替换callable(). hasattr()的语法如:hasattr(string, '__name__')

4string.letters和相关的.lowercase.uppercase被去除,请改用string.ascii_letters 等 

5)如果x < y的不能比较,抛出TypeError异常。2.x版本是返回伪随机布尔值的 

6__getslice__系列成员被废弃。a[i:j]根据上下文转换为a.__getitem__(slice(I, j)) __setitem__和 

__delitem__调用 

7file类被废弃,在Py2.5中: 

>>> file 

<type 'file'> 

Py3.X中: 

>>> file 

Traceback (most recent calllast): 

File "<pyshell#120>",line 1, in <module> 

file 

NameError: name 'file' is not defined 

Python FTP登陆

2017524

5:09

Python中默认安装的ftplib模块定义了FTP,其中函数有限,可用来实现简单的ftp客户端,用于上传或下载文件,函数列举如下

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

ftp登陆连接

from ftplib import FTP            #加载ftp模块

ftp=FTP()                         #设置变量

ftp.set_debuglevel(2)             #打开调试级别2,显示详细信息

ftp.connect("IP","port")          #连接的ftp sever和端口

ftp.login("user","password")      #连接的用户名,密码

print ftp.getwelcome()            #打印出欢迎信息

ftp.cmd("xxx/xxx")                #进入远程目录

bufsize=1024                      #设置的缓冲区大小

filename="filename.txt"           #需要下载的文件

file_handle=open(filename,"wb").write #以写模式在本地打开文件

ftp.retrbinaly("RETR filename.txt",file_handle,bufsize) #接收服务器上文件并写入本地文件

ftp.set_debuglevel(0)             #关闭调试模式

ftp.quit()                        #退出ftp

ftp相关命令操作

ftp.cwd(pathname)                 #设置FTP当前操作的路径

ftp.dir()                         #显示目录下所有目录信息

ftp.nlst()                        #获取目录下的文件

ftp.mkd(pathname)                 #新建远程目录

ftp.pwd()                         #返回当前所在位置

ftp.rmd(dirname)                  #删除远程目录

ftp.delete(filename)              #删除远程文件

ftp.rename(fromname, toname)#fromname修改名称为toname

ftp.storbinaly("STOR filename.txt",file_handel,bufsize)  #上传目标文件

ftp.retrbinary("RETR filename.txt",file_handel,bufsize)  #下载FTP文件

 

 

 

 

>>> from ftplib import FTP

>>> f = FTP('ftp.python.org')

>>> f.login('anonymous', '[email protected]')

'230 Guest login ok, access restrictions apply.'

>>> f.dir()

total 38

drwxrwxr-x 10 1075 4127 512 May 17 2000 .

drwxrwxr-x 10 1075 4127 512 May 17 2000 ..

drwxr-xr-x 3 root wheel 512 May 19 1998 bin

drwxr-sr-x 3 root 1400 512 Jun 9 1997 dev

drwxr-xr-x 3 root wheel 512 May 19 1998 etc

lrwxrwxrwx 1 root bin 7 Jun 29 1999 lib -> usr/lib

-r--r--r-- 1 guido 4127 52 Mar 24 2000 motd

drwxrwsr-x 8 1122 4127 512 May 17 2000 pub

drwxr-xr-x 5 root wheel 512 May 19 1998 usr

>>> f.retrlines('RETR motd')

Sun Microsystems Inc. SunOS 5.6 Generic August 1997

'226 Transfer complete.

>>> f.quit()

'221 Goodbye.'

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Python CGI编程

2017413

11:20

 

什么是CGI

CGI 目前由NCSA维护,NCSA定义CGI如下:

CGI(Common Gateway Interface),通用网关接口,它是一段程序,运行在服务器上如:HTTP服务器,提供同客户端HTML页面的接口。

网页浏览

为了更好的了解CGI是如何工作的,我们可以从在网页上点击一个链接或URL的流程:

·       1、使用你的浏览器访问URL并连接到HTTP web 服务器。

·       2Web服务器接收到请求信息后会解析URL,并查找访问的文件在服务器上是否存在,如果存在返回文件的内容,否则返回错误信息。

·       3、浏览器从服务器上接收信息,并显示接收的文件或者错误信息。

CGI程序可以是Python脚本,PERL脚本,SHELL脚本,C或者C++程序等。

CGI架构图

Web服务器支持及配置

在你进行CGI编程前,确保您的Web服务器支持CGI及已经配置了CGI的处理程序。

Apache 支持CGI 配置:

设置好CGI目录:

ScriptAlias /cgi-bin/ /var/www/cgi-bin/

所有的HTTP服务器执行CGI程序都保存在一个预先配置的目录。这个目录被称为CGI目录,并按照惯例,它被命名为/var/www/cgi-bin目录。

CGI文件的扩展名为.cgipython也可以使用.py扩展名。

默认情况下,Linux服务器配置运行的cgi-bin目录中为/var/www

如果你想指定其他运行CGI脚本的目录,可以修改httpd.conf配置文件,如下所示:

<Directory "/var/www/cgi-bin">
   AllowOverride None
   Options +ExecCGI
   Order allow,deny
   Allow from all
</Directory>

AddHandler 中添加 .py 后缀,这样我们就可以访问 .py 结尾的 python 脚本文件:

AddHandler cgi-script .cgi .pl .py

第一个CGI程序

我们使用Python创建第一个CGI程序,文件名为hello.py,文件位于/var/www/cgi-bin目录中,内容如下:

#!/usr/bin/python3

print ("Content-type:text/html")
print ()                            # 空行,告诉服务器结束头部
print ('<html>')
print ('<head>')
print ('<meta charset="utf-8">')
print ('<title>Hello Word - 我的第一个 CGI 程序!</title>')
print ('</head>')
print ('<body>')
print ('<h2>Hello Word! 我是来自菜鸟教程的第一CGI程序</h2>')
print ('</body>')
print ('</html>')

文件保存后修改 hello.py,修改文件权限为 755

chmod 755 hello.py

以上程序在浏览器访问显示结果如下:

这个的hello.py脚本是一个简单的Python脚本,脚本第一行的输出内容"Content-type:text/html"发送到浏览器并告知浏览器显示的内容类型为"text/html"

print 输出一个空行用于告诉服务器结束头部信息。

HTTP头部

hello.py文件内容中的" Content-type:text/html"即为HTTP头部的一部分,它会发送给浏览器告诉浏览器文件的内容类型。

HTTP头部的格式如下:

HTTP 字段名: 字段内容

例如:

Content-type: text/html

以下表格介绍了CGI程序中HTTP头部经常使用的信息:

描述

Content-type:

请求的与实体对应的MIME信息。例如: Content-type:text/html

Expires: Date

响应过期的日期和时间

Location: URL

用来重定向接收方到非请求URL的位置来完成请求或标识新的资源

Last-modified: Date

请求资源的最后修改时间

Content-length: N

请求的内容长度

Set-Cookie: String

设置Http Cookie

CGI环境变量

所有的CGI程序都接收以下的环境变量,这些变量在CGI程序中发挥了重要的作用:

变量名

描述

CONTENT_TYPE

这个环境变量的值指示所传递来的信息的MIME类型。目前,环境变量CONTENT_TYPE一般都是:application/x-www-form-urlencoded,他表示数据来自于HTML表单。

CONTENT_LENGTH

如果服务器与CGI程序信息的传递方式是POST,这个环境变量即使从标准输入STDIN中可以读到的有效数据的字节数。这个环境变量在读取所输入的数据时必须使用。

HTTP_COOKIE

客户机内的 COOKIE 内容。

HTTP_USER_AGENT

提供包含了版本数或其他专有数据的客户浏览器信息。

PATH_INFO

这个环境变量的值表示紧接在CGI程序名之后的其他路径信息。它常常作为CGI程序的参数出现。

QUERY_STRING

如果服务器与CGI程序信息的传递方式是GET,这个环境变量的值即使所传递的信息。这个信息经跟在CGI程序名的后面,两者中间用一个问号'?'分隔。

REMOTE_ADDR

这个环境变量的值是发送请求的客户机的IP地址,例如上面的192.168.1.67。这个值总是存在的。而且它是Web客户机需要提供给Web服务器的唯一标识,可以在CGI程序中用它来区分不同的Web客户机。

REMOTE_HOST

这个环境变量的值包含发送CGI请求的客户机的主机名。如果不支持你想查询,则无需定义此环境变量。

REQUEST_METHOD

提供脚本被调用的方法。对于使用 HTTP/1.0 协议的脚本,仅 GET POST 有意义。

SCRIPT_FILENAME

CGI脚本的完整路径

SCRIPT_NAME

CGI脚本的的名称

SERVER_NAME

这是你的 WEB 服务器的主机名、别名或IP地址。

SERVER_SOFTWARE

这个环境变量的值包含了调用CGI程序的HTTP服务器的名称和版本号。例如,上面的值为Apache/2.2.14(Unix)

以下是一个简单的CGI脚本输出CGI的环境变量:

#!/usr/bin/python3

importos

print ("Content-type: text/html")
print ()
print ("<meta charset=\"utf-8\">")
print ("<b>环境变量</b><br>")
print ("<ul>")
for key in os.environ.keys():
    print("<li><spanstyle='color:green'>%30s </span> : %s </li>" % (key,os.environ[key]))
print ("</ul>")

将以上点保存为 test.py ,并修改文件权限为 755,执行结果如下:

GETPOST方法

浏览器客户端通过两种方法向服务器传递信息,这两种方法就是 GET 方法和 POST 方法。

使用GET方法传输数据

GET方法发送编码后的用户信息到服务端,数据信息包含在请求页面的URL上,以"?"号分割, 如下所示:

http://www.test.com/cgi-bin/hello.py?key1=value1&key2=value2

有关 GET 请求的其他一些注释:

·       GET 请求可被缓存

·       GET 请求保留在浏览器历史记录中

·       GET 请求可被收藏为书签

·       GET 请求不应在处理敏感数据时使用

·       GET 请求有长度限制

·       GET 请求只应当用于取回数据

简单的url实例:GET方法

以下是一个简单的URL,使用GET方法向hello_get.py程序发送两个参数:

/cgi-bin/test.py?name=菜鸟教程&url=http://www.runoob.com

以下为hello_get.py文件的代码:

#!/usr/bin/python3

# CGI处理模块
importcgi, cgitb

# 创建 FieldStorage的实例化
form = cgi.FieldStorage()

# 获取数据
site_name = form.getvalue('name')
site_url  = form.getvalue('url')

print ("Content-type:text/html")
print ()
print ("<html>")
print ("<head>")
print ("<meta charset=\"utf-8\">")
print ("<title>菜鸟教程 CGI 测试实例</title>")
print ("</head>")
print ("<body>")
print ("<h2>%s官网:%s</h2>"% (site_name, site_url))
print ("</body>")
print ("</html>")

文件保存后修改 hello_get.py,修改文件权限为 755

chmod 755 hello_get.py

浏览器请求输出结果:

简单的表单实例:GET方法

以下是一个通过HTML的表单使用GET方法向服务器发送两个数据,提交的服务器脚本同样是hello_get.py文件,hello_get.html 代码如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<form action="/cgi-bin/hello_get.py"method="get">
站点名称:<input type="text" name="name">  <br />

站点 URL: <input type="text" name="url" />
<input type="submit" value="提交" />
</form>
</body>
</html>

默认情况下 cgi-bin 目录只能存放脚本文件,我们将 hello_get.html 存储在 test 目录下,修改文件权限为 755

chmod 755 hello_get.html

Gif 演示如下所示:

使用POST方法传递数据

使用POST方法向服务器传递数据是更安全可靠的,像一些敏感信息如用户密码等需要使用POST传输数据。

以下同样是hello_get.py ,它也可以处理浏览器提交的POST表单数据:

#!/usr/bin/python3

# CGI处理模块
importcgi, cgitb

# 创建 FieldStorage的实例化
form = cgi.FieldStorage()

# 获取数据
site_name = form.getvalue('name')
site_url  = form.getvalue('url')

print ("Content-type:text/html")
print ()
print ("<html>")
print ("<head>")
print ("<meta charset=\"utf-8\">")
print ("<title>菜鸟教程 CGI 测试实例</title>")
print ("</head>")
print ("<body>")
print ("<h2>%s官网:%s</h2>"% (site_name, site_url))
print ("</body>")
print ("</html>")

以下为表单通过POST方法(method="post")向服务器脚本 hello_get.py 提交数据:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<form action="/cgi-bin/hello_get.py"method="post">
站点名称:<input type="text" name="name">  <br />

站点 URL: <input type="text" name="url" />
<input type="submit" value="提交" />
</form>
</body>
</html>
</form>

Gif 演示如下所示:

通过CGI程序传递checkbox数据

checkbox用于提交一个或者多个选项数据,HTML代码如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<form action="/cgi-bin/checkbox.py"method="POST" target="_blank">
<input type="checkbox" name="runoob" value="on" /> 菜鸟教程
<input type="checkbox" name="google" value="on" /> Google
<input type="submit" value="选择站点" />
</form>
</body>
</html>

以下为 checkbox.py 文件的代码:

#!/usr/bin/python3

# 引入 CGI 处理模块
importcgi, cgitb

# 创建 FieldStorage的实例
form = cgi.FieldStorage()

# 接收字段数据
if form.getvalue('google'):
   google_flag =""
else:
   google_flag =""

ifform.getvalue('runoob'):
   runoob_flag =""
else:
   runoob_flag =""

print ("Content-type:text/html")
print ()
print ("<html>")
print ("<head>")
print ("<meta charset=\"utf-8\">")
print ("<title>菜鸟教程 CGI 测试实例</title>")
print ("</head>")
print ("<body>")
print ("<h2> 菜鸟教程是否选择了 :%s</h2>" %runoob_flag)
print ("<h2> Google 是否选择了 :%s</h2>" %google_flag)
print ("</body>")
print ("</html>")

修改 checkbox.py 权限:

chmod 755 checkbox.py

浏览器访问 Gif 演示图:

通过CGI程序传递Radio数据

Radio 只向服务器传递一个数据,HTML代码如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<form action="/cgi-bin/radiobutton.py"method="post" target="_blank">
<input type="radio" name="site" value="runoob" /> 菜鸟教程
<input type="radio" name="site" value="google" /> Google
<input type="submit" value="提交" />
</form>
</body>
</html>

radiobutton.py 脚本代码如下:

#!/usr/bin/python3

# 引入 CGI 处理模块
importcgi, cgitb

# 创建 FieldStorage的实例
form = cgi.FieldStorage()

# 接收字段数据
if form.getvalue('site'):
   site = form.getvalue('site')
else:
   site ="提交数据为空"

print ("Content-type:text/html")
print ()
print ("<html>")
print ("<head>")
print ("<meta charset=\"utf-8\">")
print ("<title>菜鸟教程 CGI 测试实例</title>")
print ("</head>")
print ("<body>")
print ("<h2> 选中的网站是%s</h2>" %site)
print ("</body>")
print ("</html>")

修改 radiobutton.py 权限:

chmod 755 radiobutton.py

浏览器访问 Gif 演示图:

通过CGI程序传递 Textarea 数据

Textarea 向服务器传递多行数据,HTML代码如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<form action="/cgi-bin/textarea.py"method="post" target="_blank">
<textarea name="textcontent" cols="40" rows="4">
在这里输入内容...
</textarea>
<input type="submit" value="提交" />
</form>
</body>
</html>

textarea.py 脚本代码如下:

#!/usr/bin/python3

# 引入 CGI 处理模块
importcgi, cgitb

# 创建 FieldStorage的实例
form = cgi.FieldStorage()

# 接收字段数据
if form.getvalue('textcontent'):
   text_content = form.getvalue('textcontent')
else:
   text_content ="没有内容"

print ("Content-type:text/html")
print ()
print ("<html>")
print ("<head>")
print ("<meta charset=\"utf-8\">")
print ("<title>菜鸟教程 CGI 测试实例</title>")
print ("</head>")
print ("<body>")
print ("<h2> 输入的内容是:%s</h2>"% text_content)
print ("</body>")
print ("</html>")

修改 textarea.py 权限:

chmod 755 textarea.py

浏览器访问 Gif 演示图:

通过CGI程序传递下拉数据。

HTML 下拉框代码如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<form action="/cgi-bin/dropdown.py"method="post" target="_blank">
<select name="dropdown">
<option value="runoob" selected>菜鸟教程</option>
<option value="google">Google</option>
</select>
<input type="submit" value="提交"/>
</form>
</body>
</html>

dropdown.py 脚本代码如下所示:

#!/usr/bin/python3

# 引入 CGI 处理模块
importcgi, cgitb

# 创建 FieldStorage的实例
form = cgi.FieldStorage()

# 接收字段数据
if form.getvalue('dropdown'):
   dropdown_value = form.getvalue('dropdown')
else:
   dropdown_value = "没有内容"

print ("Content-type:text/html")
print ()
print ("<html>")
print ("<head>")
print ("<meta charset=\"utf-8\">")
print ("<title>菜鸟教程 CGI 测试实例</title>")
print ("</head>")
print ("<body>")
print ("<h2> 选中的选项是:%s</h2>"% dropdown_value)
print ("</body>")
print ("</html>")

修改 dropdown.py 权限:

chmod 755 dropdown.py

浏览器访问 Gif 演示图:

CGI中使用Cookie

http 协议一个很大的缺点就是不对用户身份的进行判断,这样给编程人员带来很大的不便, cookie 功能的出现弥补了这个不足。

cookie 就是在客户访问脚本的同时,通过客户的浏览器,在客户硬盘上写入纪录数据,当下次客户访问脚本时取回数据信息,从而达到身份判别的功能,cookie 常用在身份校验中。

cookie的语法

http cookie的发送是通过http头部来实现的,他早于文件的传递,头部set-cookie的语法如下:

Set-cookie:name=name;expires=date;path=path;domain=domain;secure

·       name=name: 需要设置cookie的值(name不能使用";"","),有多个name值时用 ";" 分隔,例如:name1=name1;name2=name2;name3=name3

·       expires=date: cookie的有效期限,格式: expires="Wdy,DD-Mon-YYYY HH:MM:SS"

·       path=path: 设置cookie支持的路径,如果path是一个路径,则cookie对这个目录下的所有文件及子目录生效,例如: path="/cgi-bin/",如果path是一个文件,则cookie指对这个文件生效,例如:path="/cgi-bin/cookie.cgi"

·       domain=domain: cookie生效的域名,例如:domain="www.runoob.com"

·       secure: 如果给出此标志,表示cookie只能通过SSL协议的https服务器来传递。

·       cookie的接收是通过设置环境变量HTTP_COOKIE来实现的,CGI程序可以通过检索该变量获取cookie信息。

Cookie设置

Cookie的设置非常简单,cookie会在http头部单独发送。以下实例在cookie中设置了name expires

#!/usr/bin/python3
#
print ('Content-Type: text/html')
print ('Set-Cookie: name="菜鸟教程";expires=Wed,28 Aug 2016 18:30:00 GMT')
print ()
print ("""
<html>
 
<head>
    <meta charset="utf-8">
   
<title>菜鸟教程(runoob.com)</title>
 
</head>
    <body>
        <h1>Cookie setOK!</h1>
    </body>
</html>
""")

将以上代码保存到 cookie_set.py,并修改 cookie_set.py 权限:

chmod 755 cookie_set.py

以上实例使用了 Set-Cookie 头信息来设置Cookie信息,可选项中设置了Cookie的其他属性,如过期时间Expires,域名Domain,路径Path。这些信息设置在"Content-type:text/html"之前。

检索Cookie信息

Cookie信息检索页非常简单,Cookie信息存储在CGI的环境变量HTTP_COOKIE中,存储格式如下:

key1=value1;key2=value2;key3=value3....

以下是一个简单的CGI检索cookie信息的程序:

#!/usr/bin/python3

# 导入模块
importos
import Cookie

print ("Content-type: text/html")
print ()

print ("""
<html>
<head>
<meta charset="
utf-8">
<title>
菜鸟教程(runoob.com)</title>
</head>
<body>
<h1>
读取cookie信息</h1>
"""
)

if 'HTTP_COOKIE'in os.environ:
    cookie_string=os.environ.get('HTTP_COOKIE')
    c=Cookie.SimpleCookie()
    c.load(cookie_string)

try:
        data=c['name'].value
       
print("cookiedata: "+data+"<br>")
    exceptKeyError:
        print("cookie 没有设置或者已过去<br>")
print ("""
</body>
</html>
"""
)

将以上代码保存到 cookie_get.py,并修改 cookie_get.py 权限:

chmod 755 cookie_get.py

以上 cookie 设置颜色 Gif 如下所示:

文件上传实例

HTML设置上传文件的表单需要设置 enctype 属性为 multipart/form-data,代码如下所示:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
 <formenctype="multipart/form-data"
                     action="/cgi-bin/save_file.py" method="post">
   <p>选中文件: <input type="file" name="filename" /></p>
   <p><inputtype="submit" value="上传" /></p>
   </form>
</body>
</html>

save_file.py脚本文件代码如下:

#!/usr/bin/python3

importcgi, os
importcgitb; cgitb.enable()

form = cgi.FieldStorage()

# 获取文件名
fileitem = form['filename']

# 检测文件是否上传
if fileitem.filename:
   #设置文件路径
   fn = os.path.basename(fileitem.filename)
   open('/tmp/' + fn, 'wb').write(fileitem.file.read())

message ='文件 "' + fn +'" 上传成功'
  
else:
   message ='文件没有上传'
  
print ("""\
Content-Type: text/html\n
<html>
<head>
<meta charset="
utf-8">
<title>
菜鸟教程(runoob.com)</title>
</head>
<body>
  
<p>%s</p>
</body>
</html>
""" % (message,))

将以上代码保存到 save_file.py,并修改 save_file.py 权限:

chmod 755 save_file.py

以上 cookie 设置颜色 Gif 如下所示:

如果你使用的系统是Unix/Linux,你必须替换文件分隔符,在window下只需要使用open()语句即可:

fn = os.path.basename(fileitem.filename.replace("\\", "/" ))

文件下载对话框

我们先在当前目录下创建 foo.txt 文件,用于程序的下载。

文件下载通过设置HTTP头信息来实现,功能代码如下:

#!/usr/bin/python3

# HTTP 头部
print ("Content-Disposition: attachment; filename=\"foo.txt\"")
print ()
# 打开文件
fo = open("foo.txt", "rb")

str = fo.read();
print (str)

# 关闭文件
fo.close()

Python3 MySQL 数据库连接

2017413

11:20

 

本文我们为大家介绍 Python3 使用 PyMySQL 连接数据库,并实现简单的增删改查。

什么是 PyMySQL

PyMySQL 是在 Python3.x 版本中用于连接 MySQL 服务器的一个库,Python2中则使用mysqldb

PyMySQL 遵循 Python 数据库 API v2.0 规范,并包含了 pure-Python MySQL客户端库。

PyMySQL 安装

在使用 PyMySQL 之前,我们需要确保 PyMySQL 已安装。

PyMySQL 下载地址:https://github.com/PyMySQL/PyMySQL

如果还未安装,我们可以使用以下命令安装最新版的 PyMySQL

$ pip install PyMySQL

如果你的系统不支持 pip 命令,可以使用以下方式安装:

1、使用 git 命令下载安装包安装(你也可以手动下载)

$ git clone https://github.com/PyMySQL/PyMySQL
$ cd PyMySQL/
$ python3 setup.py install

2、如果需要制定版本号,可以使用 curl 命令来安装:

$ #X.X PyMySQL 的版本号
$ curl -L https://github.com/PyMySQL/PyMySQL/tarball/pymysql-X.X | tar xz
$ cd PyMySQL*
$ python3 setup.py install
$
# 现在你可以删除 PyMySQL* 目录

注意:请确保您有root权限来安装上述模块。

安装的过程中可能会出现"ImportError: No module namedsetuptools"的错误提示,意思是你没有安装setuptools,你可以访问https://pypi.python.org/pypi/setuptools 找到各个系统的安装方法。

Linux系统安装实例:

$ wget https://bootstrap.pypa.io/ez_setup.py
$ python3 ez_setup.py

数据库连接

连接数据库前,请先确认以下事项:

·       您已经创建了数据库 TESTDB.

·       TESTDB数据库中您已经创建了表 EMPLOYEE

·       EMPLOYEE表字段为 FIRST_NAME, LAST_NAME, AGE, SEX INCOME

·       连接数据库TESTDB使用的用户名为 "testuser" ,密码为 "test123",你可以可以自己设定或者直接使用root用户名及其密码,Mysql数据库用户授权请使用Grant命令。

·       在你的机子上已经安装了 Python MySQLdb 模块。

·       如果您对sql语句不熟悉,可以访问我们的 SQL基础教程

实例:

以下实例链接MysqlTESTDB数据库:

#!/usr/bin/python3

importpymysql

# 打开数据库连接
db = pymysql.connect("localhost","testuser","test123","TESTDB" )

# 使用 cursor() 方法创建一个游标对象cursor
cursor = db.cursor()

# 使用execute()  方法执行 SQL 查询
cursor.execute("SELECT VERSION()")

# 使用 fetchone() 方法获取单条数据.
data = cursor.fetchone()

print ("Database version : %s " % data)

# 关闭数据库连接
db.close()

执行以上脚本输出结果如下:

Database version : 5.5.20-log

创建数据库表

如果数据库连接存在我们可以使用execute()方法来为数据库创建表,如下所示创建表EMPLOYEE

#!/usr/bin/python3

importpymysql

# 打开数据库连接
db = pymysql.connect("localhost","testuser","test123","TESTDB" )

# 使用 cursor() 方法创建一个游标对象cursor
cursor = db.cursor()

# 使用 execute() 方法执行 SQL,如果表存在则删除
cursor.execute("DROP TABLE IF EXISTSEMPLOYEE")

# 使用预处理语句创建表
sql = """CREATETABLE EMPLOYEE (
        
FIRST_NAME  CHAR(20) NOT NULL,
         LAST_NAME  CHAR(20),
         AGE INT, 
         SEX CHAR(1),
         INCOME FLOAT )"""

cursor.execute(sql)

# 关闭数据库连接
db.close()

数据库插入操作

以下实例使用执行 SQL INSERT 语句向表 EMPLOYEE 插入记录:

#!/usr/bin/python3

importpymysql

# 打开数据库连接
db = pymysql.connect("localhost","testuser","test123","TESTDB" )

# 使用cursor()方法获取操作游标
cursor = db.cursor()

# SQL 插入语句
sql = """INSERTINTO EMPLOYEE(FIRST_NAME,
        
LAST_NAME, AGE, SEX, INCOME)
         VALUES ('Mac', 'Mohan', 20, 'M',2000)"""
try:
   #执行sql语句
   cursor.execute(sql)
   #提交到数据库执行
   db.commit()
except:
   #如果发生错误则回滚
   db.rollback()

# 关闭数据库连接
db.close()

以上例子也可以写成如下形式:

#!/usr/bin/python3

importpymysql

# 打开数据库连接
db = pymysql.connect("localhost","testuser","test123","TESTDB" )

# 使用cursor()方法获取操作游标
cursor = db.cursor()

# SQL 插入语句
sql = "INSERT INTOEMPLOYEE(FIRST_NAME, \
      
LAST_NAME, AGE, SEX, INCOME) \
       VALUES ('%s', '%s', '%d', '%c','%d' )" % \
      
('Mac', 'Mohan', 20, 'M', 2000)
try:
   #执行sql语句
   cursor.execute(sql)
   #执行sql语句
   db.commit()
except:
   #发生错误时回滚
   db.rollback()

# 关闭数据库连接
db.close()

以下代码使用变量向SQL语句中传递参数:

..................................
user_id = "test123"
password = "password"

con.execute('insert into Login values("%s", "%s")' % \
            
(user_id, password))
..................................

数据库查询操作

Python查询Mysql使用 fetchone() 方法获取单条数据, 使用fetchall() 方法获取多条数据。

·       fetchone(): 该方法获取下一个查询结果集。结果集是一个对象

·       fetchall(): 接收全部的返回结果行.

·       rowcount: 这是一个只读属性,并返回执行execute()方法后影响的行数。

实例:

查询EMPLOYEE表中salary(工资)字段大于1000的所有数据:

#!/usr/bin/python3

importpymysql

# 打开数据库连接
db = pymysql.connect("localhost","testuser","test123","TESTDB" )

# 使用cursor()方法获取操作游标
cursor = db.cursor()

# SQL 查询语句
sql = "SELECT* FROM EMPLOYEE \
      
WHERE INCOME > '%d'" % (1000)
try:
   #执行SQL语句
   cursor.execute(sql)
   #获取所有记录列表
   results = cursor.fetchall()
   for row in results:
      fname = row[0]
      lname = row[1]
      age = row[2]
      sex = row[3]
      income = row[4]
       #打印结果
      print("fname=%s,lname=%s,age=%d,sex=%s,income=%d"% \
            
(fname, lname, age, sex, income ))
except:
   print("Error:unable to fecth data")

# 关闭数据库连接
db.close()

以上脚本执行结果如下:

fname=Mac, lname=Mohan, age=20, sex=M,income=2000

数据库更新操作

更新操作用于更新数据表的的数据,以下实例将 TESTDB表中的 SEX 字段全部修改为 'M'AGE 字段递增1

#!/usr/bin/python3

importpymysql

# 打开数据库连接
db = pymysql.connect("localhost","testuser","test123","TESTDB" )

# 使用cursor()方法获取操作游标
cursor = db.cursor()

# SQL 更新语句
sql = "UPDATEEMPLOYEE SET AGE = AGE + 1
                         
WHERE SEX ='%c'" % ('M')
try:
   #执行SQL语句
   cursor.execute(sql)
   #提交到数据库执行
   db.commit()
except:
   #发生错误时回滚
   db.rollback()

# 关闭数据库连接
db.close()

删除操作

删除操作用于删除数据表中的数据,以下实例演示了删除数据表 EMPLOYEE AGE 大于 20 的所有数据:

#!/usr/bin/python3

importpymysql

# 打开数据库连接
db = pymysql.connect("localhost","testuser","test123","TESTDB" )

# 使用cursor()方法获取操作游标
cursor = db.cursor()

# SQL 删除语句
sql = "DELETEFROM EMPLOYEE WHERE AGE > '%d'" %(20)
try:
   #执行SQL语句
   cursor.execute(sql)
   #提交修改
   db.commit()
except:
   #发生错误时回滚
   db.rollback()

# 关闭连接
db.close()

执行事务

事务机制可以确保数据一致性。

事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。

·       原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。

·       一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

·       隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

·       持久性(durability)。持续性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

Python DB API 2.0 的事务提供了两个方法 commit rollback

实例

# SQL删除记录语句
sql = "DELETEFROM EMPLOYEE WHERE AGE > '%d'" %(20)
try:
   #执行SQL语句
   cursor.execute(sql)
   #向数据库提交
   db.commit()
except:
   #发生错误时回滚
   db.rollback()

对于支持事务的数据库,Python数据库编程中,当游标建立之时,就自动开始了一个隐形的数据库事务。

commit()方法游标的所有更新操作,rollback()方法回滚当前游标的所有操作。每一个方法都开始了一个新的事务。

错误处理

DB API中定义了一些数据库操作的错误及异常,下表列出了这些错误和异常:

异常

描述

Warning

当有严重警告时触发,例如插入数据是被截断等等。必须是 StandardError 的子类。

Error

警告以外所有其他错误类。必须是 StandardError 的子类。

InterfaceError

当有数据库接口模块本身的错误(而不是数据库的错误)发生时触发。必须是Error的子类。

DatabaseError

和数据库有关的错误发生时触发。必须是Error的子类。

DataError

当有数据处理时的错误发生时触发,例如:除零错误,数据超范围等等。必须是DatabaseError的子类。

OperationalError

指非用户控制的,而是操作数据库时发生的错误。例如:连接意外断开、数据库名未找到、事务处理失败、内存分配错误等等操作数据库是发生的错误。必须是DatabaseError的子类。

IntegrityError

完整性相关的错误,例如外键检查失败等。必须是DatabaseError子类。

InternalError

数据库的内部错误,例如游标(cursor)失效了、事务同步失败等等。必须是DatabaseError子类。

ProgrammingError

程序错误,例如数据表(table)没找到或已存在、SQL语句语法错误、参数数量错误等等。必须是DatabaseError的子类。

NotSupportedError

不支持错误,指使用了数据库不支持的函数或API等。例如在连接对象上使用.rollback()函数,然而数据库并不支持事务或者事务已关闭。必须是DatabaseError的子类。

Python3异步IO

2017318

5:36

协程

2017320

14:14

在学习异步IO模型前,我们先来了解协程。

协程,又称微线程,纤程。英文名Coroutine

协程的概念很早就提出来了,但直到最近几年才在某些语言(如Lua)中得到广泛应用。

子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。

所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。

子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。

协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。比如子程序A、B:

def A():
   
print('1')
   
print('2')
   
print('3')

def B():
   
print('x')
   
print('y')
   
print('z')

假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:

1
2
x
y
3
z

但是在A中是没有调用B的,所以协程的调用比函数调用理解起来要难一些。

看起来A、B的执行有点像多线程,但协程的特点在于是一个线程执行,那和多线程比,协程有何优势?

最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

Python对协程的支持是通过generator实现的。

在generator中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。

但是Pythonyield不但可以返回一个值,它还可以接收调用者发出的参数。

来看例子:

传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。

如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:

def consumer():
   
r = ''
    while True:
       
n = yield r
       
if not n:
           
return
        print('[CONSUMER] Consuming %s...' % n)
       
r = '200 OK'

def produce(c):
   
c.send(None)
   
n = 0
    while n < 5:
       
n = n + 1
        print('[PRODUCER] Producing %s...' % n)
       
r = c.send(n)
        print('[PRODUCER] Consumer return: %s' % r)
   
c.close()

c =consumer()
produce(c)

执行结果:

[PRODUCER]Producing 1...
[CONSUMER]
Consuming 1...
[PRODUCER]
Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER]
Consuming 2...
[PRODUCER]
Consumer return: 200 OK
[PRODUCER] Producing 3...
[CONSUMER]
Consuming 3...
[PRODUCER]
Consumer return: 200 OK
[PRODUCER] Producing 4...
[CONSUMER]
Consuming 4...
[PRODUCER]
Consumer return: 200 OK
[PRODUCER] Producing 5...
[CONSUMER]
Consuming 5...
[PRODUCER]
Consumer return: 200 OK

注意到consumer函数是一个generator,把一个consumer传入produce后:

1.   首先调用c.send(None)启动生成器;

2.   然后,一旦生产了东西,通过c.send(n)切换到consumer执行;

3.  consumer通过yield拿到消息,处理,又通过yield把结果传回;

4.  produce拿到consumer处理的结果,继续生产下一条消息;

5.  produce决定不生产了,通过c.close()关闭consumer,整个过程结束。

整个流程无锁,由一个线程执行,produceconsumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。

最后套用Donald Knuth的一句话总结协程的特点:

“子程序就是协程的一种特例。”

aiohttp

2017320

14:15

asyncio可以实现单线程并发IO操作。如果仅用在客户端,发挥的威力不大。如果把asyncio用在服务器端,例如Web服务器,由于HTTP连接就是IO操作,因此可以用单线程+coroutine实现多用户的高并发支持。

asyncio实现了TCPUDPSSL等协议,aiohttp则是基于asyncio实现的HTTP框架。

我们先安装aiohttp

pipinstall aiohttp

然后编写一个HTTP服务器,分别处理以下URL:

·       / - 首页返回b'<h1>Index</h1>'

·       /hello/{name} - 根据URL参数返回文本hello, %s!

代码如下:

import asyncio

fromaiohttp import web

async def index(request):
   
await asyncio.sleep(0.5)
   
return web.Response(body=b'<h1>Index</h1>')

async def hello(request):
   
await asyncio.sleep(0.5)
   
text = '<h1>hello, %s!</h1>' % request.match_info['name']
   
return web.Response(body=text.encode('utf-8'))

async def init(loop):
   
app = web.Application(loop=loop)
    app.router.add_route('GET', '/', index)
   
app.router.add_route('GET', '/hello/{name}', hello)
   
srv = await loop.create_server(app.make_handler(),'127.0.0.1', 8000)
   
print('Server started at http://127.0.0.1:8000...')
   
return srv

loop =asyncio.get_event_loop()
loop.run_until_complete(init(loop))
loop.run_forever()

注意aiohttp的初始化函数init()也是一个coroutineloop.create_server()则利用asyncio创建TCP服务

 

async/await

2017320

14:15

asyncio提供的@asyncio.coroutine可以把一个generator标记为coroutine类型,然后在coroutine内部用yield from调用另一个coroutine实现异步操作。

为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法asyncawait,可以让coroutine的代码更简洁易读。

请注意,asyncawait是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换:

1.   @asyncio.coroutine替换为async

2.   yield from替换为await

让我们对比一下上一节的代码:

@asyncio.coroutine
def hello():
   
print("Hello world!")
   
r = yield from asyncio.sleep(1)
   
print("Hello again!")

用新语法重新编写如下:

async def hello():
   
print("Hello world!")
   
r = await asyncio.sleep(1)
   
print("Hello again!")

剩下的代码保持不变。

小结

Python从3.5版本开始为asyncio提供了asyncawait的新语法;

注意新语法只能用在Python 3.5以及后续版本,如果使用3.4版本,则仍需使用上一节的方案。

练习

将上一节的异步获取sina、sohu和163的网站首页源码用新语法重写并运行。

asyncio

2017320

14:15

asyncioPython 3.4版本引入的标准库,直接内置了对异步IO的支持。

asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO

asyncio实现Helloworld代码如下:

import asyncio

@asyncio.coroutine
def hello():
   
print("Hello world!")
   
# 异步调用asyncio.sleep(1):
    r = yield from asyncio.sleep(1)
   
print("Hello again!")

# 获取EventLoop:
loop = asyncio.get_event_loop()
# 执行coroutine
loop.run_until_complete(hello())
loop.close()

@asyncio.coroutine把一个generator标记为coroutine类型,然后,我们就把这个coroutine扔到EventLoop中执行。

hello()会首先打印出Hello world!,然后,yield from语法可以让我们方便地调用另一个generator。由于asyncio.sleep()也是一个coroutine,所以线程不会等待asyncio.sleep(),而是直接中断并执行下一个消息循环。当asyncio.sleep()返回时,线程就可以从yield from拿到返回值(此处是None),然后接着执行下一行语句。

asyncio.sleep(1)看成是一个耗时1秒的IO操作,在此期间,主线程并未等待,而是去执行EventLoop中其他可以执行的coroutine了,因此可以实现并发执行。

我们用Task封装两个coroutine试试:

import threading
import asyncio

@asyncio.coroutine
def hello():
   
print('Hello world! (%s)' % threading.currentThread())
   
yield from asyncio.sleep(1)
   
print('Hello again! (%s)' % threading.currentThread())

loop =asyncio.get_event_loop()
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

观察执行过程:

Hello world! (<_MainThread(MainThread, started 140735195337472)>)
Hello world! (
<_MainThread(MainThread,started 140735195337472)>)
(
暂停约1)
Hello again! (
<_MainThread(MainThread,started 140735195337472)>)
Hello again! (
<_MainThread(MainThread,started 140735195337472)>)

由打印的当前线程名称可以看出,两个coroutine是由同一个线程并发执行的。

如果把asyncio.sleep()换成真正的IO操作,则多个coroutine就可以由一个线程并发执行。

我们用asyncio的异步网络连接来获取sina、sohu和163的网站首页:

import asyncio

@asyncio.coroutine
def wget(host):
   
print('wget %s...' % host)
   
connect =asyncio.open_connection(host, 80)
   
reader, writer = yield from connect
   
header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
   
writer.write(header.encode('utf-8'))
   
yield from writer.drain()
   
while True:
       
line = yield from reader.readline()
       
if line == b'\r\n':
           
break
        print('%s header > %s' % (host, line.decode('utf-8').rstrip()))
   
# Ignore the body, close the socket
    writer.close()

loop =asyncio.get_event_loop()
tasks = [wget(host)
for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

执行结果如下:

wget www.sohu.com...
wget
www.sina.com.cn...
wget
www.163.com...
(
等待一段时间)
(
打印出sohuheader)
www.sohu.com header > HTTP/1.1 200 OK
www.sohu.com header > Content-Type: text/html
...
(
打印出sinaheader)
www.sina.com.cn header > HTTP/1.1 200 OK
www.sina.com.cn header > Date: Wed, 20 May 2015 04:56:33GMT
...
(
打印出163header)
www.163.com header > HTTP/1.0 302 MovedTemporarily
www.163.com header > Server: Cdn Cache ServerV2.0
...

可见3个连接由一个线程通过coroutine并发完成。

小结

asyncio提供了完善的异步IO支持;

异步操作需要在coroutine中通过yield from完成;

多个coroutine可以封装成一组Task然后并发执行

猜你喜欢

转载自blog.csdn.net/weixin_40826349/article/details/80187388