├── A quick Python Tutorial.md ├── README.md ├── build ├── Python.pdf └── python_readlists.epub ├── content ├── 01.md ├── 02.md ├── 03.md ├── 04.md ├── 05.md ├── 06.md ├── 07.md ├── 08.md ├── 09.md ├── 10.md ├── Python小题目 针对快速教程.md ├── Python快速教程 尾声.md ├── Python快速教程总结.md ├── Python标准库——走马观花.md ├── Python标准库的学习准备.md ├── Python简史.md ├── Python补充01 序列的方法.md ├── STL01.md ├── STL02.md ├── STL03.md ├── STL04.md ├── STL05.md ├── STL06.md ├── STL07.md ├── STL08.md ├── STL09.md ├── STL10.md ├── STL11.md ├── STL12.md ├── STL13 循环器 (itertools).md ├── intermediate01.md ├── intermediate02.md ├── intermediate03.md ├── intermediate04.md ├── intermediate05.md ├── intermediate06.md ├── intermediate07.md ├── intermediate08.md ├── intermediate09.md ├── 深入01 特殊方法与多范式.md ├── 深入02 上下文管理器.md ├── 深入04 闭包.md ├── 深入05 装饰器.md ├── 深入06 Python的内存管理.md ├── 网络01 原始Python服务器.md ├── 网络02 Python服务器进化.md ├── 补充02 Python小技巧.md ├── 补充03 Python内置函数清单.md ├── 补充05 字符串格式化 (%操作符).md ├── 补充06 Python之道.md ├── 被解放的姜戈01 初试天涯.md ├── 被解放的姜戈02 庄园疑云.md ├── 被解放的姜戈03 所谓伊人.md ├── 被解放的姜戈04 各取所需.md ├── 被解放的姜戈05 黑面管家.md ├── 被解放的姜戈06 假作真时.md └── 被解放的姜戈07 马不停蹄.md └── 内容 └── Python标准库——走马观花 /A quick Python Tutorial.md: -------------------------------------------------------------------------------- 1 | 说明 2 | 3 | 1. 教程将专注于Python基础,语法基于Python 2.7 (我会提醒Python 3.x中有变化的地方,以方便读者适应3.X的情况)。测试环境为Linux, 将不会使用到标准库之外的模块。 4 | 5 | 2. 我将专注于python的主干,以便读者能以最快时间对python形成概念。 6 | 7 | 3. Linux命令行将以 $ 开始,比如 $ls, $python 8 | 9 | 4. python命令行将以 >>> 开始,比如 >>>print 'Hello World!' 10 | 11 | 5. 注释会以 # 开始 12 | 13 | 14 | 15 | 建议 16 | 17 | 1. 将教程中的命令敲到python中看看效果 18 | 19 | 2. 你可以在了解之后立即去查看相关更完备的内容 (比如查阅官方文档) 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Python-Tutorial-Vamei 2 | ===================== 3 | 4 | A Quick Tutorial of Python 5 | 6 | 7 | -------------------------------------------------------------------------------- /build/Python.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vamei/Python-Tutorial-Vamei/e5374d190d9ec9c411b2077afb800deb625d4027/build/Python.pdf -------------------------------------------------------------------------------- /build/python_readlists.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vamei/Python-Tutorial-Vamei/e5374d190d9ec9c411b2077afb800deb625d4027/build/python_readlists.epub -------------------------------------------------------------------------------- /content/01.md: -------------------------------------------------------------------------------- 1 | # Python基础01 Hello World! 2 | 3 | 简单的“Hello World!” 4 | 5 | ## Python命令行 6 | 7 | 假设你已经安装好了Python,那么在Linux命令行输入: 8 | ```bash 9 | $python 10 | ``` 11 | 12 | 将直接进入python。然后在命令行提示符>>>后面输入: 13 | 14 | ```quote 15 | >>>print 'Hello World!' 16 | ``` 17 | 18 | 可以看到,随后在屏幕上输出: 19 | 20 | ```quote 21 | Hello World! 22 | ``` 23 | 24 | `print`是一个常用关键字(keyword),其功能就是输出。 25 | 26 | 在Python 3.x中,`print`的语法会有所变化,作为一个函数使用,所以上面应写成: 27 | 28 | ```python 29 | print('Hello World!') # Python 3.x 语法 30 | ``` 31 | 32 | ## 写一段小程序 33 | 34 | 另一个使用Python的方法,是写一个Python程序。用文本编辑器写一个.py结尾的文件,比如说hello.py。 35 | 36 | 在hello.py中写入如下,并保存: 37 | 38 | ```python 39 | print 'Hello World!' # Python 2.x 语法 40 | ``` 41 | 42 | 退出文本编辑器,然后在命令行输入: 43 | 44 | ```bash 45 | $python hello.py 46 | ``` 47 | 48 | 来运行hello.py,可以看到Python随后输出。 49 | 50 | ```quote 51 | Hello World! 52 | ``` 53 | 54 | ## 总结 55 | 56 | `print`:终端输出内容。 57 | 58 | 命令行模式:运行Python,在命令行输入命令并执行。 59 | 60 | 程序模式:写一段Python程序并运行。 -------------------------------------------------------------------------------- /content/02.md: -------------------------------------------------------------------------------- 1 | #Python基础02 基本数据类型 2 | 3 | 单的数据类型以及赋值 4 | 5 | ##变量不需要声明 6 | 7 | Python的变量不需要声明,你可以直接输入: 8 | 9 | ```python 10 | a = 10 11 | ``` 12 | 13 | 那么你的内存里就有了一个变量`a`,它的值是`10`,它的类型是`int`(整数)。 14 | 15 | 在此之前你不需要做什么特别的声明,而数据类型是Python自动决定的。 16 | 17 | ```python 18 | print a 19 | print type(a) 20 | ``` 21 | 22 | 那么会有如下输出: 23 | 24 | ```quote 25 | 10 26 | 27 | ``` 28 | 29 | 这里,我们学到一个内置函数`type()`,用以查询变量的类型。 30 | 31 | ##回收变量名 32 | 33 | 如果你想让`a`存储不同的数据,你不需要删除原有变量就可以直接赋值。 34 | 35 | ```python 36 | a = 1.3 37 | print a, type(a) 38 | ``` 39 | 40 | 会有如下输出 41 | 42 | ```quote 43 | 1.3 44 | 45 | ``` 46 | 47 | 我们看到`print`的另一个用法,也就是`print`后跟多个输出,以逗号分隔。 48 | 49 | ##基本数据类型 50 | ```python 51 | a = 10 # int 整数 52 | b = 1.3 # float 浮点数 53 | c = True # bool 真值(True/False) 54 | d = 'Hello!' # str 字符串 55 | ``` 56 | 57 | 以上是最常用的数据类型,对于字符串来说,也可以用双引号。 58 | 59 | (此外还有分数,字符,复数等其他数据类型,有兴趣的可以学习一下。) 60 | 61 | ##总结 62 | 63 | 变量不需要声明,不需要删除,可以直接回收适用。 64 | 65 | `type()`: 查询数据类型。 66 | 67 | 整数、浮点数、真值、字符串。 68 | -------------------------------------------------------------------------------- /content/03.md: -------------------------------------------------------------------------------- 1 | #Python基础03 序列 2 | 3 | ##sequence 序列 4 | 5 | sequence(序列)是一组有顺序的元素的集合。 6 | 7 | (严格的说,是对象的集合,但鉴于我们还没有引入“对象”概念,暂时说元素。) 8 | 9 | 序列可以包含一个或多个元素,也可以没有任何元素。 10 | 11 | 我们之前所说的基本数据类型,都可以作为序列的元素。元素还可以是另一个序列,以及我们以后要介绍的其他对象。 12 | 13 | 序列有两种:`tuple`(定值表,也有翻译为元组)和`list`(表)。 14 | 15 | ```python 16 | s1 = (2, 1.3, 'love', 5.6, 9, 12, False) # s1是一个tuple 17 | s2 = [True, 5, 'smile'] # s2是一个list 18 | print s1, type(s1) 19 | print s2, type(s2) 20 | ``` 21 | 22 | `tuple`和`list`的主要区别在于,一旦建立,`tuple`的各个元素不可再变更,而`list`的各个元素可以再变更。 23 | 24 | 一个序列也可以作为另一个序列的元素,即,可以嵌套: 25 | 26 | ```python 27 | s3 = [1, [3, 4, 5]] 28 | ``` 29 | 30 | 不写任何元素即为空序列: 31 | 32 | ```python 33 | s4 = [] 34 | ``` 35 | 36 | ##元素的引用 37 | 38 | 序列元素的下标从`0`开始: 39 | 40 | ```python 41 | print s1[0] 42 | print s2[2] 43 | print s3[1][2] # 嵌套序列 44 | ``` 45 | 46 | 由于`list`的元素可变更,你可以对`list`的某个元素赋值: 47 | 48 | ```python 49 | s2[1] = 3.0 50 | print s2 51 | ``` 52 | 53 | 如果你对`tuple`做这样的操作,会得到错误提示。 54 | 55 | 所以,可以看到,序列的引用通过`s[索引]`实现, 索引即下标。 56 | 57 | ##其他引用方式 58 | 59 | 范围引用: 基本样式[下限:上限:步长] 60 | 61 | ```python 62 | print s1[:5] # 从开始到下标4(下标为5的元素不包括在内。即,前闭后开区间。) 63 | print s1[2:] # 从下标2开始到最后的所有元素 64 | print s1[0:5:2] # 从下标0到下标4(下标5不包括在内),每隔2取一个元素(下标为0,2,4的元素) 65 | print s1[2:0:-1] # 从下标2到下标1(步长-1,即从后向前) 66 | ``` 67 | 68 | 从上面可以看到,在范围引用的时候,如果写明上限,那么这个上限本身不包括在内。 69 | 70 | 尾部元素引用: 71 | 72 | ```python 73 | print s1[-1] # 序列最后一个元素 74 | print s1[-3] # 序列倒数第三个元素 75 | ``` 76 | 77 | 同样,如果`s1[0:-1]`, 那么最后一个元素不会被引用(再一次,不包括上限元素本身)。 78 | 79 | ##字符串是元组 80 | 81 | 字符串是一种特殊的元素,因此可以执行元组的相关操作。 82 | 83 | ```python 84 | str = 'abcdef' 85 | print str[2:4] 86 | ``` 87 | 88 | ##总结 89 | 90 | `tuple`元素不可变,`list`元素可变。 91 | 92 | 序列的引用:`s[2]`,`s[1:8:2]`。 93 | 94 | 字符串是一种`tuple`。 95 | -------------------------------------------------------------------------------- /content/04.md: -------------------------------------------------------------------------------- 1 | #Python基础04 运算 2 | 3 | Python的运算符和其他语言类似。 4 | 5 | (我们暂时只了解这些运算符的基本用法,方便我们展开后面的内容,高级应用暂时不介绍。) 6 | 7 | ##数学运算 8 | 9 | ```python 10 | print 1 + 9 # 加法 11 | print 1.3 - 4 # 减法 12 | print 3 * 5 # 乘法 13 | print 4.5 / 1.5 # 除法 14 | print 3 ** 2 # 乘方 15 | print 10 % 3 # 求余数(取模运算:-3 % 2 的结果为 1) 16 | ``` 17 | 18 | ##分支 19 | 20 | 判断是真还是假,返回True/False。 21 | 22 | ```python 23 | print 5 == 6 # 相等 24 | print 8.0 != 8.0 # 不等 25 | print 3 < 3 # 小于 26 | print 3 <= 3 # 小于或等于 27 | print 4 > 5 # 大于 28 | print 4 >= 0 # 大于或等于 29 | print 5 in [1, 3, 5] # 5是列表(list)[1,3,5]中的一个元素 30 | ``` 31 | 32 | (还有`is`、`is not`等,暂时不深入) 33 | 34 | ##逻辑运算 35 | 36 | `True`/`False`之间的运算: 37 | 38 | ```python 39 | print True and True, True and False # and: “与”运算,两者都为真才是真。 40 | print True or False # or: ”或“运算,其中之一为真即为真。 41 | print not True # not: “非”运算,取反。 42 | print 5 == 6 or 3 >= 3 # 结合上一小节内容 43 | ``` 44 | 45 | ##Pythonic 数据交换 46 | 47 | 随着时间的推移,Python语言不断演进,社区不断成长,涌现了许多关于如何正确地使用 Python 的 ideas。 48 | 49 | Pythonic可以理解为:只有Python能做到的,区别于其他语言的写法,其实就是Python的惯用和特有写法。后续每节会插入这样的惯用法。 50 | 51 | 在Python中进行两个值的交换,推荐这样写: 52 | 53 | ```python 54 | a = 3 55 | b = 5 56 | a,b = b,a 57 | ``` 58 | 59 | 而在其它的语言中,为了交换两个值,我们通常需要借助第三个临时变量: 60 | 61 | ```python 62 | tmp = a 63 | a = b 64 | b = tmp 65 | ``` 66 | 67 | ##总结 68 | 69 | 数学运算: `+`、`-`、`*`、`/`、`**`、`%`。 70 | 71 | 比较及判断:`==`、`!=`、`>`、`>=`、`<`、`<=`;`in`。 72 | 73 | 逻辑运算: `and`、`or`、`not`。 74 | -------------------------------------------------------------------------------- /content/05.md: -------------------------------------------------------------------------------- 1 | #Python基础05 缩进和选择 2 | 3 | ##缩进 4 | 5 | Python最具特色的就是用缩进来写模块。 6 | 7 | 我们下面以`if`选择结构来举例。`if`后面跟随条件,如果条件成立,则执行归属于`if`的一些语句。 8 | 9 | 先看C语言的表达方式(注意,这是C,不是Python!) 10 | 11 | ```c 12 | if ( i > 0 ) 13 | { 14 | x = 1; 15 | y = 2; 16 | } 17 | ``` 18 | 19 | 这个语句是说,如果`i > 1`成立的话,我们将进行括号中所包括的两个赋值操作。 20 | 21 | 括号中包含的就是块操作,它表明了其中的语句隶属于`if`。 22 | 23 | 在Python中,同样的目的,这段话是这样的: 24 | 25 | ```python 26 | if i > 0: 27 | x = 1 28 | y = 2 29 | ``` 30 | 31 | 在Python中, 去掉了`i > 0`周围的括号,去除了每个语句句尾的分号,表示块的花括号也消失了。 32 | 33 | 多出来了`if ...`之后的`:`(冒号),还有就是`x = 1`和`y = 2`前面有四个空格的缩进。 34 | 35 | 通过缩进,Python识别出这两个语句是隶属的`if`。 36 | 37 | Python这样设计的理由很简单,就是为了程序好看。 38 | 39 | ##if语句 40 | 41 | 我们写一个完整的程序,命名为 ifDemo.py。这个程序用于实现`if`结构。 42 | 43 | ```python 44 | i = 1 45 | x = 1 46 | if i > 0: 47 | x = x + 1 48 | print x 49 | ``` 50 | 51 | ```bash 52 | $python ifDemo.py # 运行 53 | ``` 54 | 程序运行到`if`的时候,判断结果为`True`,因此执行`x = x + 1`。 55 | 56 | `print x`语句没有缩进,所以不属于`if`块。 57 | 58 | 如果将第一句改成`i = -1`,判断结果为`False`。因`x = x + 1`隶属于`if`, 不会执行。 59 | 60 | `print x`没有缩进,不属于`if`块。依然执行。 61 | 62 | 这种以四个空格的缩进来表示隶属关系的书写方式,我们以后还会看到。Python很强调程序的可读性。强制缩进的要求让程序员写出整洁的程序。 63 | 64 | 复杂一些的`if`分支: 65 | 66 | ```python 67 | i = 1 68 | if i > 0: 69 | print 'positive i' 70 | i = i + 1 71 | elif i == 0: 72 | print 'i is 0' 73 | i = i * 10 74 | else: 75 | print 'negative i' 76 | i = i - 1 77 | 78 | print 'new i:', i 79 | ``` 80 | 81 | 这里有三个代码块,分别以`if`、`elif`、`else`引领。 82 | Python检测条件时,如果发现`if`的条件为`False`,那么跳过这个代码块,继续检测下一个`elif`的条件。如果所有的条件都为`False`,那么执行`else`块。 83 | 84 | 上面的结构,实际上将程序分出三个分支。程序根据条件,只执行三个分支中的一个。 85 | 86 | 整个`if`可以放在另一个`if`语句中,也就是`if`语句的嵌套使用: 87 | 88 | ```python 89 | i = 5 90 | if i > 1: 91 | print 'i bigger than 1' 92 | print 'good' 93 | if i > 2: 94 | print 'i bigger than 2' 95 | print 'even better' 96 | ``` 97 | 98 | `if i > 2:` 后面的块相对于该`if`缩进了四个空格,以表明其隶属于内层的`if`,而不是外层的`if`。 99 | 100 | 此外,不同于其他语言。Python的`if`块中必须要有语句,不能留空。 101 | 102 | 如果的确不需要执行任何操作,用关键字`pass`来填充。表示不进行任何操作。 103 | 104 | ```python 105 | i = 5 106 | if i == 5: 107 | pass 108 | 109 | print 'Nothing did.' 110 | ``` 111 | 112 | ##总结 113 | 114 | `if`语句之后需要写冒号。 115 | 116 | 以四个空格的缩进来表示隶属关系, Python中不能随意缩进。 117 | 118 | `if`完整语法: 119 | ```quote 120 | if <条件1>: 121 | statement 122 | elif <条件2>: 123 | statement 124 | elif <条件3>: 125 | statement 126 | else: 127 | statement 128 | ``` -------------------------------------------------------------------------------- /content/06.md: -------------------------------------------------------------------------------- 1 | #Python基础06 循环 2 | 3 | 循环用于重复执行一些程序块。从上一讲的选择结构,我们已经看到了如何用缩进来表示程序块的隶属关系。循环也会用到类似的写法。 4 | 5 | ##for循环 6 | 7 | `for`循环需要预先设定好循环的次数(n),然后执行隶属于`for`的语句n次。 8 | 9 | 基本构造是: 10 | 11 | ```quote 12 | for 元素 in 序列: 13 | statement 14 | ``` 15 | 16 | 举例来说,我们编辑一个叫forDemo.py的文件: 17 | 18 | ```python 19 | for a in [3, 4.4, 'life']: 20 | print a 21 | ``` 22 | 23 | 这个循环就是每次从表(`list`)[3, 4.4, 'life'] 中取出一个元素(回忆:表是一种序列),然后将这个元素赋值给`a`,并执行隶属于`for`的操作`print`。 24 | 25 | 介绍一个新的Python函数`range()`,来帮助你建立表。 26 | 27 | ```python 28 | # Python 2.x 29 | idx = range(5) # idx为list类型 30 | print idx 31 | ``` 32 | 33 | 可以看到`idx`是`[0, 1, 2, 3, 4]`。 34 | 35 | 这个函数的功能是新建一个表。这个表的元素都是整数,从0开始,下一个元素比前一个大1,直到函数中所写的上限(不包括该上限本身)。 36 | 37 | (关于`range()`,还有丰富用法,有兴趣可以查阅) 38 | 39 | Python 3.x中,`range()`则返回一个`range`类型,可以通过`list()`函数来获得列表`list`。 40 | 41 | ```python 42 | # Python 3.x 43 | idx = list(range(5)() # list()将range类转换为list类 44 | print idx 45 | ``` 46 | 47 | 举例: 48 | 49 | ```python 50 | for a in range(10): 51 | print a ** 2 52 | ``` 53 | 54 | ##while循环 55 | 56 | while的用法是: 57 | 58 | ```quote 59 | while 条件: 60 | statement 61 | ``` 62 | 63 | while会不停地循环执行隶属于它的语句,直到条件为假`False`。 64 | 65 | 举例: 66 | 67 | ```python 68 | while i < 10: 69 | print i 70 | i = i + 1 71 | ``` 72 | 73 | 中断循环 74 | 75 | `continue`:在循环的某一次执行中,如果遇到`continue`, 那么跳过这一次执行,进行下一次循环。 76 | `break` :停止并跳出整个循环。 77 | 78 | ```python 79 | for i in range(10): 80 | if i == 2: 81 | continue 82 | print i 83 | ``` 84 | 85 | 当循环执行到`i == 2`的时候,`if`条件成立,触发`continue`,跳过本次执行(不执行`print`),继续进行下一次执行(i = 3)。 86 | 87 | 88 | ```python 89 | for i in range(10): 90 | if i == 2: 91 | break 92 | print i 93 | ``` 94 | 当循环执行到`i == 2`的时候,`if`条件成立,触发`break`, 整个循环停止。 95 | 96 | ##总结 97 | 98 | `range()` 99 | 100 | ```quote 101 | for 元素 in 序列: 102 | ``` 103 | 104 | ```quote 105 | while 条件: 106 | ``` 107 | 108 | ```quote 109 | continue 110 | break 111 | ``` -------------------------------------------------------------------------------- /content/07.md: -------------------------------------------------------------------------------- 1 | #Python基础07 函数 2 | 3 | 函数最重要的目的是方便我们重复使用相同的一段程序。 4 | 5 | 将一些操作隶属于一个函数,以后你想实现相同的操作的时候,只用调用函数名就可以,而不需要重复敲所有的语句。 6 | 7 | ##函数的定义 8 | 9 | 首先,我们要定义一个函数, 以说明这个函数的功能。 10 | ```python 11 | def square_sum(a,b): 12 | c = a**2 + b**2 13 | return c 14 | ``` 15 | 这个函数的功能是求两个数的平方和。 16 | 17 | 首先,def,这个关键字通知python:我在定义一个函数。square_sum是函数名。 18 | 19 | 括号中的a, b是函数的参数,是对函数的输入。参数可以有多个,也可以完全没有(但括号要保留)。 20 | 21 | 我们已经在循环和选择中见过冒号和缩进来表示的隶属关系。 22 | 23 | c = a**2 + b**2 # 这一句是函数内部进行的运算 24 | 25 | return c # 返回c的值,也就是输出的功能。Python的函数允许不返回值,也就是不用return。 26 | 27 | return可以返回多个值,以逗号分隔。相当于返回一个tuple(定值表)。 28 | 29 | return a,b,c # 相当于 return (a,b,c) 30 | 31 | 32 | 33 | 在Python中,当程序执行到return的时候,程序将停止执行函数内余下的语句。return并不是必须的,当没有return, 或者return后面没有返回值时,函数将自动返回None。None是Python中的一个特别的数据类型,用来表示什么都没有,相当于C中的NULL。None多用于关键字参数传递的默认值。 34 | 35 | 36 | 37 | ##函数调用和参数传递 38 | 39 | 定义过函数后,就可以在后面程序中使用这一函数 40 | ```python 41 | print square_sum(3,4) 42 | ``` 43 | Python通过位置,知道3对应的是函数定义中的第一个参数a, 4对应第二个参数b,然后把参数传递给函数square_sum。 44 | 45 | (Python有丰富的参数传递方式,还有关键字传递、表传递、字典传递等,基础教程将只涉及位置传递) 46 | 47 | 函数经过运算,返回值25, 这个25被print打印出来。 48 | 49 | 50 | 51 | 我们再看下面两个例子 52 | 53 | ```python 54 | a = 1 55 | 56 | def change_integer(a): 57 | a = a + 1 58 | return a 59 | 60 | print change_integer(a) 61 | print a 62 | 63 | #===(Python中 "#" 后面跟的内容是注释,不执行 ) 64 | 65 | b = [1,2,3] 66 | 67 | def change_list(b): 68 | b[0] = b[0] + 1 69 | return b 70 | 71 | print change_list(b) 72 | print b 73 | ``` 74 | 第一个例子,我们将一个整数变量传递给函数,函数对它进行操作,但原整数变量a不发生变化。 75 | 76 | 第二个例子,我们将一个表传递给函数,函数进行操作,原来的表b发生变化。 77 | 78 | 对于基本数据类型的变量,变量传递给函数后,函数会在内存中复制一个新的变量,从而不影响原来的变量。(我们称此为值传递) 79 | 80 | 但是对于表来说,表传递给函数的是一个指针,指针指向序列在内存中的位置,在函数中对表的操作将在原有内存中进行,从而影响原有变量。 (我们称此为指针传递) 81 | 82 | 83 | 84 | ##总结 85 | ```python 86 | def function_name(a,b,c): 87 | statement 88 | return something # return不是必须的 89 | ``` 90 | 函数的目的: 提高程序的重复可用性。 91 | 92 | return None 93 | 94 | 通过位置,传递参数。 95 | 96 | 基本数据类型的参数:值传递 97 | 98 | 表作为参数:指针传递 99 | 100 | 101 | 102 | 练习: 103 | 104 | 写一个判断闰年的函数,参数为年、月、日。若是是闰年,返回True 105 | -------------------------------------------------------------------------------- /content/08.md: -------------------------------------------------------------------------------- 1 | #Python基础08 面向对象的基本概念 2 | 3 | Python使用类(class)和对象(object),进行面向对象(object-oriented programming,简称OOP)的编程。 4 | 5 | 面向对象的最主要目的是提高程序的重复使用性。我们这么早切入面向对象编程的原因是,Python的整个概念是基于对象的。了解OOP是进一步学习Python的关键。 6 | 7 | 下面是对面向对象的一种理解,基于分类。 8 | 9 | ##相近对象,归为类 10 | 11 | 在人类认知中,会根据属性相近把东西归类,并且给类别命名。比如说,鸟类的共同属性是有羽毛,通过产卵生育后代。任何一只特别的鸟都在鸟类的原型基础上的。 12 | 13 | 面向对象就是模拟了以上人类认知过程。在Python语言,为了听起来酷,我们把上面说的“东西”称为对象(object)。 14 | 15 | 先定义鸟类 16 | ```python 17 | class Bird(object): 18 | have_feather = True 19 | way_of_reproduction = 'egg' 20 | ``` 21 | 我们定义了一个类别(class),就是鸟(Bird)。在隶属于这个类比的语句块中,我们定义了两个变量,一个是有羽毛(have_feather),一个是生殖方式(way_of_reproduction),这两个变量对应我们刚才说的属性(attribute)。我们暂时先不说明括号以及其中的内容,记为问题1。 22 | 23 | 假设我养了一只小鸡,叫summer。它是个对象,且属于鸟类。使用前面定义的类: 24 | ```python 25 | summer = Bird() 26 | print summer.way_of_reproduction 27 | ``` 28 | 通过第一句创建对象,并说明summer是类别鸟中的一个对象,summer就有了鸟的类属性,对属性的引用是通过 对象.属性(object.attribute) 的形式实现的。 29 | 30 | 可怜的summer,你就是个有毛产的蛋货,好不精致。 31 | 32 | ##动作 33 | 34 | 日常认知中,我们在通过属性识别类别的时候,有时根据这个东西能做什么事情来区分类别。比如说,鸟会移动。这样,鸟就和房屋的类别区分开了。这些动作会带来一定的结果,比如移动导致位置的变化。 35 | 36 | 这样的一些“行为”属性为方法(method)。Python中通过在类的内部定义函数,来说明方法。 37 | 38 | ```python 39 | class Bird(object): 40 | have_feather = True 41 | way_of_reproduction = 'egg' 42 | def move(self, dx, dy): 43 | position = [0,0] 44 | position[0] = position[0] + dx 45 | position[1] = position[1] + dy 46 | return position 47 | 48 | summer = Bird() 49 | print 'after move:',summer.move(5,8) 50 | ``` 51 | 我们重新定义了鸟这个类别。鸟新增一个方法属性,就是表示移动的方法move。(我承认这个方法很傻,你可以在看过下一讲之后定义个有趣些的方法) 52 | 53 | (它的参数中有一个self,它是为了方便我们引用对象自身。方法的第一个参数必须是self,无论是否用到。有关self的内容会在下一讲展开) 54 | 55 | 另外两个参数,dx, dy表示在x、y两个方向移动的距离。move方法会最终返回运算过的position。 56 | 57 | 在最后调用move方法的时候,我们只传递了dx和dy两个参数,不需要传递self参数(因为self只是为了内部使用)。 58 | 59 | 我的summer可以跑了。 60 | 61 | ##子类 62 | 63 | 类别本身还可以进一步细分成子类 64 | 65 | 比如说,鸟类可以进一步分成鸡,大雁,黄鹂。 66 | 67 | 在OOP中,我们通过继承(inheritance)来表达上述概念。 68 | 69 | ```python 70 | class Chicken(Bird): 71 | way_of_move = 'walk' 72 | possible_in_KFC = True 73 | 74 | class Oriole(Bird): 75 | way_of_move = 'fly' 76 | possible_in_KFC = False 77 | 78 | summer = Chicken() 79 | print summer.have_feather 80 | print summer.move(5,8) 81 | ``` 82 | 新定义的鸡(Chicken)类的,增加了两个属性:移动方式(way_of_move),可能在KFC找到(possible_in_KFC) 83 | 84 | 在类定义时,括号里为了Bird。这说明,Chicken是属于鸟类(Bird)的一个子类,即Chicken继承自Bird。自然而然,Bird就是Chicken的父类。Chicken将享有Bird的所有属性。尽管我只声明了summer是鸡类,它通过继承享有了父类的属性(无论是变量属性have_feather还是方法属性move) 85 | 86 | 87 | 88 | 新定义的黄鹂(Oriole)类,同样继承自鸟类。在创建一个黄鹂对象时,该对象自动拥有鸟类的属性。 89 | 90 | 91 | 92 | 通过继承制度,我们可以减少程序中的重复信息和重复语句。如果我们分别定义两个类,而不继承自鸟类,就必须把鸟类的属性分别输入到鸡类和黄鹂类的定义中。整个过程会变得繁琐,因此,面向对象提高了程序的可重复使用性。 93 | 94 | (回到问题1, 括号中的object,当括号中为object时,说明这个类没有父类(到头了)) 95 | 96 | 97 | 98 | 将各种各样的东西分类,从而了解世界,从人类祖先开始,我们就在练习了这个认知过程,面向对象是符合人类思维习惯的。所谓面向过程,也就是执行完一个语句再执行下一个,更多的是机器思维。通过面向对象的编程,我们可以更方便的表达思维中的复杂想法。 99 | 100 | 101 | 102 | ##总结 103 | 104 | 将东西根据属性归类 ( 将object归为class ) 105 | 106 | 方法是一种属性,表示动作 107 | 108 | 用继承来说明父类-子类关系。子类自动具有父类的所有属性。 109 | 110 | self代表了根据类定义而创建的对象。 111 | 112 | 建立对一个对象: 对象名 = 类名() 113 | 114 | 引用对象的属性: object.attribute 115 | -------------------------------------------------------------------------------- /content/09.md: -------------------------------------------------------------------------------- 1 | #Python基础09 面向对象的进一步拓展 2 | 3 | 我们熟悉了对象和类的基本概念。我们将进一步拓展,以便能实际运用对象和类。 4 | 5 | ##调用类的其它信息 6 | 7 | 上一讲中提到,在定义方法时,必须有self这一参数。这个参数表示某个对象。对象拥有类的所有性质,那么我们可以通过self,调用类属性。 8 | 9 | ```python 10 | class Human(object): 11 | laugh = 'hahahaha' 12 | def show_laugh(self): 13 | print self.laugh 14 | def laugh_100th(self): 15 | for i in range(100): 16 | self.show_laugh() 17 | 18 | li_lei = Human() 19 | li_lei.laugh_100th() 20 | ``` 21 | 这里有一个类属性laugh。在方法show_laugh()中,通过self.laugh,调用了该属性的值。 22 | 23 | 还可以用相同的方式调用其它方法。方法show_laugh(),在方法laugh_100th中()被调用。 24 | 25 | 通过对象可以修改类属性值。但这是危险的。类属性被所有同一类及其子类的对象共享。类属性值的改变会影响所有的对象。 26 | 27 | ##__init__()方法 28 | 29 | __init__()是一个特殊方法(special method)。Python有一些特殊方法。Python会特殊的对待它们。特殊方法的特点是名字前后有两个下划线。 30 | 31 | 如果你在类中定义了__init__()这个方法,创建对象时,Python会自动调用这个方法。这个过程也叫初始化。 32 | ```python 33 | class happyBird(Bird): 34 | def __init__(self,more_words): 35 | print 'We are happy birds.',more_words 36 | 37 | summer = happyBird('Happy,Happy!') 38 | ``` 39 | 这里继承了Bird类,它的定义见上一讲。 40 | 41 | 42 | 43 | 屏幕上打印: 44 | > We are happy birds.Happy,Happy! 45 | 46 | 我们看到,尽管我们只是创建了summer对象,但__init__()方法被自动调用了。最后一行的语句(summer = happyBird...)先创建了对象,然后执行: 47 | ```python 48 | summer.__init__(more_words) 49 | ``` 50 | 'Happy,Happy!' 被传递给了__init__()的参数more_words 51 | 52 | 53 | 54 | ##对象的性质 55 | 56 | 我们讲到了许多属性,但这些属性是类的属性。所有属于该类的对象会共享这些属性。比如说,鸟都有羽毛,鸡都不会飞。 57 | 58 | 在一些情况下,我们定义对象的性质,用于记录该对象的特别信息。比如说,人这个类。性别是某个人的一个性质,不是所有的人类都是男,或者都是女。这个性质的值随着对象的不同而不同。李雷是人类的一个对象,性别是男;韩美美也是人类的一个对象,性别是女。 59 | 60 | 当定义类的方法时,必须要传递一个self的参数。这个参数指代的就是类的一个对象。我们可以通过操纵self,来修改某个对象的性质。比如用类来新建一个对象,即下面例子中的li_lei, 那么li_lei就被self表示。我们通过赋值给self.attribute,给li_lei这一对象增加一些性质,比如说性别的男女。self会传递给各个方法。在方法内部,可以通过引用self.attribute,查询或修改对象的性质。 61 | 62 | 这样,在类属性的之外,又给每个对象增添了各自特色的性质,从而能描述多样的世界。 63 | 64 | ```python 65 | class Human(object): 66 | def __init__(self, input_gender): 67 | self.gender = input_gender 68 | def printGender(self): 69 | print self.gender 70 | 71 | li_lei = Human('male') # 这里,'male'作为参数传递给__init__()方法的input_gender变量。 72 | print li_lei.gender 73 | li_lei.printGender() 74 | ``` 75 | 在初始化中,将参数input_gender,赋值给对象的性质,即self.gender。 76 | 77 | li_lei拥有了对象性质gender。gender不是一个类属性。Python在建立了li_lei这一对象之后,使用li_lei.gender这一对象性质,专门储存属于对象li_lei的特有信息。 78 | 79 | 对象的性质也可以被其它方法调用,调用方法与类属性的调用相似,正如在printGender()方法中的调用。 80 | 81 | ##总结 82 | 83 | 通过self调用类属性 84 | 85 | __init__(): 在建立对象时自动执行 86 | 87 | 类属性和对象的性质的区别 88 | -------------------------------------------------------------------------------- /content/10.md: -------------------------------------------------------------------------------- 1 | #Python基础10 反过头来看看 2 | 3 | 从最初的“Hello World”,走到面向对象。该回过头来看看,教程中是否遗漏了什么。 4 | 5 | 我们之前提到一句话,"Everything is Object". 那么我们就深入体验一下这句话。 6 | 7 | 8 | 9 | 需要先要介绍两个内置函数,dir()和help() 10 | 11 | dir()用来查询一个类或者对象所有属性。你可以尝试一下 12 | 13 | ```python 14 | >>>print dir(list) 15 | ``` 16 | 17 | help()用来查询的说明文档。你可以尝试一下 18 | 19 | ```python 20 | >>>print help(list) 21 | ``` 22 | 23 | (list是Python内置的一个类,对应于我们之前讲解过的列表) 24 | 25 | 26 | ##list是一个类 27 | 28 | 在上面以及看到,表是Python已经定义好的一个类。当我们新建一个表时,比如: 29 | 30 | ```python 31 | >>>nl = [1,2,5,3,5] 32 | ``` 33 | 34 | 实际上,nl是类list的一个对象。 35 | 36 | 实验一些list的方法: 37 | 38 | ```python 39 | >>>print nl.count(5) # 计数,看总共有多少个5 40 | >>>print nl.index(3) # 查询 nl 的第一个3的下标 41 | >>>nl.append(6) # 在 nl 的最后增添一个新元素6 42 | >>>nl.sort() # 对nl的元素排序 43 | >>>print nl.pop() # 从nl中去除最后一个元素,并将该元素返回。 44 | >>>nl.remove(2) # 从nl中去除第一个2 45 | >>>nl.insert(0,9) # 在下标为0的位置插入9 46 | ``` 47 | 总之,list是一个类。每个列表都属于该类。 48 | 49 | Python补充中有list常用方法的附录。 50 | 51 | 52 | 53 | ##运算符是特殊方法 54 | 55 | 使用dir(list)的时候,能看到一个属性,是__add__()。从形式上看是特殊方法(下划线,下划线)。它特殊在哪呢? 56 | 57 | 这个方法定义了"+"运算符对于list对象的意义,两个list的对象相加时,会进行的操作。 58 | 59 | ```python 60 | >>>print [1,2,3] + [5,6,9] 61 | ``` 62 | 63 | 64 | 运算符,比如+, -, >, <, 以及下标引用[start:end]等等,从根本上都是定义在类内部的方法。 65 | 66 | 67 | 68 | 尝试一下 69 | ```python 70 | >>>print [1,2,3] - [3,4] 71 | ``` 72 | 73 | 会有错误信息,说明该运算符“-”没有定义。现在我们继承list类,添加对"-"的定义 74 | 75 | ```python 76 | class superList(list): 77 | def __sub__(self, b): 78 | a = self[:] # 这里,self是supeList的对象。由于superList继承于list,它可以利用和list[:]相同的引用方法来表示整个对象。 79 | b = b[:] 80 | while len(b) > 0: 81 | element_b = b.pop() 82 | if element_b in a: 83 | a.remove(element_b) 84 | return a 85 | 86 | print superList([1,2,3]) - superList([3,4]) 87 | ``` 88 | 内置函数len()用来返回list所包含的元素的总数。内置函数__sub__()定义了“-”的操作:从第一个表中去掉第二个表中出现的元素。如果__sub__()已经在父类中定义,你又在子类中定义了,那么子类的对象会参考子类的定义,而不会载入父类的定义。任何其他的属性也是这样。 89 | 90 | (教程最后也会给出一个特殊方法的清单) 91 | 92 | 定义运算符对于复杂的对象非常有用。举例来说,人类有多个属性,比如姓名,年龄和身高。我们可以把人类的比较(>, <, =)定义成只看年龄。这样就可以根据自己的目的,将原本不存在的运算增加在对象上了。 93 | 94 | ##下一步 95 | 96 | 希望你已经对Python有了一个基本了解。你可能跃跃欲试,要写一些程序练习一下。这会对你很有好处。 97 | 98 | 但是,Python的强大很大一部分原因在于,它提供有很多已经写好的,可以现成用的对象。我们已经看到了内置的比如说list,还有tuple等等。它们用起来很方便。在Python的标准库里,还有大量可以用于操作系统互动,Internet开发,多线程,文本处理的对象。而在所有的这些的这些的基础上,又有很多外部的库包,定义了更丰富的对象,比如numpy, tkinter, django等用于科学计算,GUI开发,web开发的库,定义了各种各样的对象。对于一般用户来说,使用这些库,要比自己去从头开始容易得多。我们要开始攀登巨人的肩膀了。 99 | 100 | 谢谢你的关注, 101 | 102 | 欢迎来到Python的世界。 103 | 104 | 105 | 106 | ##总结 107 | 108 | len() dir() help() 109 | 110 | 数据结构list(列表)是一个类。 111 | 112 | 运算符是方法 113 | -------------------------------------------------------------------------------- /content/Python小题目 针对快速教程.md: -------------------------------------------------------------------------------- 1 | Python小题目 针对快速教程 2 | 3 | 4 | 5 | 6 | 作业的目的是帮助熟悉之前学习的内容: 7 | 8 | 9 | 10 | 1. 11 | 12 | 写一个程序,判断2008年是否是闰年。 13 | 14 | 写一个程序,用于计算2008年10月1日是这一年的第几天?(2008年1月1日是这一年的第一天) 15 | 16 | 这些小题目是为了方便大家加深对Python理解而设计的。 17 | 18 | 19 | 20 | 2. 21 | 22 | 有一个record.txt的文档,内容如下: 23 | ```python 24 | # name, age, score 25 | 26 | tom, 12, 86 27 | 28 | Lee, 15, 99 29 | 30 | Lucy, 11, 58 31 | 32 | Joseph, 19, 56 33 | ``` 34 | 第一栏为姓名(name),第二栏为年纪(age),第三栏为得分(score) 35 | 36 | 现在,写一个Python程序, 37 | 38 | 1)读取文件 39 | 40 | 2)打印如下结果: 41 | 42 | 得分低于60的人都有谁? 43 | 44 | 谁的名字以L开头? 45 | 46 | 所有人的总分是多少? 47 | 48 | 3)姓名的首字母需要大写,该record.txt是否符合此要求? 如何纠正错误的地方? 49 | -------------------------------------------------------------------------------- /content/Python快速教程 尾声.md: -------------------------------------------------------------------------------- 1 | #Python快速教程 尾声 2 | 3 | 4 | 5 | 6 | 写了将近两年的Python快速教程,终于大概成形。这一系列文章,包括Python基础、标准库、Django框架。前前后后的文章,包含了Python最重要的组成部分。这一内容的跨度远远超过我的预期,也超过了我看过的任何Python相关书籍。最初动笔的原因,除了要总结,还对很多Python书和教程觉得不满意,觉得太晦涩,又不够全面。现在,我比较确定,参考我在Linux、网络、算法方面的总结,读者可以在无基础的背景下,在短时间,有深度的学习Python了。 7 | 8 | 9 | 10 | 这一篇也是尾声。准备在一个长的时间内,停止更新Python快速教程,把精力集中在其它方面。毕竟一件事情做久了,会缺乏自我突破。编程是一个很有创新性,很需要突破自我的工作。在一个方面待久了,自己都会觉得发馊。而未知的计算机领域中,还有很多有趣的技术值得更深入的学习。另一方面,Python用舒服了,导致我一想到问题,基本都是Python的思路。这样,Python就成了我的舒服区域。我懒得去想,Java会如何解决,Scala会如何解决,C会如何解决。成长的空间被压缩得很小。为了自己能前进,要打破这个舒适区。 11 | 12 | 13 | 14 | 谢谢各位阅读这些的Python文章。你们的支持是我走到现在的最大动力。希望这些技术文章能点缀你的闲暇。如果再能有一些具体的帮助,那就最好不过了。 15 | 16 | 17 | 18 | 19 | 20 | 下面是我在知乎回答的一个帖子“你是如何自学Python的”,略做修改,作为总结: 21 | 22 | 23 | 24 | 我是自学的Python。从对Python一无所知,到在博客上写Python相关的系列文章,期间有不少门槛,但也充满乐趣。乐趣是自学的最大动力。Python是一个容易编写,又功能强大的动态语言。使用Python,可以在短短几行内实现相当强大的功能。通过自己写一些小程序,迅速的看到效果,发现问题,这是学习Python最便利的地方。在《黑客与画家》中,Paul也说,动态语言可以给Hacker们更多涂涂画画的快感。这深得我心。 25 | 26 | 在学习Python之前,可以了解一下Python的特点和设计理念(Python简史)。在设计之初,Python就试图在复杂、强大的C和方便、功能有限的bash之间,找到一个平衡点。Python的语法比较简单,用起来很方便,因此有些人把它当作脚本语言使用。但Python要比普通的脚本语言功能强大很多。通过良好的可拓展性,Python的功能相当全面,应用面很广:web服务器,网络爬虫,科学运算,机器学习,游戏开发…… 当然,天下没有免费的午餐,也没有完美的语言,Python为了达到上述两点,有意的牺牲了Python的运行速度。如果你是在编写高业务量、运算量的程序,可能Python并不是最好的选择。 27 | 28 | ----- 29 | 30 | Python的主体内容大致可以分为以下几个部分: 31 | 32 | 面向过程。包括基本的表达式,if语句,循环,函数等。如果你有任何一个语言的基础,特别是C语言的基础,这一部分就是分分钟了解下Python规定的事。如果你没有语言基础,建议用Python Programming为参考书。这本书是计算机导论性质的教材,不需要编程基础。 33 | 面向对象,包括面向对象的基本概念,类,方法,属性,继承等。Python是面向对象的语言,“一切皆对象”。面向对象是很难回避的。Python的面向对象机制是相对比较松散的,不像Java和C++那么严格。好处是容易学,容易维护,坏处是容易犯错。 34 | 应用功能,包括IO,数据容器如表和词典,内置函数,模块,格式化字符串等。这些在其它语言中也经常出现,有比较强的实用性。 35 | 高级语法,上下文管理器,列表推导,函数式编程,装饰器,特殊方法等。这些语法并不是必须的,你可以用前面比较基础的语法实现。学这些高级语法的主要原因是:它们太方便了。比如列表推导一行可以做到的事情,用循环结构要好几行才行。 36 | 37 | 学习Python主体最好的参考书是Learning Python,它非常全面,满满的都是干货。虽然很厚,读起来并不难读。另一个是参考官网的教程Python.org 38 | 39 | Python号称“Battery Included",也就是说,功能都已经包含在了语言中。这一自信,主要来自Python功能全面的标准库。标准库提供了许多功能模块,每个模块是某一方面功能的接口,比如文件管理,操作系统互动,字符处理,网络接口,编码加密等等。 40 | 41 | The Python Standard Library中,你可以看到标准库模块的列表。这里也是标准库最好的学习资料。如果想找书,我只看到过两本关于标准库的: 42 | Python Essential Reference 43 | The Python Standard Library by Example 44 | 45 | 说实话,这两本都不算很好的标准库教材,而标准库的参考书也确实很难写。因为标准库只是调用功能的接口,最终实现的是Python和系统的互动。这需要很强的系统知识,比如文件系统知识,进程管理,http原理,socket编程,数据库原理…… 如果这些知识都已经准备充分,那么标准库学起来完全没有难度。然而,这些背景知识的学习曲线,要远远陡过Python本身。 46 | 47 | 更深入的Python学习也是如此,需要大量的背景知识,而不止是Python自身。如果你对Python的拓展性感兴趣,可以多多尝试混合编程。如果你对Python的编译和运行机制感兴趣,你可以往Python底层这一深度挖,看看编译器是怎么做的。如果你对应用感兴趣,你可以多学习几个自己用的上的第三方包。学到这个时候,拼的是境界,也没有什么定法。广阔空间,留待探索。 48 | 49 | 基本上,学过主体内容之后,Python还是要靠做项目来练习。有不少小练习题类型的资料,比如Python Cookbook。但更好的方式是自己去想一些应用场景,用Python来解决。比如: 50 | 51 | 建设一个网站 52 | 做一个网页爬虫 53 | 系统管理 54 | Python功能全面,所以不要担心自己想的问题Python解决不了 (基本上Python解决不了的问题,别的语言也没戏)。比如我学习多线程的动力,就因为要并行的下载大量的文件。基本上一个项目下来,会用到Python好几块的内容,知识会特别巩固。 55 | 56 | 最后,和其它任何知识的学习一样,笔记和总结很重要。在看参考书和看网页时,可以做一些笔记。等到学了一段时间后,可以把笔记整理成更有条理的参考卡片(reference card),或者写博客。 57 | 58 | 59 | 60 | 长路漫漫,与诸君共勉。 61 | -------------------------------------------------------------------------------- /content/Python快速教程总结.md: -------------------------------------------------------------------------------- 1 | Python快速教程总结 2 | 3 | 4 | 5 | 6 | 7 | 到现在为止,Python快速教程就到一段落。谢谢大家的鼓励和支持,特别是对其中错误的包容和指正。 8 | 9 | 10 | 11 | 在第一部分,基础部分中,除了一些基础知识,我们着力于建立对象的概念。在第二部分中,我们深入到许多实用并且常用的Python用法。如果只是想了解Python是什么,那么第一部分应该可以给出一个基本概念。加上第二部分的话,我们应该可以有足够的知识读懂Python程序,并编写一些应用。 12 | 13 | 我会在以后写一些选读性的随笔,以便对教程更进一步补充。 14 | 15 | 16 | 17 | 我还在考虑下一步在博客中写什么。Python依然有一个庞大的标准库可以展开。但标准库的讲解离不开对计算机原理和互联网原理的了解,很难以一个快速教程的方式呈现出来。但离开标准库,Python也显得很单薄。一个可能是,先写一个总体纲要,描述一下标准库各个包所包含的内容,以及可能的用途,不知是否可行? 18 | 19 | 另外,最近看了一些Google Engine和Django的内容,觉得也不错。只可惜自己在这方面经验太浅,不知从何说起。 20 | 21 | 另一方面,我个人的Python经验也非常有限。希望能多参与到一些项目,以便能积累经验。 22 | -------------------------------------------------------------------------------- /content/Python标准库——走马观花.md: -------------------------------------------------------------------------------- 1 | #Python标准库——走马观花 2 | 3 | 4 | 5 | 6 | 7 | Python的一大好处在于它有一套很有用的标准库(standard library)。标准库是随着Python一起安装在你的电脑中的,是Python的一部分 (当然也有特殊情况。有些场合会因为系统安全性的要求,不使用全部的标准库,比如说Google App Engine)。 8 | 9 | 10 | 11 | 利用已有的类(class)和函数(function)进行开发,可以省去你从头写所有程序的苦恼。这些标准库就是盖房子已经烧好的砖,要比你自己去烧砖来得便捷得多。 12 | 13 | 14 | 15 | 我将根据我个人的使用经验中,先挑选出标准库下面三个方面的包(package)介绍,以说明标准库的强大功能: 16 | 17 | Python增强 18 | 19 | 系统互动 20 | 21 | 网络 22 | 23 | 24 | 25 | ##第一类:Python增强 26 | 27 | Python自身的已有的一些功能可以随着标准库的使用而得到增强。 28 | 29 | 1) 文字处理 30 | 31 | Python的string类提供了对字符串进行处理的方法。但Python并不止步于此。通过标准库中的re包,Python实现了对正则表达式(regular expression)的支持。Python的正则表达式可以和Perl以及Linux bash的正则表达相媲美。 32 | 33 | (正则表达式通过自定义的模板在文本中搜索或替换符合该模板的字符串。比如你可以搜索一个文本中所有的数字。正则表达式的关键在于根据自己的需要构成模板。) 34 | 35 | 此外,Python标准库还为字符串的输出提供更加丰富的格式, 比如: string包,textwrap包。 36 | 37 | 38 | 39 | 2) 数据对象 40 | 41 | 我们之前的快速教程介绍了表(list), 字典(dictionary)等数据对象。它们各自有不同的特征,适用于不同场合的对数据的组织和管理。Python的标准库定义了更多的数据对象,比如说数组(array),队列(Queue)。这些数据对象也分别有各自的特点和功能。一个熟悉数据结构(data structure)的Python用户可以在这些包中找到自己需要的数据结构。 42 | 43 | 此外,我们也会经常使用copy包,以复制对象。 44 | 45 | 46 | 47 | 3) 日期和时间 48 | 49 | 日期和时间的管理并不复杂,但容易犯错。Python的标准库中对日期和时间的管理颇为完善(利用time包管理时间,利用datetime包管理日期和时间),你不仅可以进行日期时间的查询和变换(比如:2012年7月18日对应的是星期几),还可以对日期时间进行运算(比如2000.1.1 13:00的378小时之后是什么日期,什么时间)。通过这些标准库,还可以根据需要控制日期时间输出的文本格式(比如:输出’2012-7-18‘还是'18 Jul 2012') 50 | 51 | 52 | 53 | 4) 数学运算 54 | 55 | 标准库中,Python定义了一些新的数字类型(decimal包, fractions包), 以弥补之前的数字类型(integer, float)可能的不足。标准库还包含了random包,用于处理随机数相关的功能(产生随机数,随机取样等)。math包补充了一些重要的数学常数和数学函数,比如pi,三角函数等等。 56 | 57 | (尽管numpy并不是标准库中的包,但它的数组运算的良好支持,让它在基于Python的科研和计算方面得到相当广泛的应用,可以适当关注。) 58 | 59 | 60 | 61 | 5) 存储 62 | 63 | 之前我们的快速教程中,只提及了文本的输入和输出。实际上,Python可以输入或输出任意的对象。这些对象可以通过标准库中的pickle包转换成为二进制格式(binary),然后存储于文件之中,也可以反向从二进制文件中读取对象。 64 | 65 | 此外,标准库中还支持基本的数据库功能(sqlite3包)。XML和csv格式的文件也有相应的处理包。 66 | 67 | 68 | 69 | ##第二类:系统互动 70 | 71 | 系统互动,主要指Python和操作系统(operate system)、文件系统(file system)的互动。Python可以实现一个操作系统的许多功能。它能够像bash脚本那样管理操作系统,这也是Python有时被成为脚本语言的原因。 72 | 73 | 74 | 75 | 1) Python运行控制 76 | 77 | sys包被用于管理Python自身的运行环境。Python是一个解释器(interpreter), 也是一个运行在操作系统上的程序。我们可以用sys包来控制这一程序运行的许多参数,比如说Python运行所能占据的内存和CPU, Python所要扫描的路径等。另一个重要功能是和Python自己的命令行互动,从命令行读取命令和参数。 78 | 79 | 80 | 81 | 2) 操作系统 82 | 83 | 如果说Python构成了一个小的世界,那么操作系统就是包围这个小世界的大世界。Python与操作系统的互动可以让Python在自己的小世界里管理整个大世界。 84 | 85 | os包是Python与操作系统的接口。我们可以用os包来实现操作系统的许多功能,比如管理系统进程,改变当前路径(相当于’cd‘),改变文件权限等,建立。但要注意,os包是建立在操作系统的平台上的,许多功能在Windows系统上是无法实现的。另外,在使用os包中,要注意其中的有些功能已经被其他的包取代。 86 | 87 | 我们通过文件系统来管理磁盘上储存的文件。查找、删除,复制文件,以及列出文件列表等都是常见的文件操作。这些功能经常可以在操作系统中看到(比如ls, mv, cp等Linux命令),但现在可以通过Python标准库中的glob包、shutil包、os.path包、以及os包的一些函数等,在Python内部实现。 88 | 89 | subprocess包被用于执行外部命令,其功能相当于我们在操作系统的命令行中输入命令以执行,比如常见的系统命令'ls'或者'cd',还可以是任意可以在命令行中执行的程序。 90 | 91 | 92 | 93 | 4) 线程与进程 94 | 95 | Python支持多线程(threading包)运行和多进程(multiprocessing包)运行。通过多线程和多进程,可以提高系统资源的利用率,提高计算机的处理速度。Python在这些包中,附带有相关的通信和内存管理工具。此外,Python还支持类似于UNIX的signal系统,以实现进程之间的粗糙的信号通信。 96 | 97 | 98 | 99 | ##第三类:网络 100 | 101 | 现在,网络功能的强弱很大程度上决定了一个语言的成功与否。从Ruby, JavaScript, php身上都可以感受到这一点。Python的标准库对互联网开发的支持并不充分,这也是Django等基于Python的项目的出发点: 增强Python在网络方面的应用功能。这些项目取得了很大的成功,也是许多人愿意来学习Python的一大原因。但应注意到,这些基于Python的项目也是建立在Python标准库的基础上的。 102 | 103 | 104 | 105 | 1) 基于socket层的网络应用 106 | 107 | socket是网络可编程部分的底层。通过socket包,我们可以直接管理socket,比如说将socket赋予给某个端口(port),连接远程端口,以及通过连接传输数据。我们也可以利用SocketServer包更方便地建立服务器。 108 | 109 | 通过与多线程和多进程配合,建立多线程或者多进程的服务器,可以有效提高服务器的工作能力。此外,通过asyncore包实现异步处理,也是改善服务器性能的一个方案。 110 | 111 | 112 | 113 | 2) 互联网应用 114 | 115 | 在实际应用中,网络的很多底层细节(比如socket)都是被高层的协议隐藏起来的。建立在socket之上的http协议实际上更容易也更经常被使用。http通过request/responce的模式建立连接并进行通信,其信息内容也更容易理解。Python标准库中有http的服务器端和客户端的应用支持(BaseHTTPServer包; urllib包, urllib2包), 并且可以通过urlparse包对URL(URL实际上说明了网络资源所在的位置)进行理解和操作。 116 | 117 | 118 | 119 | 以上的介绍比较粗糙,只希望能为大家提供一个了解标准库的入口。欢迎大家一起分享标准库的使用经验。 120 | -------------------------------------------------------------------------------- /content/Python标准库的学习准备.md: -------------------------------------------------------------------------------- 1 | #Python标准库的学习准备 2 | 3 | 4 | 5 | 6 | 7 | Python标准库是Python强大的动力所在,我们已经在前文中有所介绍。由于标准库所涉及的应用很广,所以需要学习一定的背景知识。 8 | 9 | 10 | 11 | ##硬件原理 12 | 13 | 这一部份需要了解内存,CPU,磁盘存储以及IO的功能和性能,了解计算机工作的流程,了解指令的概念。这些内容基础而重要。 14 | 15 | Python标准库的一部份是为了提高系统的性能(比如mmap),所以有必要了解基本的计算机各个组成部分的性能。 16 | 17 | 18 | 19 | ##操作系统 20 | 21 | 在了解操作系统时,下面是重点: 22 | 23 | 1) 操作系统的进程管理,比如什么是UID, PID, daemon 24 | 25 | 2) 进程之间的信号通信,比如使用kill传递信号的方式 26 | 27 | 学习进程相关的内容,是为了方便于学习os包,thread包,multiprocessing包,signal包 28 | 29 | 3) 文件管理,文件的几种类型。 30 | 31 | 4) 文件读写(IO)接口 32 | 33 | 5) 文件的权限以及其它的文件信息(meta data) 34 | 35 | 6) 常用系统命令以及应用,比如ls, mv, rm, mkdir, chmod, zip, tar..., 36 | 37 | 学习文件相关的内容,,是为了学习os包, shutil包中文件管理相关的部分。学习文件接口对于文本输入输出的理解很重要,也会影响到对于socket包, select包概念的理解。此外,python中的归档(archive)和压缩(compress)功能也和操作系统中的类似。 38 | 39 | 7) Linux shell,比如说file name matching,对于理解glob包等有帮助。如果你对Linux的正则表达(regular expression)有了解的话,python的正则表达的学习会变得比较容易。学习Linux命令行中的参数传递对于理解python标准库中解析命令行的包也是有用的。 40 | 41 | 42 | 43 | ##网络 44 | 45 | Python的一大应用是在网络方面。但Python和标准库只是提供了接口,并不涉及底层。网络知识可以大大降低学习曲线的陡度。 46 | 47 | 1) TCP/IP的基础的分层架构。这方面的内容太广博了,所以可以有选择地了解骨干知识。 48 | 49 | 2) 常用的应用层协议,比如http, 以及邮件相关的协议,特别是它们的工作过程。 50 | 51 | 3) 根据需要,了解html/css/javascript/jQuery/frame等 52 | 53 | 如果想利用python建服务器,比如在google app engine上,这些知识是需要的。 54 | 55 | 56 | 57 | ##算法与数据结构 58 | 59 | 标准库中定义有一些数据对象的封装。因此,你并不需要重头编写它们。相关数据结构的应用需要一些数据结构的知识,比如队列,树等。 60 | 61 | 标准库中已经实现了许多算法,比如排序等,可以方便的调用。算法的基础知识可以帮助你做决定。 62 | 63 | 64 | 65 | ##数据库 66 | 67 | Python中内置了sqlite3。如果你只需要一个简单的数据库,可以直接从标准库中调用sqlite3。 68 | 69 | 当使用Python中数据库相关的包时(比如sqlite3),需要对数据库,特别是关系型数据库,有一个基本了解。 70 | 71 | 72 | 73 | ##加密和文本编码 74 | 75 | Python的加密算法同样基于一些经典加密算法,比如MD5,RSA算法。加密的基本知识将很有帮助。 76 | 77 | 使用非ASCII编码,比如中文时,文本编码的知识很重要。 78 | 79 | 80 | 81 | 总结 82 | 83 | Python基本的对象概念和动态类型概念。可以参照快速教程,并尝试阅读更多的资料和源码,来加深对概念的理解。Python标准库学习 84 | 的难度在于背景知识。一个了解相关背景知识(或者其它语言的库)的程序员,可以在很短的时间内掌握Python基础库。 85 | -------------------------------------------------------------------------------- /content/Python简史.md: -------------------------------------------------------------------------------- 1 | #Python简史 2 | 3 | 4 | 5 | 6 | Python是我喜欢的语言,简洁,优美,容易使用。前两天,我很激昂的向朋友宣传Python的好处。 7 | 8 | 听过之后,朋友问我:好吧,我承认Python不错,但它为什么叫Python呢? 9 | 10 | 我不是很确定:呃,似乎是一个电视剧的名字。 11 | 12 | 朋友又问:那你说的Guido是美国人么? (Guido von Rossum,Python的作者) 13 | 14 | 我再次不是很确定:他从google换到Dropbox工作,但他的名字像是荷兰人的 (有一个von在中间)。 15 | 16 | 17 | 18 | 所以,后面我花了些时间调查Python的历史。这是很好的学习。我看到了Python中许多功能的来源和Python的设计理念,比如哪些功能是历史遗留,哪些功能是重复,如何增加功能…… 而且,Python也是开源(open source)运动的一个成功案例。从Python的历史中,我们可以一窥开源开发的理念和成就。 19 | 20 | 这也可以作为我写的Python快速教程的序篇。 21 | 22 | 23 | 24 | ##Python的起源 25 | 26 | Python的作者,Guido von Rossum,确实是荷兰人。1982年,Guido从阿姆斯特丹大学(University of Amsterdam)获得了数学和计算机硕士学位。然而,尽管他算得上是一位数学家,但他更加享受计算机带来的乐趣。用他的话说,尽管拥有数学和计算机双料资质,他总趋向于做计算机相关的工作,并热衷于做任何和编程相关的活儿。 27 | 28 | 29 | 30 | 在那个时候,他接触并使用过诸如Pascal、C、 Fortran等语言。这些语言的基本设计原则是让机器能更快运行。在80年代,虽然IBM和苹果已经掀起了个人电脑浪潮,但这些个人电脑的配置很低 (在今天看来)。比如早期的Macintosh,只有8MHz的CPU主频和128KB的RAM,一个大的数组就能占满内存。所有的编译器的核心是做优化,以便让程序能够运行。为了增进效率,语言也迫使程序员像计算机一样思考,以便能写出更符合机器口味的程序。在那个时代,程序员恨不得用手榨取计算机每一寸的能力。有人甚至认为C语言的指针是在浪费内存。至于动态类型,内存自动管理,面向对象…… 别想了,那会让你的电脑陷入瘫痪。 31 | 32 | 33 | 34 | 然而,这种思考方式让Guido感到苦恼。Guido知道如何用C语言写出一个功能,但整个编写过程需要耗费大量的时间 (即使他已经准确的知道了如何实现)。他的另一个选择是shell。Bourne Shell作为UNIX系统的解释器(interpreter)已经长期存在。UNIX的管理员们常常用shell去写一些简单的脚本,以进行一些系统维护的工作,比如定期备份、文件系统管理等等。shell可以像胶水一样,将UNIX下的许多功能连接在一起。许多C语言下上百行的程序,在shell下只用几行就可以完成。然而,shell的本质是调用命令。它并不是一个真正的语言。比如说,shell没有数值型的数据类型,加法运算都很复杂。总之,shell不能全面的调动计算机的功能。 35 | 36 | (关于shell,你可以参考Linux架构和Linux命令行与命令) 37 | 38 | 39 | 40 | Guido希望有一种语言,这种语言能够像C语言那样,能够全面调用计算机的功能接口,又可以像shell那样,可以轻松的编程。ABC语言让Guido看到希望。ABC是由荷兰的CWI (Centrum Wiskunde & Informatica, 数学和计算机研究所)开发的。Guido在CWI工作,并参与到ABC语言的开发。ABC语言以教学为目的。与当时的大部分语言不同,ABC语言的目标是“让用户感觉更好”。ABC语言希望让语言变得容易阅读,容易使用,容易记忆,容易学习,并以此来激发人们学习编程的兴趣。比如下面是一段来自Wikipedia的ABC程序,这个程序用于统计文本中出现的词(word)的总数: 41 | 42 | ```python 43 | HOW TO RETURN words document: 44 | PUT {} IN collection 45 | FOR line IN document: 46 | FOR word IN split line: 47 | IF word not.in collection: 48 | INSERT word IN collection 49 | RETURN collection 50 | ``` 51 | HOW TO用于定义一个函数。一个Python程序员应该很容易理解这段程序。ABC语言使用冒号(:)和缩进来表示程序块(C语言使用{}来表示程序块)。行尾没有分号。for和if结构中也没有括号()。如果将HOW TO改为def,将PUT行改为collection = [],将INSERT行改为collection.append(word),这就几乎是一个标准的Python函数。上面的函数读起来就像一段自然的文字。 52 | 53 | 54 | 55 | 尽管已经具备了良好的可读性和易用性,ABC语言最终没有流行起来。在当时,ABC语言编译器需要比较高配置的电脑才能运行。而这些电脑的使用者通常精通计算机,他们更多考虑程序的效率,而非它的学习难度。除了硬件上的困难外,ABC语言的设计也存在一些致命的问题: 56 | 57 | 可拓展性差。ABC语言不是模块化语言。如果想在ABC语言中增加功能,比如对图形化的支持,就必须改动很多地方。 58 | 不能直接进行IO。ABC语言不能直接操作文件系统。尽管你可以通过诸如文本流的方式导入数据,但ABC无法直接读写文件。输入输出的困难对于计算机语言来说是致命的。你能想像一个打不开车门的跑车么? 59 | 过度革新。ABC用自然语言的方式来表达程序的意义,比如上面程序中的HOW TO (如何)。然而对于程序员来说,他们更习惯用function或者define来定义一个函数。同样,程序员也习惯了用等号(=)来分配变量。这尽管让ABC语言显得特别,但实际上增加了程序员的学习难度 (程序员大都掌握不止一种语言)。 60 | 传播困难。ABC编译器很大,必须被保存在磁带(tape)上。当时Guido在访问的时候,就必须有一个大磁带来给别人安装ABC编译器。 这样,ABC语言就很难快速传播。 61 | IBM tape drive:读写磁带 62 | 63 | 64 | 65 | 1989年,为了打发圣诞节假期,Guido开始写Python语言的编译/解释器。Python来自Guido所挚爱的电视剧Monty Python's Flying Circus (BBC1960-1970年代播放的室内情景幽默剧,以当时的英国生活为素材)。他希望这个新的叫做Python的语言,能实现他的理念(一种C和shell之间,功能全面,易学易用,可拓展的语言)。Guido作为一个语言设计爱好者,已经有过设计语言的(不很成功)的尝试。这一次,也不过是一次纯粹的hacking行为。 66 | 67 | 68 | 69 | ##Python的诞生 70 | 71 | 1991年,第一个Python编译器(同时也是解释器)诞生。它是用C语言实现的,并能够调用C库(.so文件)。从一出生,Python已经具有了:类(class),函数(function),异常处理(exception),包括表(list)和词典(dictionary)在内的核心数据类型,以及模块(module)为基础的拓展系统。 72 | 73 | 74 | 75 | 最初的Python logo: 由Guido的兄弟Just von Rossum设计 76 | 77 | Python语法很多来自C,但又受到ABC语言的强烈影响。来自ABC语言的一些规定直到今天还富有争议,比如强制缩进。但这些语法规定让Python容易读。另一方面,Python聪明的选择服从一些惯例(特别是C语言的惯例)。比如使用等号赋值,使用def来定义函数。Guido认为,如果“常识”上确立的东西,没有必要过度纠结。 78 | 79 | Python从一开始就特别在意可拓展性(extensibility)。Python可以在多个层次上拓展。从高层上,你可以引入.py文件。在底层,你可以引用C语言的库。Python程序员可以快速的使用Python写.py文件作为拓展模块。但当性能是考虑的重要因素时,Python程序员可以深入底层,写C程序,编译为.so文件引入到Python中使用。Python就好像是使用钢构建房一样,先规定好大的框架。而程序员可以在此框架下相当自由的拓展或更改。 80 | 81 | 最初的Python完全由Guido本人开发。Python得到Guido同事的欢迎。他们迅速的反馈使用意见,并参与到Python的改进。Guido和一些同事构成Python的核心团队。他们将自己大部分的业余时间用于hack Python (也包括工作时间,因为他们将Python用于工作)。随后,Python拓展到CWI之外。Python将许多机器层面上的细节隐藏,交给编译器处理,并凸显出逻辑层面的编程思考。Python程序员可以花更多的时间用于思考程序的逻辑,而不是具体的实现细节 (Guido有一件T恤,写着:人生苦短,我用Python)。这一特征吸引了广大的程序员。Python开始流行。 82 | 83 | 84 | 85 | 我们不得不暂停我们的Python时间,转而看一看这时的计算机概况。1990年代初,个人计算机开始进入普通家庭。Intel发布了486处理器,windows发布window 3.0开始的一系列视窗系统。计算机的性能大大提高。程序员开始关注计算机的易用性 (比如图形化界面)。 86 | 87 | Windows 3.0 88 | 89 | 由于计算机性能的提高,软件的世界也开始随之改变。硬件足以满足许多个人电脑的需要。硬件厂商甚至渴望高需求软件的出现,以带动硬件的更新换代。C++和Java相继流行。C++和Java提供了面向对象的编程范式,以及丰富的对象库。在牺牲了一定的性能的代价下,C++和Java大大提高了程序的产量。语言的易用性被提到一个新的高度。我们还记得,ABC失败的一个重要原因是硬件的性能限制。从这方面说,Python要比ABC幸运许多。 90 | 91 | 另一个悄然发生的改变是Internet。1990年代还是个人电脑的时代,windows和Intel挟PC以令天下,盛极一时。尽管Internet为主体的信息革命尚未到来,但许多程序员以及资深计算机用户已经在频繁使用Internet进行交流 (包括email和newsgroup)。Internet让信息交流成本大大下降。一种新的软件开发模式开始流行:开源 (open source)。程序员利用业余时间进行软件开发,并开放源代码。1991年,Linus在comp.os.minix新闻组上发布了Linux内核源代码,吸引大批hacker的加入。Linux和GNU相互合作,最终构成了一个充满活力的开源平台。 92 | 93 | 94 | 95 | 硬件性能不是瓶颈,Python又容易使用,所以许多人开始转向Python。Guido维护了一个maillist,Python用户就通过邮件进行交流。Python用户来自许多领域,有不同的背景,对Python也有不同的需求。Python相当的开放,又容易拓展,所以当用户不满足于现有功能,很容易对Python进行拓展或改造。随后,这些用户将改动发给Guido,并由Guido决定是否将新的特征加入到Python或者标准库中。如果代码能被纳入Python自身或者标准库,这将极大的荣誉。Python自身也因此变得更好。 96 | 97 | (Guido不得不作出许多决定,这也是他被称为Benevolent Dictator For Life的原因) 98 | 99 | Python被称为“Battery Included”,是说它以及其标准库的功能强大。这些是整个社区的贡献。Python的开发者来自不同领域,他们将不同领域的优点带给Python。比如Python标准库中的正则表达(regular expression)是参考Perl,而lambda, map, filter, reduce函数参考Lisp。Python本身的一些功能以及大部分的标准库来自于社区。Python的社区不断扩大,进而拥有了自己的newsgroup,网站(python.org),以及基金 (Python Software Foundation)。从Python 2.0开始,Python也从maillist的开发方式,转为完全开源的开发方式。社区气氛已经形成,工作被整个社区分担,Python也获得了更加高速的发展。 100 | 101 | (由于Guido享有绝对的仲裁权,所以在Python早期maillist的开发时代,不少爱好者相当担心Guido的生命。他们甚至作出假设:如果Guido挂了的话,Python会怎样。见If Guido was hit by a bus) 102 | 103 | 到今天,Python的框架已经确立。Python语言以对象为核心组织代码(Everything is object),支持多种编程范式(multi-paradigm),采用动态类型(dynamic typing),自动进行内存回收(garbage collection)。Python支持解释运行(interpret),并能调用C库进行拓展。Python有强大的标准库 (battery included)。由于标准库的体系已经稳定,所以Python的生态系统开始拓展到第三方包。这些包,如Django, web.py, wxpython, numpy, matplotlib,PIL,将Python升级成了物种丰富的热带雨林。 104 | 105 | 106 | 107 | 今天Python已经进入到3.0的时代。由于Python 3.0向后不兼容,所以从2.0到3.0的过渡并不容易。另一方面,Python的性能依然值得改进,Python的运算性能低于C++和Java(见Google的讨论)。Python依然是一个在发展中的语言。我期待看到Python的未来。 108 | 109 | 110 | 111 | ##Python启示录 112 | 113 | Python崇尚优美、清晰、简单,是一个优秀并广泛使用的语言 (TIOBE语言排行第八,Google的第三大开发语言,Dropbox的基础语言,豆瓣的服务器语言)。这个世界并不缺乏优秀的语言,但Python的发展史作为一个代表,带给我许多启示。 114 | 115 | 在Python的开发过程中,社区起到了重要的作用。Guido自认为自己不是全能型的程序员,所以他只负责制订框架。如果问题太复杂,他会选择绕过去,也就是cut the corner。这些问题最终由社区中的其他人解决。社区中的人才是异常丰富的,就连创建网站,筹集基金这样与开发稍远的事情,也有人乐意于处理。如今的项目开发越来越复杂,越来越庞大,合作以及开放的心态成为项目最终成功的关键。 116 | 117 | Python从其他语言中学到了很多,无论是已经进入历史的ABC,还是依然在使用的C和Perl,以及许多没有列出的其他语言。可以说,Python的成功代表了它所有借鉴的语言的成功。同样,Ruby借鉴了Python,它的成功也代表了Python某些方面的成功。每个语言都是混合体,都有它优秀的地方,但也有各种各样的缺陷。同时,一个语言“好与不好”的评判,往往受制于平台、硬件、时代等等外部原因。程序员经历过许多语言之争。我想,为什么不以开放的心态和客观的分析,去区分一下每个语言的具体优点缺点,去区分内部和外部的因素。说不定哪一天发现,我不喜欢的某个语言中,正包含了我所需要的东西。 118 | 119 | 无论Python未来的命运如何,Python的历史已经是本很有趣的小说。 120 | 121 | 122 | 123 | 如果你因为本文对Python产生了兴趣,欢迎阅读我的Python快速教程。 124 | 125 | 126 | 127 | 本文主要参考: 128 | 129 | Guido在Dropbox所做演讲 130 | 131 | 132 | 133 | http://v.youku.com/v_show/id_XNTExOTc1NTU2.html 134 | 135 | python.org的文档 136 | 137 | Wikipedia 138 | -------------------------------------------------------------------------------- /content/Python补充01 序列的方法.md: -------------------------------------------------------------------------------- 1 | #Python补充01 序列的方法 2 | 3 | 4 | 5 | 在快速教程中,我们了解了最基本的序列(sequence)。回忆一下,序列包含有定值表(tuple)和表(list)。此外,字符串(string)是一种特殊的定值表。表的元素可以更改,定值表一旦建立,其元素不可更改。 6 | 7 | 8 | 9 | 任何的序列都可以引用其中的元素(item)。 10 | 11 | 12 | 13 | 下面的内建函数(built-in function)可用于序列(表,定值表,字符串): 14 | 15 | # s为一个序列 16 | 17 | len(s) 返回: 序列中包含元素的个数 18 | 19 | min(s) 返回: 序列中最小的元素 20 | 21 | max(s) 返回: 序列中最大的元素 22 | 23 | all(s) 返回: True, 如果所有元素都为True的话 24 | 25 | any(s) 返回: True, 如果任一元素为True的话 26 | 27 | 28 | 29 | 下面的方法主要起查询功能,不改变序列本身, 可用于表和定值表: 30 | 31 | sum(s) 返回:序列中所有元素的和 32 | 33 | # x为元素值,i为下标(元素在序列中的位置) 34 | 35 | s.count(x) 返回: x在s中出现的次数 36 | 37 | s.index(x) 返回: x在s中第一次出现的下标 38 | 39 | 40 | 41 | 由于定值表的元素不可变更,下面方法只适用于表: 42 | 43 | # l为一个表, l2为另一个表 44 | 45 | l.extend(l2) 在表l的末尾添加表l2的所有元素 46 | 47 | l.append(x) 在l的末尾附加x元素 48 | 49 | l.sort() 对l中的元素排序 50 | 51 | l.reverse() 将l中的元素逆序 52 | 53 | l.pop() 返回:表l的最后一个元素,并在表l中删除该元素 54 | 55 | del l[i] 删除该元素 56 | 57 | (以上这些方法都是在原来的表的上进行操作,会对原来的表产生影响,而不是返回一个新表。) 58 | 59 | 60 | 61 | 下面是一些用于字符串的方法。尽管字符串是定值表的特殊的一种,但字符串(string)类有一些方法是改变字符串的。这些方法的本质不是对原有字符串进行操作,而是删除原有字符串,再建立一个新的字符串,所以并不与定值表的特点相矛盾。 62 | 63 | #str为一个字符串,sub为str的一个子字符串。s为一个序列,它的元素都是字符串。width为一个整数,用于说明新生成字符串的宽度。 64 | 65 | str.count(sub) 返回:sub在str中出现的次数 66 | 67 | str.find(sub) 返回:从左开始,查找sub在str中第一次出现的位置。如果str中不包含sub,返回 -1 68 | 69 | str.index(sub) 返回:从左开始,查找sub在str中第一次出现的位置。如果str中不包含sub,举出错误 70 | 71 | str.rfind(sub) 返回:从右开始,查找sub在str中第一次出现的位置。如果str中不包含sub,返回 -1 72 | 73 | str.rindex(sub) 返回:从右开始,查找sub在str中第一次出现的位置。如果str中不包含sub,举出错误 74 | 75 | 76 | 77 | str.isalnum() 返回:True, 如果所有的字符都是字母或数字 78 | 79 | str.isalpha() 返回:True,如果所有的字符都是字母 80 | 81 | str.isdigit() 返回:True,如果所有的字符都是数字 82 | 83 | str.istitle() 返回:True,如果所有的词的首字母都是大写 84 | 85 | str.isspace() 返回:True,如果所有的字符都是空格 86 | 87 | str.islower() 返回:True,如果所有的字符都是小写字母 88 | 89 | str.isupper() 返回:True,如果所有的字符都是大写字母 90 | 91 | 92 | 93 | str.split([sep, [max]]) 返回:从左开始,以空格为分割符(separator),将str分割为多个子字符串,总共分割max次。将所得的子字符串放在一个表中返回。可以str.split(',')的方式使用逗号或者其它分割符 94 | 95 | str.rsplit([sep, [max]]) 返回:从右开始,以空格为分割符(separator),将str分割为多个子字符串,总共分割max次。将所得的子字符串放在一个表中返回。可以str.rsplit(',')的方式使用逗号或者其它分割符 96 | 97 | 98 | 99 | str.join(s) 返回:将s中的元素,以str为分割符,合并成为一个字符串。 100 | 101 | str.strip([sub]) 返回:去掉字符串开头和结尾的空格。也可以提供参数sub,去掉位于字符串开头和结尾的sub 102 | 103 | str.replace(sub, new_sub) 返回:用一个新的字符串new_sub替换str中的sub 104 | 105 | 106 | 107 | str.capitalize() 返回:将str第一个字母大写 108 | 109 | str.lower() 返回:将str全部字母改为小写 110 | 111 | str.upper() 返回:将str全部字母改为大写 112 | 113 | str.swapcase() 返回:将str大写字母改为小写,小写改为大写 114 | 115 | str.title() 返回:将str的每个词(以空格分隔)的首字母大写 116 | 117 | 118 | 119 | str.center(width) 返回:长度为width的字符串,将原字符串放入该字符串中心,其它空余位置为空格。 120 | 121 | str.ljust(width) 返回:长度为width的字符串,将原字符串左对齐放入该字符串,其它空余位置为空格。 122 | 123 | str.rjust(width) 返回:长度为width的字符串,将原字符串右对齐放入该字符串,其它空余位置为空格。 124 | -------------------------------------------------------------------------------- /content/STL01.md: -------------------------------------------------------------------------------- 1 | #Python标准库01 正则表达式 (re包) 2 | 3 | 4 | 5 | 6 | 我将从正则表达式开始讲Python的标准库。正则表达式是文字处理中常用的工具,而且不需要额外的系统知识或经验。我们会把系统相关的包放在后面讲解。 7 | 8 | 9 | 10 | 正则表达式(regular expression)主要功能是从字符串(string)中通过特定的模式(pattern),搜索想要找到的内容。 11 | 12 | 13 | ##语法 14 | 15 | 之前,我们简介了字符串相关的处理函数。我们可以通过这些函数实现简单的搜索功能,比如说从字符串“I love you”中搜索是否有“you”这一子字符串。但有些时候,我们只是模糊地知道我们想要找什么,而不能具体说出我是在找“you”,比如说,我想找出字符串中包含的数字,这些数字可以是0到9中的任何一个。这些模糊的目标可以作为信息写入正则表达式,传递给Python,从而让Python知道我们想要找的是什么。 16 | 17 | (官方documentation) 18 | 19 | 在Python中使用正则表达式需要标准库中的一个包re。 20 | ```python 21 | import re 22 | m = re.search('[0-9]','abcd4ef') 23 | print(m.group(0)) 24 | ``` 25 | re.search()接收两个参数,第一个'[0-9]'就是我们所说的正则表达式,它告诉Python的是,“听着,我从字符串想要找的是从0到9的一个数字字符”。 26 | 27 | re.search()如果从第二个参数找到符合要求的子字符串,就返回一个对象m,你可以通过m.group()的方法查看搜索到的结果。如果没有找到符合要求的字符,re.search()会返回None。 28 | 29 | 30 | 31 | 如果你熟悉Linux或者Perl, 你应该已经熟悉正则表达式。当我们打开Linux shell的时候,可以用正则表达式去查找或着删除我们想要的文件,比如说: 32 | ```bash 33 | $rm book[0-9][0-9].txt 34 | ``` 35 | 这就是要删除类似于book02.txt的文件。book[0-9][0-9].txt所包含的信息是,以book开头,后面跟两个数字字符,之后跟有".txt"的文件名。如果不符合条件的文件名,比如说: 36 | 37 | bo12.txt 38 | 39 | book1.txt 40 | 41 | book99.text 42 | 43 | 都不会被选中。 44 | 45 | Perl中内建有正则表达式的功能,据说是所有正则表达式系统中最强的,这也是Perl成为系统管理员利器的一个原因。 46 | 47 | 48 | 49 | ##正则表达式的函数 50 | ```python 51 | m = re.search(pattern, string) # 搜索整个字符串,直到发现符合的子字符串。 52 | m = re.match(pattern, string) # 从头开始检查字符串是否符合正则表达式。必须从字符串的第一个字符开始就相符。 53 | ``` 54 | 可以从这两个函数中选择一个进行搜索。上面的例子中,我们如果使用re.match()的话,则会得到None,因为字符串的起始为‘a’, 不符合'[0-9]'的要求。 55 | 56 | 57 | 对于返回的m, 我们使用m.group()来调用结果。(我们会在后面更详细解释m.group()) 58 | 59 | 60 | 61 | 我们还可以在搜索之后将搜索到的子字符串进行替换: 62 | 63 | str = re.sub(pattern, replacement, string) # 在string中利用正则变换pattern进行搜索,对于搜索到的字符串,用另一字符串replacement替换。返回替换后的字符串。 64 | 65 | 66 | 此外,常用的正则表达式函数还有 67 | 68 | re.split() # 根据正则表达式分割字符串, 将分割后的所有子字符串放在一个表(list)中返回 69 | 70 | re.findall() # 根据正则表达式搜索字符串,将所有符合的子字符串放在一给表(list)中返回 71 | 72 | 73 | 74 | (在熟悉了上面的函数后,可以看一下re.compile(),以便于提高搜索效率。) 75 | 76 | 77 | 78 | ##写一个正则表达式 79 | 80 | 关键在于将信息写成一个正则表达式。我们先看正则表达式的常用语法: 81 | 82 | 1)单个字符: 83 | 84 | . 任意的一个字符 85 | 86 | a|b 字符a或字符b 87 | 88 | [afg] a或者f或者g的一个字符 89 | 90 | [0-4] 0-4范围内的一个字符 91 | 92 | [a-f] a-f范围内的一个字符 93 | 94 | [^m] 不是m的一个字符 95 | 96 | \s 一个空格 97 | 98 | \S 一个非空格 99 | 100 | \d [0-9] 101 | 102 | \D [^0-9] 103 | 104 | \w [0-9a-zA-Z] 105 | 106 | \W [^0-9a-zA-Z] 107 | 108 | 109 | 110 | 2)重复 111 | 112 | 紧跟在单个字符之后,表示多个这样类似的字符 113 | 114 | * 重复 >=0 次 115 | 116 | + 重复 >=1 次 117 | 118 | ? 重复 0或者1 次 119 | 120 | {m} 重复m次。比如说 a{4}相当于aaaa,再比如说[1-3]{2}相当于[1-3][1-3] 121 | 122 | {m, n} 重复m到n次。比如说a{2, 5}表示a重复2到5次。小于m次的重复,或者大于n次的重复都不符合条件。 123 | 124 | 125 | 126 | 正则表达 相符的字符串举例 127 | 128 | [0-9]{3,5} 9678 129 | 130 | a?b b 131 | 132 | a+b aaaaab 133 | 134 | 135 | 136 | 3) 位置 137 | 138 | ^ 字符串的起始位置 139 | 140 | $ 字符串的结尾位置 141 | 142 | 143 | 144 | 正则表达 相符的字符串举例 不相符字符串 145 | 146 | ^ab.*c$ abeec cabeec (如果用re.search(), 将无法找到。) 147 | 148 | 149 | 4)返回控制 150 | 151 | 我们有可能对搜索的结果进行进一步精简信息。比如下面一个正则表达式: 152 | 153 | output_(\d{4}) 154 | 155 | 该正则表达式用括号()包围了一个小的正则表达式,\d{4}。 这个小的正则表达式被用于从结果中筛选想要的信息(在这里是四位数字)。这样被括号圈起来的正则表达式的一部分,称为群(group)。 156 | 我们可以m.group(number)的方法来查询群。group(0)是整个正则表达的搜索结果,group(1)是第一个群…… 157 | ```python 158 | import re 159 | m = re.search("output_(\d{4})", "output_1986.txt") 160 | print(m.group(1)) 161 | ``` 162 | 163 | 我们还可以将群命名,以便更好地使用m.group查询: 164 | ```python 165 | import re 166 | m = re.search("output_(?P\d{4})", "output_1986.txt") #(?P...) 为group命名 167 | print(m.group("year")) 168 | ``` 169 | 170 | 对比re.search和re.match 171 | 本节开头我们用re.search实现数字子串的提取; 172 | m = re.search('[0-9]','abcd4ef') 173 | 使用re.match同样实现上述功能(提取数字子串),我们需要用到小括号'()': 174 | ```python 175 | m = re.match('.*([0-9]+).*','abcd4ef') 176 | # 注意,我们通过m.group(1)来获取数字字串,下标0处是匹配的整个字符串 177 | print m.group(0), m.group(1) 178 | abcd4ef 4 179 | ``` 180 | 很明显,这里使用match方法要繁琐一些;体会两个方法的差异,搜索整个字符串中的子串使用re.search,而从头开始检查字符串是否符合正则表达式选用re.match; 181 | 182 | 练习 183 | 有一个文件,文件名为output_1981.10.21.txt 。下面使用Python: 读取文件名中的日期时间信息,并找出这一天是周几。将文件改名为output_YYYY-MM-DD-W.txt (YYYY:四位的年,MM:两位的月份,DD:两位的日,W:一位的周几,并假设周一为一周第一天) 184 | 185 | 186 | 187 | 188 | 189 | ##总结 190 | 191 | re.search() re.match() re.sub() re.findall() 192 | 193 | 正则表达式构成方法 194 | -------------------------------------------------------------------------------- /content/STL02.md: -------------------------------------------------------------------------------- 1 | #Python标准库02 时间与日期 (time, datetime包) 2 | 3 | 4 | 5 | Python具有良好的时间和日期管理功能。实际上,计算机只会维护一个挂钟时间(wall clock time),这个时间是从某个固定时间起点到现在的时间间隔。时间起点的选择与计算机相关,但一台计算机的话,这一时间起点是固定的。其它的日期信息都是从这一时间计算得到的。此外,计算机还可以测量CPU实际上运行的时间,也就是处理器时间(processor clock time),以测量计算机性能。当CPU处于闲置状态时,处理器时间会暂停。 6 | 7 | 8 | 9 | ##time包 10 | 11 | time包基于C语言的库函数(library functions)。Python的解释器通常是用C编写的,Python的一些函数也会直接调用C语言的库函数。 12 | ```python 13 | import time 14 | print(time.time()) # wall clock time, unit: second 15 | print(time.clock()) # processor clock time, unit: second 16 | ``` 17 | 18 | time.sleep()可以将程序置于休眠状态,直到某时间间隔之后再唤醒程序,让程序继续运行。 19 | ```python 20 | import time 21 | print('start') 22 | time.sleep(10) # sleep for 10 seconds 23 | print('wake up') 24 | ``` 25 | 当我们需要定时地查看程序运行状态时,就可以利用该方法。 26 | 27 | 28 | 29 | time包还定义了struct_time对象。该对象实际上是将挂钟时间转换为年、月、日、时、分、秒……等日期信息,存储在该对象的各个属性中(tm_year, tm_mon, tm_mday...)。下面方法可以将挂钟时间转换为struct_time对象: 30 | ```python 31 | st = time.gmtime() # 返回struct_time格式的UTC时间 32 | st = time.localtime() # 返回struct_time格式的当地时间, 当地时区根据系统环境决定。 33 | 34 | s = time.mktime(st) # 将struct_time格式转换成wall clock time 35 | ``` 36 | 37 | ##datetime包 38 | 39 | 1) 简介 40 | 41 | datetime包是基于time包的一个高级包, 为我们提供了多一层的便利。 42 | 43 | datetime可以理解为date和time两个组成部分。date是指年月日构成的日期(相当于日历),time是指时分秒微秒构成的一天24小时中的具体时间(相当于手表)。你可以将这两个分开管理(datetime.date类,datetime.time类),也可以将两者合在一起(datetime.datetime类)。由于其构造大同小异,我们将只介绍datetime.datetime类。 44 | 45 | 比如说我现在看到的时间,是2012年9月3日21时30分,我们可以用如下方式表达: 46 | ```python 47 | import datetime 48 | t = datetime.datetime(2012,9,3,21,30) 49 | print(t) 50 | ``` 51 | 所返回的t有如下属性: 52 | 53 | hour, minute, second, microsecond 54 | 55 | year, month, day, weekday # weekday表示周几 56 | 57 | 58 | 59 | 2) 运算 60 | 61 | datetime包还定义了时间间隔对象(timedelta)。一个时间点(datetime)加上一个时间间隔(timedelta)可以得到一个新的时间点(datetime)。比如今天的上午3点加上5个小时得到今天的上午8点。同理,两个时间点相减会得到一个时间间隔。 62 | 63 | ```python 64 | import datetime 65 | t = datetime.datetime(2012,9,3,21,30) 66 | t_next = datetime.datetime(2012,9,5,23,30) 67 | delta1 = datetime.timedelta(seconds = 600) 68 | delta2 = datetime.timedelta(weeks = 3) 69 | print(t + delta1) 70 | print(t + delta2) 71 | print(t_next - t) 72 | ``` 73 | 在给datetime.timedelta传递参数(如上的seconds和weeks)的时候,还可以是days, hours, milliseconds, microseconds。 74 | 75 | 76 | 77 | 两个datetime对象还可以进行比较。比如使用上面的t和t_next: 78 | ```python 79 | print(t > t_next) 80 | ``` 81 | 82 | 3) datetime对象与字符串转换 83 | 84 | 假如我们有一个的字符串,我们如何将它转换成为datetime对象呢? 85 | 86 | 一个方法是用上一讲的正则表达式来搜索字符串。但时间信息实际上有很明显的特征,我们可以用格式化读取的方式读取时间信息。 87 | ```python 88 | from datetime import datetime 89 | format = "output-%Y-%m-%d-%H%M%S.txt" 90 | str = "output-1997-12-23-030000.txt" 91 | t = datetime.strptime(str, format) 92 | strptime, p = parsing 93 | ``` 94 | 95 | 我们通过format来告知Python我们的str字符串中包含的日期的格式。在format中,%Y表示年所出现的位置, %m表示月份所出现的位置……。 96 | 97 | 反过来,我们也可以调用datetime对象的strftime()方法,来将datetime对象转换为特定格式的字符串。比如上面所定义的t_next, 98 | ```python 99 | print(t_next.strftime(format)) 100 | ``` 101 | strftime, f = formatting 102 | 103 | 具体的格式写法可参阅官方文档。 如果是Linux系统,也可查阅date命令的手册($man date),两者相通。 104 | 105 | 106 | 107 | ##总结 108 | 109 | 时间,休眠 110 | 111 | datetime, timedelta 112 | 113 | 格式化时间 114 | 115 | 116 | -------------------------------------------------------------------------------- /content/STL03.md: -------------------------------------------------------------------------------- 1 | #Python标准库03 路径与文件 (os.path包, glob包) 2 | 3 | 4 | 5 | 6 | 7 | 路径与文件的简介请参看Linux文件系统 8 | 9 | 10 | 11 | ##os.path包 12 | 13 | os.path包主要是处理路径字符串,比如说'/home/vamei/doc/file.txt',提取出有用信息。 14 | 15 | ```python 16 | import os.path 17 | path = '/home/vamei/doc/file.txt' 18 | 19 | print(os.path.basename(path)) # 查询路径中包含的文件名 20 | print(os.path.dirname(path)) # 查询路径中包含的目录 21 | 22 | info = os.path.split(path) # 将路径分割成文件名和目录两个部分,放在一个表中返回 23 | path2 = os.path.join('/', 'home', 'vamei', 'doc', 'file1.txt') # 使用目录名和文件名构成一个路径字符串 24 | 25 | p_list = [path, path2] 26 | print(os.path.commonprefix(p_list)) # 查询多个路径的共同部分 27 | ``` 28 | 此外,还有下面的方法: 29 | 30 | os.path.normpath(path) # 去除路径path中的冗余。比如'/home/vamei/../.'被转化为'/home' 31 | 32 | 33 | 34 | os.path还可以查询文件的相关信息(metadata)。文件的相关信息不存储在文件内部,而是由操作系统维护的,关于文件的一些信息(比如文件类型,大小,修改时间)。 35 | 36 | ```python 37 | import os.path 38 | path = '/home/vamei/doc/file.txt' 39 | 40 | print(os.path.exists(path)) # 查询文件是否存在 41 | 42 | print(os.path.getsize(path)) # 查询文件大小 43 | print(os.path.getatime(path)) # 查询文件上一次读取的时间 44 | print(os.path.getmtime(path)) # 查询文件上一次修改的时间 45 | 46 | print(os.path.isfile(path)) # 路径是否指向常规文件 47 | print(os.path.isdir(path)) # 路径是否指向目录文件 48 | ``` 49 | (实际上,这一部份类似于Linux中的ls命令的某些功能) 50 | 51 | 52 | 53 | ##glob包 54 | 55 | glob包最常用的方法只有一个, glob.glob()。该方法的功能与Linux中的ls相似(参看Linux文件管理命令),接受一个Linux式的文件名格式表达式(filename pattern expression),列出所有符合该表达式的文件(与正则表达式类似),将所有文件名放在一个表中返回。所以glob.glob()是一个查询目录下文件的好方法。 56 | 57 | 该文件名表达式的语法与Python自身的正则表达式不同 (你可以同时看一下fnmatch包,它的功能是检测一个文件名是否符合Linux的文件名格式表达式)。 如下: 58 | 59 | Filename Pattern Expression Python Regular Expression 60 | 61 | * .* 62 | 63 | ? . 64 | 65 | [0-9] same 66 | 67 | [a-e] same 68 | 69 | [^mnp] same 70 | 71 | 72 | 73 | 我们可以用该命令找出/home/vamei下的所有文件: 74 | ```python 75 | import glob 76 | print(glob.glob('/home/vamei/*')) 77 | ``` 78 | 79 | ##总结 80 | 81 | 文件系统 82 | 83 | os.path 84 | 85 | glob.glob 86 | -------------------------------------------------------------------------------- /content/STL04.md: -------------------------------------------------------------------------------- 1 | #Python标准库04 文件管理 (部分os包,shutil包) 2 | 3 | 4 | 5 | 在操作系统下,用户可以通过操作系统的命令来管理文件,参考linux文件管理相关命令。Python标准库则允许我们从Python内部管理文件。相同的目的,我们有了两条途径。尽管在Python调用标准库的方式不如操作系统命令直接,但有它自己的优势。你可以利用Python语言,并发挥其他Python工具,形成组合的文件管理功能。Python or Shell? 这是留给用户的选择。本文中会尽量将两者相似的功能相对应。 6 | 7 | 本文基于linux文件管理背景知识 8 | 9 | 10 | 11 | ##os包 12 | 13 | os包包括各种各样的函数,以实现操作系统的许多功能。这个包非常庞杂。os包的一些命令就是用于文件管理。我们这里列出最常用的: 14 | 15 | mkdir(path) 16 | 17 | 创建新目录,path为一个字符串,表示新目录的路径。相当于$mkdir命令 18 | 19 | rmdir(path) 20 | 21 | 删除空的目录,path为一个字符串,表示想要删除的目录的路径。相当于$rmdir命令 22 | 23 | listdir(path) 24 | 25 | 返回目录中所有文件。相当于$ls命令。 26 | 27 | 28 | 29 | remove(path) 30 | 31 | 删除path指向的文件。 32 | 33 | rename(src, dst) 34 | 35 | 重命名文件,src和dst为两个路径,分别表示重命名之前和之后的路径。 36 | 37 | 38 | 39 | chmod(path, mode) 40 | 41 | 改变path指向的文件的权限。相当于$chmod命令。 42 | 43 | chown(path, uid, gid) 44 | 45 | 改变path所指向文件的拥有者和拥有组。相当于$chown命令。 46 | 47 | stat(path) 48 | 49 | 查看path所指向文件的附加信息,相当于$ls -l命令。 50 | 51 | symlink(src, dst) 52 | 53 | 为文件dst创建软链接,src为软链接文件的路径。相当于$ln -s命令。 54 | 55 | 56 | 57 | getcwd() 58 | 59 | 查询当前工作路径 (cwd, current working directory),相当于$pwd命令。 60 | 61 | 62 | 63 | 比如说我们要新建目录new: 64 | ```python 65 | import os 66 | os.mkdir('/home/vamei/new') 67 | ``` 68 | 69 | ##shutil包 70 | 71 | copy(src, dst) 72 | 73 | 复制文件,从src到dst。相当于$cp命令。 74 | 75 | move(src, dst) 76 | 77 | 移动文件,从src到dst。相当于$mv命令。 78 | 79 | 80 | 81 | 比如我们想复制文件a.txt: 82 | ```python 83 | import shutil 84 | shutil.copy('a.txt', 'b.txt') 85 | ``` 86 | 87 | 想深入细节,请参照官方文档os, shutil。 88 | 89 | 结合本章以及之前的内容,我们把Python打造成一个文件管理的利器了。 90 | 91 | 92 | 93 | ##总结 94 | 95 | os包: rmdir, mkdir, listdir, remove, rename, chmod, chown, stat, symlink 96 | 97 | shutil包: copy, move 98 | -------------------------------------------------------------------------------- /content/STL05.md: -------------------------------------------------------------------------------- 1 | #Python标准库05 存储对象 (pickle包,cPickle包) 2 | 3 | 4 | 5 | 6 | 7 | 在之前对Python对象的介绍中 (面向对象的基本概念,面向对象的进一步拓展),我提到过Python“一切皆对象”的哲学,在Python中,无论是变量还是函数,都是一个对象。当Python运行时,对象存储在内存中,随时等待系统的调用。然而,内存里的数据会随着计算机关机和消失,如何将对象保存到文件,并储存在硬盘上呢? 8 | 9 | 计算机的内存中存储的是二进制的序列 (当然,在Linux眼中,是文本流)。我们可以直接将某个对象所对应位置的数据抓取下来,转换成文本流 (这个过程叫做serialize),然后将文本流存入到文件中。由于Python在创建对象时,要参考对象的类定义,所以当我们从文本中读取对象时,必须在手边要有该对象的类定义,才能懂得如何去重建这一对象。从文件读取时,对于Python的内建(built-in)对象 (比如说整数、词典、表等等),由于其类定义已经载入内存,所以不需要我们再在程序中定义类。但对于用户自行定义的对象,就必须要先定义类,然后才能从文件中载入对象 (比如面向对象的基本概念中的对象那个summer)。 10 | 11 | 12 | 13 | ##pickle包 14 | 15 | 对于上述过程,最常用的工具是Python中的pickle包。 16 | 17 | 1) 将内存中的对象转换成为文本流: 18 | 19 | ```pyhton 20 | import pickle 21 | 22 | # define class 23 | class Bird(object): 24 | have_feather = True 25 | way_of_reproduction = 'egg' 26 | 27 | summer = Bird() # construct an object 28 | picklestring = pickle.dumps(summer) # serialize object 29 | ``` 30 | 使用pickle.dumps()方法可以将对象summer转换成了字符串 picklestring(也就是文本流)。随后我们可以用普通文本的存储方法来将该字符串储存在文件(文本文件的输入输出)。 31 | 32 | 33 | 34 | 当然,我们也可以使用pickle.dump()的方法,将上面两部合二为一: 35 | 36 | ```python 37 | import pickle 38 | 39 | # define class 40 | class Bird(object): 41 | have_feather = True 42 | way_of_reproduction = 'egg' 43 | 44 | summer = Bird() # construct an object 45 | fn = 'a.pkl' 46 | with open(fn, 'w') as f: # open file with write-mode 47 | picklestring = pickle.dump(summer, f) # serialize and save object 48 | ``` 49 | 对象summer存储在文件a.pkl 50 | 51 | 52 | 53 | 2) 重建对象 54 | 55 | 首先,我们要从文本中读出文本,存储到字符串 (文本文件的输入输出)。然后使用pickle.loads(str)的方法,将字符串转换成为对象。要记得,此时我们的程序中必须已经有了该对象的类定义。 56 | 57 | 58 | 59 | 此外,我们也可以使用pickle.load()的方法,将上面步骤合并: 60 | 61 | ```python 62 | import pickle 63 | 64 | # define the class before unpickle 65 | class Bird(object): 66 | have_feather = True 67 | way_of_reproduction = 'egg' 68 | 69 | fn = 'a.pkl' 70 | with open(fn, 'r') as f: 71 | summer = pickle.load(f) # read file and build object 72 | ``` 73 | 74 | 75 | 76 | 77 | ##cPickle包 78 | 79 | cPickle包的功能和用法与pickle包几乎完全相同 (其存在差别的地方实际上很少用到),不同在于cPickle是基于c语言编写的,速度是pickle包的1000倍。对于上面的例子,如果想使用cPickle包,我们都可以将import语句改为: 80 | 81 | import cPickle as pickle 82 | 就不需要再做任何改动了。 83 | 84 | 85 | 86 | ##总结 87 | 88 | 对象 -> 文本 -> 文件 89 | 90 | pickle.dump(), pickle.load(), cPickle 91 | -------------------------------------------------------------------------------- /content/STL06.md: -------------------------------------------------------------------------------- 1 | #Python标准库06 子进程 (subprocess包) 2 | 3 | 4 | 5 | 6 | 这里的内容以Linux进程基础和Linux文本流为基础。subprocess包主要功能是执行外部的命令和程序。比如说,我需要使用wget下载文件。我在Python中调用wget程序。从这个意义上来说,subprocess的功能与shell类似。 7 | 8 | 9 | 10 | ##subprocess以及常用的封装函数 11 | 12 | 当我们运行python的时候,我们都是在创建并运行一个进程。正如我们在Linux进程基础中介绍的那样,一个进程可以fork一个子进程,并让这个子进程exec另外一个程序。在Python中,我们通过标准库中的subprocess包来fork一个子进程,并运行一个外部的程序(fork,exec见Linux进程基础)。 13 | 14 | 15 | 16 | subprocess包中定义有数个创建子进程的函数,这些函数分别以不同的方式创建子进程,所以我们可以根据需要来从中选取一个使用。另外subprocess还提供了一些管理标准流(standard stream)和管道(pipe)的工具,从而在进程间使用文本通信。 17 | 18 | 19 | 20 | 使用subprocess包中的函数创建子进程的时候,要注意: 21 | 22 | 1) 在创建子进程之后,父进程是否暂停,并等待子进程运行。 23 | 24 | 2) 函数返回什么 25 | 26 | 3) 当returncode不为0时,父进程如何处理。 27 | 28 | 29 | 30 | subprocess.call() 31 | 父进程等待子进程完成 32 | 返回退出信息(returncode,相当于exit code,见Linux进程基础) 33 | 34 | 35 | 36 | subprocess.check_call() 37 | 38 | 父进程等待子进程完成 39 | 40 | 返回0 41 | 42 | 检查退出信息,如果returncode不为0,则举出错误subprocess.CalledProcessError,该对象包含有returncode属性,可用try...except...来检查(见Python错误处理)。 43 | 44 | 45 | 46 | subprocess.check_output() 47 | 48 | 父进程等待子进程完成 49 | 50 | 返回子进程向标准输出的输出结果 51 | 52 | 检查退出信息,如果returncode不为0,则举出错误subprocess.CalledProcessError,该对象包含有returncode属性和output属性,output属性为标准输出的输出结果,可用try...except...来检查。 53 | 54 | 55 | 56 | 这三个函数的使用方法相类似,我们以subprocess.call()来说明: 57 | ```python 58 | import subprocess 59 | rc = subprocess.call(["ls","-l"]) 60 | ``` 61 | 我们将程序名(ls)和所带的参数(-l)一起放在一个表中传递给subprocess.call() 62 | 63 | 64 | 65 | 可以通过一个shell来解释一整个字符串: 66 | ```python 67 | import subprocess 68 | out = subprocess.call("ls -l", shell=True) 69 | out = subprocess.call("cd ..", shell=True) 70 | ``` 71 | 我们使用了shell=True这个参数。这个时候,我们使用一整个字符串,而不是一个表来运行子进程。Python将先运行一个shell,再用这个shell来解释这整个字符串。 72 | 73 | shell命令中有一些是shell的内建命令,这些命令必须通过shell运行,$cd。shell=True允许我们运行这样一些命令。 74 | 75 | 76 | 77 | ##Popen() 78 | 79 | 实际上,我们上面的三个函数都是基于Popen()的封装(wrapper)。这些封装的目的在于让我们容易使用子进程。当我们想要更个性化我们的需求的时候,就要转向Popen类,该类生成的对象用来代表子进程。 80 | 81 | 82 | 83 | 与上面的封装不同,Popen对象创建后,主程序不会自动等待子进程完成。我们必须调用对象的wait()方法,父进程才会等待 (也就是阻塞block): 84 | ```python 85 | import subprocess 86 | child = subprocess.Popen(["ping","-c","5","www.google.com"]) 87 | print("parent process") 88 | ``` 89 | 从运行结果中看到,父进程在开启子进程之后并没有等待child的完成,而是直接运行print。 90 | 91 | 92 | 93 | 对比等待的情况: 94 | ```python 95 | import subprocess 96 | child = subprocess.Popen(["ping","-c","5","www.google.com"]) 97 | child.wait() 98 | print("parent process") 99 | ``` 100 | 101 | 此外,你还可以在父进程中对子进程进行其它操作,比如我们上面例子中的child对象: 102 | 103 | child.poll() # 检查子进程状态 104 | 105 | child.kill() # 终止子进程 106 | 107 | child.send_signal() # 向子进程发送信号 108 | 109 | child.terminate() # 终止子进程 110 | 111 | 112 | 113 | 子进程的PID存储在child.pid 114 | 115 | 116 | 117 | ##子进程的文本流控制 118 | 119 | (沿用child子进程) 子进程的标准输入,标准输出和标准错误也可以通过如下属性表示: 120 | 121 | child.stdin 122 | 123 | child.stdout 124 | 125 | child.stderr 126 | 127 | 128 | 129 | 我们可以在Popen()建立子进程的时候改变标准输入、标准输出和标准错误,并可以利用subprocess.PIPE将多个子进程的输入和输出连接在一起,构成管道(pipe): 130 | ```python 131 | import subprocess 132 | child1 = subprocess.Popen(["ls","-l"], stdout=subprocess.PIPE) 133 | child2 = subprocess.Popen(["wc"], stdin=child1.stdout,stdout=subprocess.PIPE) 134 | out = child2.communicate() 135 | print(out) 136 | ``` 137 | subprocess.PIPE实际上为文本流提供一个缓存区。child1的stdout将文本输出到缓存区,随后child2的stdin从该PIPE中将文本读取走。child2的输出文本也被存放在PIPE中,直到communicate()方法从PIPE中读取出PIPE中的文本。 138 | 139 | 要注意的是,communicate()是Popen对象的一个方法,该方法会阻塞父进程,直到子进程完成。 140 | 141 | 142 | 143 | 我们还可以利用communicate()方法来使用PIPE给子进程输入: 144 | ```python 145 | import subprocess 146 | child = subprocess.Popen(["cat"], stdin=subprocess.PIPE) 147 | child.communicate("vamei") 148 | ``` 149 | 我们启动子进程之后,cat会等待输入,直到我们用communicate()输入"vamei"。 150 | 151 | 152 | 153 | 通过使用subprocess包,我们可以运行外部程序。这极大的拓展了Python的功能。如果你已经了解了操作系统的某些应用,你可以从Python中直接调用该应用(而不是完全依赖Python),并将应用的结果输出给Python,并让Python继续处理。shell的功能(比如利用文本流连接各个应用),就可以在Python中实现。 154 | 155 | 156 | 157 | ##总结 158 | 159 | subprocess.call, subprocess.check_call(), subprocess.check_output() 160 | 161 | subprocess.Popen(), subprocess.PIPE 162 | 163 | Popen.wait(), Popen.communicate() 164 | -------------------------------------------------------------------------------- /content/STL07.md: -------------------------------------------------------------------------------- 1 | #Python标准库07 信号 (signal包,部分os包) 2 | 3 | 4 | 5 | 在了解了Linux的信号基础之后,Python标准库中的signal包就很容易学习和理解。signal包负责在Python程序内部处理信号,典型的操作包括预设信号处理函数,暂停并等待信号,以及定时发出SIGALRM等。要注意,signal包主要是针对UNIX平台(比如Linux, MAC OS),而Windows内核中由于对信号机制的支持不充分,所以在Windows上的Python不能发挥信号系统的功能。 6 | 7 | 8 | 9 | ##定义信号名 10 | 11 | signal包定义了各个信号名及其对应的整数,比如 12 | ```python 13 | import signal 14 | print signal.SIGALRM 15 | print signal.SIGCONT 16 | ``` 17 | Python所用的信号名和Linux一致。你可以通过 18 | ```bash 19 | $man 7 signal 20 | ``` 21 | 查询 22 | 23 | 24 | 25 | ##预设信号处理函数 26 | 27 | signal包的核心是使用signal.signal()函数来预设(register)信号处理函数,如下所示: 28 | 29 | singnal.signal(signalnum, handler) 30 | 31 | signalnum为某个信号,handler为该信号的处理函数。我们在信号基础里提到,进程可以无视信号,可以采取默认操作,还可以自定义操作。当handler为signal.SIG_IGN时,信号被无视(ignore)。当handler为singal.SIG_DFL,进程采取默认操作(default)。当handler为一个函数名时,进程采取函数中定义的操作。 32 | 33 | ```python 34 | import signal 35 | # Define signal handler function 36 | def myHandler(signum, frame): 37 | print('I received: ', signum) 38 | 39 | # register signal.SIGTSTP's handler 40 | signal.signal(signal.SIGTSTP, myHandler) 41 | signal.pause() 42 | print('End of Signal Demo') 43 | ``` 44 | 45 | 46 | 在主程序中,我们首先使用signal.signal()函数来预设信号处理函数。然后我们执行signal.pause()来让该进程暂停以等待信号,以等待信号。当信号SIGUSR1被传递给该进程时,进程从暂停中恢复,并根据预设,执行SIGTSTP的信号处理函数myHandler()。myHandler的两个参数一个用来识别信号(signum),另一个用来获得信号发生时,进程栈的状况(stack frame)。这两个参数都是由signal.singnal()函数来传递的。 47 | 48 | 上面的程序可以保存在一个文件中(比如test.py)。我们使用如下方法运行: 49 | 50 | $python test.py 51 | 52 | 以便让进程运行。当程序运行到signal.pause()的时候,进程暂停并等待信号。此时,通过按下CTRL+Z向该进程发送SIGTSTP信号。我们可以看到,进程执行了myHandle()函数, 随后返回主程序,继续执行。(当然,也可以用$ps查询process ID, 再使用$kill来发出信号。) 53 | 54 | (进程并不一定要使用signal.pause()暂停以等待信号,它也可以在进行工作中接受信号,比如将上面的signal.pause()改为一个需要长时间工作的循环。) 55 | 56 | 57 | 58 | 我们可以根据自己的需要更改myHandler()中的操作,以针对不同的信号实现个性化的处理。 59 | 60 | 61 | 62 | ##定时发出SIGALRM信号 63 | 64 | 一个有用的函数是signal.alarm(),它被用于在一定时间之后,向进程自身发送SIGALRM信号: 65 | 66 | ```python 67 | import signal 68 | # Define signal handler function 69 | def myHandler(signum, frame): 70 | print("Now, it's the time") 71 | exit() 72 | 73 | # register signal.SIGALRM's handler 74 | signal.signal(signal.SIGALRM, myHandler) 75 | signal.alarm(5) 76 | while True: 77 | print('not yet') 78 | ``` 79 | 我们这里用了一个无限循环以便让进程持续运行。在signal.alarm()执行5秒之后,进程将向自己发出SIGALRM信号,随后,信号处理函数myHandler开始执行。 80 | 81 | 82 | 83 | ##发送信号 84 | 85 | signal包的核心是设置信号处理函数。除了signal.alarm()向自身发送信号之外,并没有其他发送信号的功能。但在os包中,有类似于linux的kill命令的函数,分别为 86 | 87 | os.kill(pid, sid) 88 | 89 | os.killpg(pgid, sid) 90 | 91 | 分别向进程和进程组(见Linux进程关系)发送信号。sid为信号所对应的整数或者singal.SIG*。 92 | 93 | 94 | 95 | 实际上signal, pause,kill和alarm都是Linux应用编程中常见的C库函数,在这里,我们只不过是用Python语言来实现了一下。实际上,Python 的解释器是使用C语言来编写的,所以有此相似性也并不意外。此外,在Python 3.4中,signal包被增强,信号阻塞等功能被加入到该包中。我们暂时不深入到该包中。 96 | 97 | 98 | 99 | ##总结 100 | 101 | signal.SIG* 102 | 103 | signal.signal() 104 | 105 | signal.pause() 106 | 107 | signal.alarm() 108 | -------------------------------------------------------------------------------- /content/STL08.md: -------------------------------------------------------------------------------- 1 | ##Python标准库08 多线程与同步 (threading包) 2 | 3 | 4 | 5 | 6 | Python主要通过标准库中的threading包来实现多线程。在当今网络时代,每个服务器都会接收到大量的请求。服务器可以利用多线程的方式来处理这些请求,以提高对网络端口的读写效率。Python是一种网络服务器的后台工作语言 (比如豆瓣网),所以多线程也就很自然被Python语言支持。 7 | 8 | (关于多线程的原理和C实现方法,请参考我之前写的Linux多线程与同步,要了解race condition, mutex和condition variable的概念) 9 | 10 | 11 | 12 | ##多线程售票以及同步 13 | 14 | 我们使用Python来实现Linux多线程与同步文中的售票程序。我们使用mutex (也就是Python中的Lock类对象) 来实现线程的同步: 15 | 16 | ```python 17 | # A program to simulate selling tickets in multi-thread way 18 | # Written by Vamei 19 | 20 | import threading 21 | import time 22 | import os 23 | 24 | # This function could be any function to do other chores. 25 | def doChore(): 26 | time.sleep(0.5) 27 | 28 | # Function for each thread 29 | def booth(tid): 30 | global i 31 | global lock 32 | while True: 33 | lock.acquire() # Lock; or wait if other thread is holding the lock 34 | if i != 0: 35 | i = i - 1 # Sell tickets 36 | print(tid,':now left:',i) # Tickets left 37 | doChore() # Other critical operations 38 | else: 39 | print("Thread_id",tid," No more tickets") 40 | os._exit(0) # Exit the whole process immediately 41 | lock.release() # Unblock 42 | doChore() # Non-critical operations 43 | 44 | # Start of the main function 45 | i = 100 # Available ticket number 46 | lock = threading.Lock() # Lock (i.e., mutex) 47 | 48 | # Start 10 threads 49 | for k in range(10): 50 | new_thread = threading.Thread(target=booth,args=(k,)) # Set up thread; target: the callable (function) to be run, args: the argument for the callable 51 | new_thread.start() # run the thread 52 | ``` 53 | 54 | 我们使用了两个全局变量,一个是i,用以储存剩余票数;一个是lock对象,用于同步线程对i的修改。此外,在最后的for循环中,我们总共设置了10个线程。每个线程都执行booth()函数。线程在调用start()方法的时候正式启动 (实际上,计算机中最多会有11个线程,因为主程序本身也会占用一个线程)。Python使用threading.Thread对象来代表线程,用threading.Lock对象来代表一个互斥锁 (mutex)。 55 | 56 | 有两点需要注意: 57 | 58 | 我们在函数中使用global来声明变量为全局变量,从而让多线程共享i和lock (在C语言中,我们通过将变量放在所有函数外面来让它成为全局变量)。如果不这么声明,由于i和lock是不可变数据对象,它们将被当作一个局部变量(参看Python动态类型)。如果是可变数据对象的话,则不需要global声明。我们甚至可以将可变数据对象作为参数来传递给线程函数。这些线程将共享这些可变数据对象。 59 | 我们在booth中使用了两个doChore()函数。可以在未来改进程序,以便让线程除了进行i=i-1之外,做更多的操作,比如打印剩余票数,找钱,或者喝口水之类的。第一个doChore()依然在Lock内部,所以可以安全地使用共享资源 (critical operations, 比如打印剩余票数)。第二个doChore()时,Lock已经被释放,所以不能再去使用共享资源。这时候可以做一些不使用共享资源的操作 (non-critical operation, 比如找钱、喝水)。我故意让doChore()等待了0.5秒,以代表这些额外的操作可能花费的时间。你可以定义的函数来代替doChore()。 60 | 61 | 62 | ##OOP创建线程 63 | 64 | 上面的Python程序非常类似于一个面向过程的C程序。我们下面介绍如何通过面向对象 (OOP, object-oriented programming,参看Python面向对象的基本概念和Python面向对象的进一步拓展) 的方法实现多线程,其核心是继承threading.Thread类。我们上面的for循环中已经利用了threading.Thread()的方法来创建一个Thread对象,并将函数booth()以及其参数传递给改对象,并调用start()方法来运行线程。OOP的话,通过修改Thread类的run()方法来定义线程所要执行的命令。 65 | 66 | ```python 67 | # A program to simulate selling tickets in multi-thread way 68 | # Written by Vamei 69 | 70 | import threading 71 | import time 72 | import os 73 | 74 | # This function could be any function to do other chores. 75 | def doChore(): 76 | time.sleep(0.5) 77 | 78 | # Function for each thread 79 | class BoothThread(threading.Thread): 80 | def __init__(self, tid, monitor): 81 | self.tid = tid 82 | self.monitor = monitor 83 | threading.Thread.__init__(self) 84 | def run(self): 85 | while True: 86 | monitor['lock'].acquire() # Lock; or wait if other thread is holding the lock 87 | if monitor['tick'] != 0: 88 | monitor['tick'] = monitor['tick'] - 1 # Sell tickets 89 | print(self.tid,':now left:',monitor['tick']) # Tickets left 90 | doChore() # Other critical operations 91 | else: 92 | print("Thread_id",self.tid," No more tickets") 93 | os._exit(0) # Exit the whole process immediately 94 | monitor['lock'].release() # Unblock 95 | doChore() # Non-critical operations 96 | 97 | # Start of the main function 98 | monitor = {'tick':100, 'lock':threading.Lock()} 99 | 100 | # Start 10 threads 101 | for k in range(10): 102 | new_thread = BoothThread(k, monitor) 103 | new_thread.start() 104 | ``` 105 | 我们自己定义了一个类BoothThread, 这个类继承自thread.Threading类。然后我们把上面的booth()所进行的操作统统放入到BoothThread类的run()方法中。注意,我们没有使用全局变量声明global,而是使用了一个词典monitor存放全局变量,然后把词典作为参数传递给线程函数。由于词典是可变数据对象,所以当它被传递给函数的时候,函数所使用的依然是同一个对象,相当于被多个线程所共享。这也是多线程乃至于多进程编程的一个技巧 (应尽量避免上面的global声明的用法,因为它并不适用于windows平台)。 106 | 107 | 上面OOP编程方法与面向过程的编程方法相比,并没有带来太大实质性的差别。 108 | 109 | 110 | 111 | ##其他 112 | 113 | threading.Thread对象: 我们已经介绍了该对象的start()和run(), 此外: 114 | 115 | join()方法,调用该方法的线程将等待直到改Thread对象完成,再恢复运行。这与进程间调用wait()函数相类似。 116 | 117 | 118 | 下面的对象用于处理多线程同步。对象一旦被建立,可以被多个线程共享,并根据情况阻塞某些进程。请与Linux多线程与同步中的同步工具参照阅读。 119 | 120 | threading.Lock对象: mutex, 有acquire()和release()方法。 121 | 122 | threading.Condition对象: condition variable,建立该对象时,会包含一个Lock对象 (因为condition variable总是和mutex一起使用)。可以对Condition对象调用acquire()和release()方法,以控制潜在的Lock对象。此外: 123 | 124 | wait()方法,相当于cond_wait() 125 | notify_all(),相当与cond_broadcast() 126 | nofify(),与notify_all()功能类似,但只唤醒一个等待的线程,而不是全部 127 | threading.Semaphore对象: semaphore,也就是计数锁(semaphore传统意义上是一种进程间同步工具,见Linux进程间通信)。创建对象的时候,可以传递一个整数作为计数上限 (sema = threading.Semaphore(5))。它与Lock类似,也有Lock的两个方法。 128 | threading.Event对象: 与threading.Condition相类似,相当于没有潜在的Lock保护的condition variable。对象有True和False两个状态。可以多个线程使用wait()等待,直到某个线程调用该对象的set()方法,将对象设置为True。线程可以调用对象的clear()方法来重置对象为False状态。 129 | 130 | 131 | 练习 132 | 参照Linux多线程与同步中的condition variable的例子,使用Python实现。同时考虑使用面向过程和面向对象的编程方法。 133 | 更多的threading的内容请参考: 134 | http://docs.python.org/library/threading.html 135 | 136 | 137 | 138 | ##总结 139 | 140 | threading.Thread 141 | 142 | Lock, Condition, Semaphore, Event 143 | 144 | -------------------------------------------------------------------------------- /content/STL09.md: -------------------------------------------------------------------------------- 1 | 2 | ##Python标准库09 当前进程信息 (部分os包) 3 | 4 | 5 | 6 | 7 | 我们在Linux的概念与体系,多次提及进程的重要性。Python的os包中有查询和修改进程信息的函数。学习Python的这些工具也有助于理解Linux体系。 8 | 9 | 10 | 11 | ##进程信息 12 | 13 | os包中相关函数如下: 14 | 15 | uname() 返回操作系统相关信息。类似于Linux上的uname命令。 16 | 17 | umask() 设置该进程创建文件时的权限mask。类似于Linux上的umask命令,见Linux文件管理背景知识 18 | 19 | 20 | 21 | get*() 查询 (*由以下代替) 22 | 23 | uid, euid, resuid, gid, egid, resgid :权限相关,其中resuid主要用来返回saved UID。相关介绍见Linux用户与“最小权限”原则 24 | 25 | pid, pgid, ppid, sid :进程相关。相关介绍见Linux进程关系 26 | 27 | 28 | 29 | put*() 设置 (*由以下代替) 30 | 31 | euid, egid: 用于更改euid,egid。 32 | 33 | uid, gid : 改变进程的uid, gid。只有super user才有权改变进程uid和gid (意味着要以$sudo python的方式运行Python)。 34 | 35 | pgid, sid : 改变进程所在的进程组(process group)和会话(session)。 36 | 37 | 38 | 39 | getenviron():获得进程的环境变量 40 | 41 | setenviron():更改进程的环境变量 42 | 43 | 44 | 45 | 例1,进程的real UID和real GID 46 | ```python 47 | import os 48 | print(os.getuid()) 49 | print(os.getgid()) 50 | ``` 51 | 将上面的程序保存为py_id.py文件,分别用$python py_id.py和$sudo python py_id.py看一下运行结果 52 | 53 | 54 | 55 | ##saved UID和saved GID 56 | 57 | 我们希望saved UID和saved GID如我们在Linux用户与“最小权限”原则中描述的那样工作,但这很难。原因在于,当我们写一个Python脚本后,我们实际运行的是python这个解释器,而不是Python脚本文件。对比C,C语言直接运行由C语言编译成的执行文件。我们必须更改python解释器本身的权限来运用saved UID机制,然而这么做又是异常危险的。 58 | 59 | 比如说,我们的python执行文件为/usr/bin/python (你可以通过$which python获知) 60 | 61 | 我们先看一下 62 | 63 | $ls -l /usr/bin/python 64 | 65 | 的结果: 66 | 67 | -rwxr-xr-x root root 68 | 69 | 70 | 71 | 我们修改权限以设置set UID和set GID位 (参考Linux用户与“最小权限”原则) 72 | 73 | $sudo chmod 6755 /usr/bin/python 74 | 75 | /usr/bin/python的权限成为: 76 | 77 | -rwsr-sr-x root root 78 | 79 | 80 | 81 | 随后,我们运行文件下面test.py文件,这个文件可以是由普通用户vamei所有: 82 | ```python 83 | import os 84 | print(os.getresuid()) 85 | ``` 86 | 我们得到结果: 87 | 88 | (1000, 0, 0) 89 | 90 | 上面分别是UID,EUID,saved UID。我们只用执行一个由普通用户拥有的python脚本,就可以得到super user的权限!所以,这样做是极度危险的,我们相当于交出了系统的保护系统。想像一下Python强大的功能,别人现在可以用这些强大的功能作为攻击你的武器了!使用下面命令来恢复到从前: 91 | 92 | $sudo chmod 0755 /usr/bin/python 93 | 94 | 95 | 96 | 关于脚本文件的saved UID/GID,更加详细的讨论见 97 | 98 | http://www.faqs.org/faqs/unix-faq/faq/part4/section-7.html 99 | 100 | 101 | 102 | ##总结 103 | 104 | get*, set* 105 | 106 | umask(), uname() 107 | -------------------------------------------------------------------------------- /content/STL10.md: -------------------------------------------------------------------------------- 1 | ##Python标准库10 多进程初步 (multiprocessing包) 2 | 3 | 4 | 5 | 6 | 7 | 我们已经见过了使用subprocess包来创建子进程,但这个包有两个很大的局限性:1) 我们总是让subprocess运行外部的程序,而不是运行一个Python脚本内部编写的函数。2) 进程间只通过管道进行文本交流。以上限制了我们将subprocess包应用到更广泛的多进程任务。(这样的比较实际是不公平的,因为subprocessing本身就是设计成为一个shell,而不是一个多进程管理包) 8 | 9 | 10 | 11 | ##threading和multiprocessing 12 | 13 | (请尽量先阅读Python多线程与同步) 14 | 15 | multiprocessing包是Python中的多进程管理包。与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程。该进程可以运行在Python程序内部编写的函数。该Process对象与Thread对象的用法相同,也有start(), run(), join()的方法。此外multiprocessing包中也有Lock/Event/Semaphore/Condition类 (这些对象可以像多线程那样,通过参数传递给各个进程),用以同步进程,其用法与threading包中的同名类一致。所以,multiprocessing的很大一部份与threading使用同一套API,只不过换到了多进程的情境。 16 | 17 | 但在使用这些共享API的时候,我们要注意以下几点: 18 | 19 | 在UNIX平台上,当某个进程终结之后,该进程需要被其父进程调用wait,否则进程成为僵尸进程(Zombie)。所以,有必要对每个Process对象调用join()方法 (实际上等同于wait)。对于多线程来说,由于只有一个进程,所以不存在此必要性。 20 | multiprocessing提供了threading包中没有的IPC(比如Pipe和Queue),效率上更高。应优先考虑Pipe和Queue,避免使用Lock/Event/Semaphore/Condition等同步方式 (因为它们占据的不是用户进程的资源)。 21 | 多进程应该避免共享资源。在多线程中,我们可以比较容易地共享资源,比如使用全局变量或者传递参数。在多进程情况下,由于每个进程有自己独立的内存空间,以上方法并不合适。此时我们可以通过共享内存和Manager的方法来共享资源。但这样做提高了程序的复杂度,并因为同步的需要而降低了程序的效率。 22 | Process.PID中保存有PID,如果进程还没有start(),则PID为None。 23 | 24 | 25 | 26 | 我们可以从下面的程序中看到Thread对象和Process对象在使用上的相似性与结果上的不同。各个线程和进程都做一件事:打印PID。但问题是,所有的任务在打印的时候都会向同一个标准输出(stdout)输出。这样输出的字符会混合在一起,无法阅读。使用Lock同步,在一个任务输出完成之后,再允许另一个任务输出,可以避免多个任务同时向终端输出。 27 | 28 | ```python 29 | # Similarity and difference of multi thread vs. multi process 30 | # Written by Vamei 31 | 32 | import os 33 | import threading 34 | import multiprocessing 35 | 36 | # worker function 37 | def worker(sign, lock): 38 | lock.acquire() 39 | print(sign, os.getpid()) 40 | lock.release() 41 | 42 | # Main 43 | print('Main:',os.getpid()) 44 | 45 | # Multi-thread 46 | record = [] 47 | lock = threading.Lock() 48 | for i in range(5): 49 | thread = threading.Thread(target=worker,args=('thread',lock)) 50 | thread.start() 51 | record.append(thread) 52 | 53 | for thread in record: 54 | thread.join() 55 | 56 | # Multi-process 57 | record = [] 58 | lock = multiprocessing.Lock() 59 | for i in range(5): 60 | process = multiprocessing.Process(target=worker,args=('process',lock)) 61 | process.start() 62 | record.append(process) 63 | 64 | for process in record: 65 | process.join() 66 | ``` 67 | 所有Thread的PID都与主程序相同,而每个Process都有一个不同的PID。 68 | 69 | (练习: 使用mutiprocessing包将Python多线程与同步中的多线程程序更改为多进程程序) 70 | 71 | 72 | 73 | ##Pipe和Queue 74 | 75 | 正如我们在Linux多线程中介绍的管道PIPE和消息队列message queue,multiprocessing包中有Pipe类和Queue类来分别支持这两种IPC机制。Pipe和Queue可以用来传送常见的对象。 76 | 77 | 78 | 79 | 1) Pipe可以是单向(half-duplex),也可以是双向(duplex)。我们通过mutiprocessing.Pipe(duplex=False)创建单向管道 (默认为双向)。一个进程从PIPE一端输入对象,然后被PIPE另一端的进程接收,单向管道只允许管道一端的进程输入,而双向管道则允许从两端输入。 80 | 81 | 下面的程序展示了Pipe的使用: 82 | 83 | 84 | 85 | ```python 86 | # Multiprocessing with Pipe 87 | # Written by Vamei 88 | 89 | import multiprocessing as mul 90 | 91 | def proc1(pipe): 92 | pipe.send('hello') 93 | print('proc1 rec:',pipe.recv()) 94 | 95 | def proc2(pipe): 96 | print('proc2 rec:',pipe.recv()) 97 | pipe.send('hello, too') 98 | 99 | # Build a pipe 100 | pipe = mul.Pipe() 101 | 102 | # Pass an end of the pipe to process 1 103 | p1 = mul.Process(target=proc1, args=(pipe[0],)) 104 | # Pass the other end of the pipe to process 2 105 | p2 = mul.Process(target=proc2, args=(pipe[1],)) 106 | p1.start() 107 | p2.start() 108 | p1.join() 109 | p2.join() 110 | ``` 111 | 这里的Pipe是双向的。 112 | 113 | Pipe对象建立的时候,返回一个含有两个元素的表,每个元素代表Pipe的一端(Connection对象)。我们对Pipe的某一端调用send()方法来传送对象,在另一端使用recv()来接收。 114 | 115 | 116 | 117 | 2) Queue与Pipe相类似,都是先进先出的结构。但Queue允许多个进程放入,多个进程从队列取出对象。Queue使用mutiprocessing.Queue(maxsize)创建,maxsize表示队列中可以存放对象的最大数量。 118 | 下面的程序展示了Queue的使用: 119 | 120 | ```python 121 | # Written by Vamei 122 | import os 123 | import multiprocessing 124 | import time 125 | #================== 126 | # input worker 127 | def inputQ(queue): 128 | info = str(os.getpid()) + '(put):' + str(time.time()) 129 | queue.put(info) 130 | 131 | # output worker 132 | def outputQ(queue,lock): 133 | info = queue.get() 134 | lock.acquire() 135 | print (str(os.getpid()) + '(get):' + info) 136 | lock.release() 137 | #=================== 138 | # Main 139 | record1 = [] # store input processes 140 | record2 = [] # store output processes 141 | lock = multiprocessing.Lock() # To prevent messy print 142 | queue = multiprocessing.Queue(3) 143 | 144 | # input processes 145 | for i in range(10): 146 | process = multiprocessing.Process(target=inputQ,args=(queue,)) 147 | process.start() 148 | record1.append(process) 149 | 150 | # output processes 151 | for i in range(10): 152 | process = multiprocessing.Process(target=outputQ,args=(queue,lock)) 153 | process.start() 154 | record2.append(process) 155 | 156 | for p in record1: 157 | p.join() 158 | 159 | queue.close() # No more object will come, close the queue 160 | 161 | for p in record2: 162 | p.join() 163 | ``` 164 | 一些进程使用put()在Queue中放入字符串,这个字符串中包含PID和时间。另一些进程从Queue中取出,并打印自己的PID以及get()的字符串。 165 | 166 | 167 | 168 | ##总结 169 | 170 | Process, Lock, Event, Semaphore, Condition 171 | 172 | Pipe, Queue 173 | -------------------------------------------------------------------------------- /content/STL11.md: -------------------------------------------------------------------------------- 1 | 2 | ##Python标准库11 多进程探索 (multiprocessing包) 3 | 4 | 5 | 6 | 7 | 在初步了解Python多进程之后,我们可以继续探索multiprocessing包中更加高级的工具。这些工具可以让我们更加便利地实现多进程。 8 | 9 | 10 | 11 | ##进程池 12 | 13 | 进程池 (Process Pool)可以创建多个进程。这些进程就像是随时待命的士兵,准备执行任务(程序)。一个进程池中可以容纳多个待命的士兵。 14 | 15 | 16 | 17 | 18 | 19 | “三个进程的进程池” 20 | 21 | 22 | 23 | 24 | 25 | 比如下面的程序: 26 | 27 | ```python 28 | import multiprocessing as mul 29 | 30 | def f(x): 31 | return x**2 32 | 33 | pool = mul.Pool(5) 34 | rel = pool.map(f,[1,2,3,4,5,6,7,8,9,10]) 35 | print(rel) 36 | ``` 37 | 我们创建了一个容许5个进程的进程池 (Process Pool) 。Pool运行的每个进程都执行f()函数。我们利用map()方法,将f()函数作用到表的每个元素上。这与built-in的map()函数类似,只是这里用5个进程并行处理。如果进程运行结束后,还有需要处理的元素,那么的进程会被用于重新运行f()函数。除了map()方法外,Pool还有下面的常用方法。 38 | 39 | apply_async(func,args) 从进程池中取出一个进程执行func,args为func的参数。它将返回一个AsyncResult的对象,你可以对该对象调用get()方法以获得结果。 40 | 41 | close() 进程池不再创建新的进程 42 | 43 | join() wait进程池中的全部进程。必须对Pool先调用close()方法才能join。 44 | 45 | 46 | 47 | ##练习 48 | 49 | 有下面一个文件download.txt。 50 | 51 | www.sina.com.cn 52 | www.163.com 53 | www.iciba.com 54 | www.cnblogs.com 55 | www.qq.com 56 | www.douban.com 57 | 使用包含3个进程的进程池下载文件中网站的首页。(你可以使用subprocess调用wget或者curl等下载工具执行具体的下载任务) 58 | 59 | 60 | 61 | ##共享资源 62 | 63 | 我们在Python多进程初步已经提到,我们应该尽量避免多进程共享资源。多进程共享资源必然会带来进程间相互竞争。而这种竞争又会造成race condition,我们的结果有可能被竞争的不确定性所影响。但如果需要,我们依然可以通过共享内存和Manager对象这么做。 64 | 65 | 66 | 67 | 68 | 69 | 共享“资源” 70 | 71 | 共享内存 72 | 73 | 在Linux进程间通信中,我们已经讲述了共享内存(shared memory)的原理,这里给出用Python实现的例子: 74 | 75 | ```python 76 | # modified from official documentation 77 | import multiprocessing 78 | 79 | def f(n, a): 80 | n.value = 3.14 81 | a[0] = 5 82 | 83 | num = multiprocessing.Value('d', 0.0) 84 | arr = multiprocessing.Array('i', range(10)) 85 | 86 | p = multiprocessing.Process(target=f, args=(num, arr)) 87 | p.start() 88 | p.join() 89 | 90 | print num.value 91 | print arr[:] 92 | ``` 93 | 这里我们实际上只有主进程和Process对象代表的进程。我们在主进程的内存空间中创建共享的内存,也就是Value和Array两个对象。对象Value被设置成为双精度数(d), 并初始化为0.0。而Array则类似于C中的数组,有固定的类型(i, 也就是整数)。在Process进程中,我们修改了Value和Array对象。回到主程序,打印出结果,主程序也看到了两个对象的改变,说明资源确实在两个进程之间共享。 94 | 95 | 96 | 97 | Manager 98 | 99 | Manager对象类似于服务器与客户之间的通信 (server-client),与我们在Internet上的活动很类似。我们用一个进程作为服务器,建立Manager来真正存放资源。其它的进程可以通过参数传递或者根据地址来访问Manager,建立连接后,操作服务器上的资源。在防火墙允许的情况下,我们完全可以将Manager运用于多计算机,从而模仿了一个真实的网络情境。下面的例子中,我们对Manager的使用类似于shared memory,但可以共享更丰富的对象类型。 100 | 101 | ```python 102 | import multiprocessing 103 | 104 | def f(x, arr, l): 105 | x.value = 3.14 106 | arr[0] = 5 107 | l.append('Hello') 108 | 109 | server = multiprocessing.Manager() 110 | x = server.Value('d', 0.0) 111 | arr = server.Array('i', range(10)) 112 | l = server.list() 113 | 114 | proc = multiprocessing.Process(target=f, args=(x, arr, l)) 115 | proc.start() 116 | proc.join() 117 | 118 | print(x.value) 119 | print(arr) 120 | print(l) 121 | ``` 122 | Manager利用list()方法提供了表的共享方式。实际上你可以利用dict()来共享词典,Lock()来共享threading.Lock(注意,我们共享的是threading.Lock,而不是进程的mutiprocessing.Lock。后者本身已经实现了进程共享)等。 这样Manager就允许我们共享更多样的对象。 123 | 124 | 125 | 126 | 我们在这里不深入讲解Manager在远程情况下的应用。有机会的话,会在网络应用中进一步探索。 127 | 128 | 129 | 130 | ##总结 131 | 132 | Pool 133 | 134 | Shared memory, Manager 135 | 136 | -------------------------------------------------------------------------------- /content/STL12.md: -------------------------------------------------------------------------------- 1 | ##Python标准库12 数学与随机数 (math包,random包) 2 | 3 | 4 | 5 | 6 | 我们已经在Python运算中看到Python最基本的数学运算功能。此外,math包补充了更多的函数。当然,如果想要更加高级的数学功能,可以考虑选择标准库之外的numpy和scipy项目,它们不但支持数组和矩阵运算,还有丰富的数学和物理方程可供使用。 7 | 8 | 此外,random包可以用来生成随机数。随机数不仅可以用于数学用途,还经常被嵌入到算法中,用以提高算法效率,并提高程序的安全性。 9 | 10 | 11 | 12 | ##math包 13 | 14 | math包主要处理数学相关的运算。math包定义了两个常数: 15 | 16 | math.e # 自然常数e 17 | 18 | math.pi # 圆周率pi 19 | 20 | 21 | 22 | 此外,math包还有各种运算函数 (下面函数的功能可以参考数学手册): 23 | 24 | math.ceil(x) # 对x向上取整,比如x=1.2,返回2 25 | 26 | math.floor(x) # 对x向下取整,比如x=1.2,返回1 27 | 28 | math.pow(x,y) # 指数运算,得到x的y次方 29 | 30 | math.log(x) # 对数,默认基底为e。可以使用base参数,来改变对数的基地。比如math.log(100,base=10) 31 | 32 | math.sqrt(x) # 平方根 33 | 34 | 35 | 36 | 三角函数: math.sin(x), math.cos(x), math.tan(x), math.asin(x), math.acos(x), math.atan(x) 37 | 38 | 这些函数都接收一个弧度(radian)为单位的x作为参数。 39 | 40 | 41 | 42 | 角度和弧度互换: math.degrees(x), math.radians(x) 43 | 44 | 45 | 46 | 双曲函数: math.sinh(x), math.cosh(x), math.tanh(x), math.asinh(x), math.acosh(x), math.atanh(x) 47 | 48 | 49 | 50 | 特殊函数: math.erf(x), math.gamma(x) 51 | 52 | 53 | 54 | ##random包 55 | 56 | 如果你已经了解伪随机数(psudo-random number)的原理,那么你可以使用如下: 57 | 58 | random.seed(x) 59 | 60 | 来改变随机数生成器的种子seed。如果你不了解其原理,你不必特别去设定seed,Python会帮你选择seed。 61 | 62 | 63 | 64 | 1) 随机挑选和排序 65 | 66 | random.choice(seq) # 从序列的元素中随机挑选一个元素,比如random.choice(range(10)),从0到9中随机挑选一个整数。 67 | 68 | random.sample(seq,k) # 从序列中随机挑选k个元素 69 | 70 | random.shuffle(seq) # 将序列的所有元素随机排序 71 | 72 | 73 | 74 | 2)随机生成实数 75 | 76 | 下面生成的实数符合均匀分布(uniform distribution),意味着某个范围内的每个数字出现的概率相等: 77 | 78 | random.random() # 随机生成下一个实数,它在[0,1)范围内。 79 | 80 | random.uniform(a,b) # 随机生成下一个实数,它在[a,b]范围内。 81 | 82 | 83 | 84 | 下面生成的实数符合其它的分布 (你可以参考一些统计方面的书籍来了解这些分布): 85 | 86 | random.gauss(mu,sigma) # 随机生成符合高斯分布的随机数,mu,sigma为高斯分布的两个参数。 87 | 88 | random.expovariate(lambd) # 随机生成符合指数分布的随机数,lambd为指数分布的参数。 89 | 90 | 此外还有对数分布,正态分布,Pareto分布,Weibull分布,可参考下面链接: 91 | 92 | http://docs.python.org/library/random.html 93 | 94 | 95 | 96 | 假设我们有一群人参加舞蹈比赛,为了公平起见,我们要随机排列他们的出场顺序。我们下面利用random包实现: 97 | 98 | import random 99 | all_people = ['Tom', 'Vivian', 'Paul', 'Liya', 'Manu', 'Daniel', 'Shawn'] 100 | random.shuffle(all_people) 101 | for i,name in enumerate(all_people): 102 | print(i,':'+name) 103 | 104 | 105 | ##练习 106 | 107 | 设计下面两种彩票号码生成器: 108 | 109 | 1. 从1到22中随机抽取5个整数 (这5个数字不重复) 110 | 111 | 2. 随机产生一个8位数字,每位数字都可以是1到6中的任意一个整数。 112 | 113 | 114 | 115 | ##总结 116 | 117 | math.floor(), math.sqrt(), math.sin(), math.degrees() 118 | 119 | random.random(), random.choice(), random.shuffle() 120 | -------------------------------------------------------------------------------- /content/STL13 循环器 (itertools).md: -------------------------------------------------------------------------------- 1 | Python标准库13 循环器 (itertools) 2 | 3 | 4 | 5 | 6 | 在循环对象和函数对象中,我们了解了循环器(iterator)的功能。循环器是对象的容器,包含有多个对象。通过调用循环器的next()方法 (__next__()方法,在Python 3.x中),循环器将依次返回一个对象。直到所有的对象遍历穷尽,循环器将举出StopIteration错误。 7 | 8 | 9 | 10 | 在for i in iterator结构中,循环器每次返回的对象将赋予给i,直到循环结束。使用iter()内置函数,我们可以将诸如表、字典等容器变为循环器。比如 11 | ```python 12 | for i in iter([2, 4, 5, 6]): 13 | print(i) 14 | ``` 15 | 16 | 标准库中的itertools包提供了更加灵活的生成循环器的工具。这些工具的输入大都是已有的循环器。另一方面,这些工具完全可以自行使用Python实现,该包只是提供了一种比较标准、高效的实现方式。这也符合Python“只有且最好只有解决方案”的理念。 17 | 18 | # import the tools 19 | from itertools import * 20 | 21 | 22 | ##无穷循环器 23 | 24 | count(5, 2) #从5开始的整数循环器,每次增加2,即5, 7, 9, 11, 13, 15 ... 25 | 26 | cycle('abc') #重复序列的元素,既a, b, c, a, b, c ... 27 | 28 | repeat(1.2) #重复1.2,构成无穷循环器,即1.2, 1.2, 1.2, ... 29 | 30 | 31 | 32 | repeat也可以有一个次数限制: 33 | 34 | repeat(10, 5) #重复10,共重复5次 35 | 36 | 37 | 38 | ##函数式工具 39 | 40 | 函数式编程是将函数本身作为处理对象的编程范式。在Python中,函数也是对象,因此可以轻松的进行一些函数式的处理,比如map(), filter(), reduce()函数。 41 | 42 | itertools包含类似的工具。这些函数接收函数作为参数,并将结果返回为一个循环器。 43 | 44 | 45 | 46 | 比如 47 | ```python 48 | from itertools import * 49 | 50 | rlt = imap(pow, [1, 2, 3], [1, 2, 3]) 51 | 52 | for num in rlt: 53 | print(num) 54 | ``` 55 | 上面显示了imap函数。该函数与map()函数功能相似,只不过返回的不是序列,而是一个循环器。包含元素1, 4, 27,即1**1, 2**2, 3**3的结果。函数pow(内置的乘方函数)作为第一个参数。pow()依次作用于后面两个列表的每个元素,并收集函数结果,组成返回的循环器。 56 | 57 | 此外,还可以用下面的函数: 58 | 59 | starmap(pow, [(1, 1), (2, 2), (3, 3)]) 60 | 61 | pow将依次作用于表的每个tuple。 62 | 63 | 64 | 65 | ifilter函数与filter()函数类似,只是返回的是一个循环器。 66 | ```python 67 | ifilter(lambda x: x > 5, [2, 3, 5, 6, 7] 68 | ``` 69 | 将lambda函数依次作用于每个元素,如果函数返回True,则收集原来的元素。6, 7 70 | 71 | 此外, 72 | 73 | ifilterfalse(lambda x: x > 5, [2, 3, 5, 6, 7]) 74 | 75 | 与上面类似,但收集返回False的元素。2, 3, 5 76 | 77 | 78 | 79 | takewhile(lambda x: x < 5, [1, 3, 6, 7, 1]) 80 | 81 | 当函数返回True时,收集元素到循环器。一旦函数返回False,则停止。1, 3 82 | 83 | 84 | 85 | dropwhile(lambda x: x < 5, [1, 3, 6, 7, 1]) 86 | 87 | 当函数返回False时,跳过元素。一旦函数返回True,则开始收集剩下的所有元素到循环器。6, 7, 1 88 | 89 | 90 | 91 | ##组合工具 92 | 93 | 我们可以通过组合原有循环器,来获得新的循环器。 94 | 95 | chain([1, 2, 3], [4, 5, 7]) # 连接两个循环器成为一个。1, 2, 3, 4, 5, 7 96 | 97 | 98 | 99 | product('abc', [1, 2]) # 多个循环器集合的笛卡尔积。相当于嵌套循环 100 | 101 | for m, n in product('abc', [1, 2]): 102 | print m, n 103 | 104 | 105 | 106 | 107 | permutations('abc', 2) # 从'abcd'中挑选两个元素,比如ab, bc, ... 将所有结果排序,返回为新的循环器。 108 | 109 | 注意,上面的组合分顺序,即ab, ba都返回。 110 | 111 | 112 | 113 | combinations('abc', 2) # 从'abcd'中挑选两个元素,比如ab, bc, ... 将所有结果排序,返回为新的循环器。 114 | 115 | 注意,上面的组合不分顺序,即ab, ba的话,只返回一个ab。 116 | 117 | 118 | 119 | combinations_with_replacement('abc', 2) # 与上面类似,但允许两次选出的元素重复。即多了aa, bb, cc 120 | 121 | 122 | 123 | ##groupby() 124 | 125 | 将key函数作用于原循环器的各个元素。根据key函数结果,将拥有相同函数结果的元素分到一个新的循环器。每个新的循环器以函数返回结果为标签。 126 | 127 | 这就好像一群人的身高作为循环器。我们可以使用这样一个key函数: 如果身高大于180,返回"tall";如果身高底于160,返回"short";中间的返回"middle"。最终,所有身高将分为三个循环器,即"tall", "short", "middle"。 128 | 129 | ```python 130 | def height_class(h): 131 | if h > 180: 132 | return "tall" 133 | elif h < 160: 134 | return "short" 135 | else: 136 | return "middle" 137 | 138 | friends = [191, 158, 159, 165, 170, 177, 181, 182, 190] 139 | 140 | friends = sorted(friends, key = height_class) 141 | for m, n in groupby(friends, key = height_class): 142 | print(m) 143 | print(list(n)) 144 | ``` 145 | 注意,groupby的功能类似于UNIX中的uniq命令。分组之前需要使用sorted()对原循环器的元素,根据key函数进行排序,让同组元素先在位置上靠拢。 146 | 147 | 148 | 149 | ##其它工具 150 | 151 | compress('ABCD', [1, 1, 1, 0]) # 根据[1, 1, 1, 0]的真假值情况,选择第一个参数'ABCD'中的元素。A, B, C 152 | 153 | islice() # 类似于slice()函数,只是返回的是一个循环器 154 | 155 | izip() # 类似于zip()函数,只是返回的是一个循环器。 156 | 157 | 158 | 159 | ##总结 160 | 161 | itertools的工具都可以自行实现。itertools只是提供了更加成形的解决方案。 162 | -------------------------------------------------------------------------------- /content/intermediate01.md: -------------------------------------------------------------------------------- 1 | #Python进阶01 词典 2 | 3 | 基础教程介绍了基本概念,特别是对象和类。 4 | 5 | 进阶教程对基础教程的进一步拓展,说明Python的细节。希望在进阶教程之后,你对Python有一个更全面的认识。 6 | 7 | 8 | 9 | 之前我们说了,列表是Python里的一个类。一个特定的表,比如说nl = [1,3,8],就是这个类的一个对象。我们可以调用这个对象的一些方法,比如 nl.append(15)。 10 | 我们要介绍一个新的类,词典 (dictionary)。与列表相似,词典也可以储存多个元素。这种储存多个元素的对象称为容器(container)。 11 | 12 | 13 | 14 | ##基本概念 15 | 16 | 常见的创建词典的方法: 17 | ```python 18 | >>>dic = {'tom':11, 'sam':57,'lily':100} 19 | 20 | >>>print type(dic) 21 | ``` 22 | 词典和表类似的地方,是包含有多个元素,每个元素以逗号分隔。但词典的元素包含有两部分,键和值,常见的是以字符串来表示键,也可以使用数字或者真值来表示键(不可变的对象可以作为键)。值可以是任意对象。键和值两者一一对应。 23 | 24 | 25 | 26 | 比如上面的例子中,'tom'对应11,'sam'对应57,'lily'对应100 27 | 28 | 29 | 30 | 与表不同的是,词典的元素没有顺序。你不能通过下标引用元素。词典是通过键来引用。 31 | ```python 32 | >>>print dic['tom'] 33 | 34 | >>>dic['tom'] = 30 35 | 36 | >>>print dic 37 | ``` 38 | 39 | 40 | 构建一个新的空的词典: 41 | ```python 42 | >>>dic = {} 43 | 44 | >>>print dic 45 | ``` 46 | 47 | 48 | 在词典中增添一个新元素的方法: 49 | ```python 50 | >>>dic['lilei'] = 99 51 | 52 | >>>print dic 53 | ``` 54 | 这里,我们引用一个新的键,并赋予它对应的值。 55 | 56 | 57 | 58 | ##词典元素的循环调用 59 | ```python 60 | dic = {'lilei': 90, 'lily': 100, 'sam': 57, 'tom': 90} 61 | for key in dic: 62 | print dic[key] 63 | ``` 64 | 65 | 在循环中,dict的每个键,被提取出来,赋予给key变量。 66 | 67 | 通过print的结果,我们可以再次确认,dic中的元素是没有顺序的。 68 | 69 | 70 | 词典的遍历也可以同时取出键和值: 71 | ```python 72 | for key,value in dict_od.items(): 73 | print key , value , '\n' 74 | ``` 75 | 76 | ##词典的常用方法 77 | ```python 78 | >>>print dic.keys() # 返回dic所有的键 79 | 80 | >>>print dic.values() # 返回dic所有的值 81 | 82 | >>>print dic.items() # 返回dic所有的元素(键值对) 83 | 84 | >>>dic.clear() # 清空dic,dict变为{} 85 | ``` 86 | 87 | 88 | 另外有一个很常用的用法: 89 | ```python 90 | >>>del dic['tom'] # 删除 dic 的‘tom’元素 91 | ``` 92 | del是Python中保留的关键字,用于删除对象。 93 | 94 | 95 | 96 | 与表类似,你可以用len()查询词典中的元素总数。 97 | ```python 98 | >>>print(len(dic)) 99 | ``` 100 | 101 | ## 词典排序 102 | 前面我们已经谈到,词典是无序的,那么,如果需要词典内容有序,就需要使用排序方法: 103 | 104 | 词典按key排序,排序后的结果存放到列表中:(这里用到了列表推导式) 105 | 106 | ```python 107 | listRes = sorted([(k, v) for k, v in dict_rate.items()], reverse=True) 108 | ``` 109 | 110 | 词典按value排序,排序后的结果存放到列表中: 111 | ```python 112 | listRes = sorted([(v, k) for k, v in dict_rate.items()], reverse=True 113 | ``` 114 | 115 | ##总结 116 | 117 | 词典的每个元素是键值对。元素没有顺序。 118 | ```python 119 | dic = {'tom':11, 'sam':57,'lily':100} 120 | 121 | dic['tom'] = 99 122 | 123 | for key in dic: ... 124 | 125 | del, len() 126 | ``` 127 | -------------------------------------------------------------------------------- /content/intermediate02.md: -------------------------------------------------------------------------------- 1 | #Python进阶02 文本文件的输入输出 2 | 3 | 4 | 5 | 6 | 7 | Python具有基本的文本文件读写功能。Python的标准库提供有更丰富的读写功能。 8 | 9 | 文本文件的读写主要通过open()所构建的文件对象来实现。 10 | 11 | 12 | 13 | ##创建文件对象 14 | 15 | 我们打开一个文件,并使用一个对象来表示该文件: 16 | ```python 17 | f = open(文件名,模式) 18 | ``` 19 | 20 | 21 | 最常用的模式有: 22 | ```python 23 | "r" # 只读 24 | 25 | "w" # 写入 26 | 27 | "a" # 追加 28 | ``` 29 | 30 | 31 | 32 | 比如 33 | ```python 34 | >>>f = open("test.txt","r") 35 | ``` 36 | 37 | 38 | ##文件对象的方法 39 | 40 | 读取: 41 | ```python 42 | content = f.read(N) # 读取N bytes的数据 43 | 44 | content = f.readline() # 读取一行 45 | 46 | content = f.readlines() # 读取所有行,储存在列表中,每个元素是一行。 47 | ``` 48 | 49 | Pythonic: 50 | 读取文件更简洁的写法: 51 | ```python 52 | datafile = open('datafile',"r") 53 | for line in datafile: 54 | do_something(line) 55 | ``` 56 | 57 | 写入: 58 | ```python 59 | f.write('I like apple') # 将'I like apple'写入文件 60 | ``` 61 | 62 | 63 | 关闭文件: 64 | ```python 65 | f.close() 66 | ``` 67 | 68 | ##总结 69 | ```python 70 | f = open(name, "r") 71 | 72 | line = f.readline() 73 | 74 | f.write('abc') 75 | 76 | f.close() 77 | ``` 78 | -------------------------------------------------------------------------------- /content/intermediate03.md: -------------------------------------------------------------------------------- 1 | #Python进阶03 模块 2 | 3 | 4 | 5 | 6 | 7 | 我们之前看到了函数和对象。从本质上来说,它们都是为了更好的组织已经有的程序,以方便重复利用。 8 | 9 | 模块(module)也是为了同样的目的。在Python中,一个.py文件就构成一个模块。通过模块,你可以调用其它文件中的程序。 10 | 11 | 12 | 13 | ##引入模块 14 | 15 | 我们先写一个first.py文件,内容如下: 16 | ```python 17 | def laugh(): 18 | print 'HaHaHaHa' 19 | ``` 20 | 21 | 再写一个second.py,并引入first中的程序: 22 | ```python 23 | import first 24 | 25 | for i in range(10): 26 | first.laugh() 27 | ``` 28 | 在second.py中,我们使用了first.py中定义的laugh()函数。 29 | 30 | 31 | 32 | 引入模块后,可以通过模块.对象的方式来调用引入模块中的某个对象。上面例子中,first为引入的模块,laugh()是我们所引入的对象。 33 | 34 | Python中还有其它的引入方式, 35 | ```python 36 | import a as b # 引入模块a,并将模块a重命名为b 37 | 38 | from a import function1 # 从模块a中引入function1对象。调用a中对象时,我们不用再说明模块,即直接使用function1,而不是a.function1。 39 | 40 | from a import * # 从模块a中引入所有对象。调用a中对象时,我们不用再说明模块,即直接使用对象,而不是a.对象。 41 | ``` 42 | 这些引用方式,可以方便后面的程序书写。 43 | 44 | 45 | 46 | ##搜索路径 47 | 48 | Python会在以下路径中搜索它想要寻找的模块: 49 | 50 | 程序所在的文件夹 51 | 标准库的安装路径 52 | 操作系统环境变量PYTHONPATH所包含的路径 53 | 54 | 55 | 如果你有自定义的模块,或者下载的模块,可以根据情况放在相应的路径,以便Python可以找到。 56 | 57 | 58 | 59 | 模块包 60 | 61 | 可以将功能相似的模块放在同一个文件夹(比如说this_dir)中,构成一个模块包。通过 62 | ```python 63 | import this_dir.module 64 | ``` 65 | 引入this_dir文件夹中的module模块。 66 | 67 | 68 | 69 | 该文件夹中必须包含一个__init__.py的文件,提醒Python,该文件夹为一个模块包。__init__.py可以是一个空文件。 70 | 71 | 72 | 73 | ##总结 74 | ``` 75 | import module 76 | 77 | module.object 78 | 79 | __init__.py 80 | ``` 81 | -------------------------------------------------------------------------------- /content/intermediate04.md: -------------------------------------------------------------------------------- 1 | #Python进阶04 函数的参数对应 2 | 3 | 4 | 5 | 我们已经接触过函数(function)的参数(arguments)传递。当时我们根据位置,传递对应的参数。我们将接触更多的参数传递方式。 6 | 7 | 8 | 9 | 回忆一下位置传递: 10 | ```python 11 | def f(a,b,c): 12 | return a+b+c 13 | 14 | print(f(1,2,3)) 15 | ``` 16 | 在调用f时,1,2,3根据位置分别传递给了a,b,c。 17 | 18 | 19 | 20 | ##关键字传递 21 | 22 | 有些情况下,用位置传递会感觉比较死板。关键字(keyword)传递是根据每个参数的名字传递参数。关键字并不用遵守位置的对应关系。依然沿用上面f的定义,更改调用方式: 23 | ```python 24 | print(f(c=3,b=2,a=1)) 25 | ``` 26 | 27 | 关键字传递可以和位置传递混用。但位置参数要出现在关键字参数之前: 28 | ```python 29 | print(f(1,c=3,b=2)) 30 | ``` 31 | 32 | ##参数默认值 33 | 34 | 在定义函数的时候,使用形如a=19的方式,可以给参数赋予默认值(default)。如果该参数最终没有被传递值,将使用该默认值。 35 | ```python 36 | def f(a,b,c=10): 37 | return a+b+c 38 | 39 | print(f(3,2)) 40 | print(f(3,2,1)) 41 | ``` 42 | 在第一次调用函数f时, 我们并没有足够的值,c没有被赋值,c将使用默认值10. 43 | 44 | 第二次调用函数的时候,c被赋值为1,不再使用默认值。 45 | 46 | 47 | 48 | ##包裹传递 49 | 50 | 在定义函数时,我们有时候并不知道调用的时候会传递多少个参数。这时候,包裹(packing)位置参数,或者包裹关键字参数,来进行参数传递,会非常有用。 51 | 52 | 53 | 54 | 下面是包裹位置传递的例子: 55 | ```python 56 | def func(*name): 57 | print type(name) 58 | print name 59 | 60 | func(1,4,6) 61 | func(5,6,7,1,2,3) 62 | ``` 63 | 两次调用,尽管参数个数不同,都基于同一个func定义。在func的参数表中,所有的参数被name收集,根据位置合并成一个元组(tuple),这就是包裹位置传递。 64 | 65 | 为了提醒Python参数,name是包裹位置传递所用的元组名,在定义func时,在name前加*号。 66 | 67 | 68 | 69 | 下面是包裹关键字传递的例子: 70 | ``python 71 | def func(**dict): 72 | print type(dict) 73 | print dict 74 | 75 | func(a=1,b=9) 76 | func(m=2,n=1,c=11) 77 | ``` 78 | 与上面一个例子类似,dict是一个字典,收集所有的关键字,传递给函数func。为了提醒Python,参数dict是包裹关键字传递所用的字典,在dict前加**。 79 | 80 | 包裹传递的关键在于定义函数时,在相应元组或字典前加*或**。 81 | 82 | 83 | 84 | ##解包裹 85 | 86 | *和**,也可以在调用的时候使用,即解包裹(unpacking), 下面为例: 87 | ```python 88 | def func(a,b,c): 89 | print a,b,c 90 | 91 | args = (1,3,4) 92 | func(*args) 93 | ``` 94 | 在这个例子中,所谓的解包裹,就是在传递tuple时,让tuple的每一个元素对应一个位置参数。在调用func时使用*,是为了提醒Python:我想要把args拆成分散的三个元素,分别传递给a,b,c。(设想一下在调用func时,args前面没有*会是什么后果?) 95 | 96 | 97 | 98 | 相应的,也存在对词典的解包裹,使用相同的func定义,然后: 99 | ```python 100 | dict = {'a':1,'b':2,'c':3} 101 | func(**dict) 102 | ``` 103 | 在传递词典dict时,让词典的每个键值对作为一个关键字传递给func。 104 | 105 | 106 | 107 | ##混合 108 | 109 | 在定义或者调用参数时,参数的几种传递方式可以混合。但在过程中要小心前后顺序。基本原则是,先位置,再关键字,再包裹位置,再包裹关键字,并且根据上面所说的原理细细分辨。 110 | 111 | 112 | 113 | 注意:请注意定义时和调用时的区分。包裹和解包裹并不是相反操作,是两个相对独立的过程。 114 | 115 | 116 | 117 | ##总结 118 | 119 | 关键字,默认值, 120 | 121 | 包裹位置,包裹关键字 122 | 123 | 解包裹 124 | -------------------------------------------------------------------------------- /content/intermediate05.md: -------------------------------------------------------------------------------- 1 | #Python进阶05 循环设计 2 | 3 | 4 | 5 | 6 | 在“循环”一节,我们已经讨论了Python基本的循环语法。这一节,我们将接触更加灵活的循环方式。 7 | 8 | 9 | 10 | ##range() 11 | 12 | 在Python中,for循环后的in跟随一个序列的话,循环每次使用的序列元素,而不是序列的下标。 13 | 14 | 之前我们已经使用过range()来控制for循环。现在,我们继续开发range的功能,以实现下标对循环的控制: 15 | ```python 16 | S = 'abcdefghijk' 17 | for i in range(0,len(S),2): 18 | print S[i] 19 | ``` 20 | 在该例子中,我们利用len()函数和range()函数,用i作为S序列的下标来控制循环。在range函数中,分别定义上限,下限和每次循环的步长。这就和C语言中的for循环相类似了。 21 | 22 | 23 | 24 | ##enumerate() 25 | 26 | 利用enumerate()函数,可以在每次循环中同时得到下标和元素: 27 | ```python 28 | S = 'abcdefghijk' 29 | for (index,char) in enumerate(S): 30 | print index 31 | print char 32 | ``` 33 | 实际上,enumerate()在每次循环中,返回的是一个包含两个元素的定值表(tuple),两个元素分别赋予index和char 34 | 35 | 增加下标的访问设计 36 | 在python 3.2中,支持enumerate带第二个参数,用来指定循环的开始位置,比如,从第一个下标位置开始循环: 37 | ```python 38 | array = [1, 2, 3, 4, 5] 39 | 40 | for i, e in enumerate(array,1): 41 | print i, e 42 | 43 | ``` 44 | 45 | 46 | ##zip() 47 | 48 | 如果你多个等长的序列,然后想要每次循环时从各个序列分别取出一个元素,可以利用zip()方便地实现: 49 | ```python 50 | ta = [1,2,3] 51 | tb = [9,8,7] 52 | tc = ['a','b','c'] 53 | for (a,b,c) in zip(ta,tb,tc): 54 | print(a,b,c) 55 | ``` 56 | 每次循环时,从各个序列分别从左到右取出一个元素,合并成一个tuple,然后tuple的元素赋予给a,b,c 57 | 58 | zip()函数的功能,就是从多个列表中,依次各取出一个元素。每次取出的(来自不同列表的)元素合成一个元组,合并成的元组放入zip()返回的列表中。zip()函数起到了聚合列表的功能。 59 | 60 | 61 | 62 | 我们可以分解聚合后的列表,如下: 63 | ```python 64 | ta = [1,2,3] 65 | tb = [9,8,7] 66 | 67 | # cluster 68 | zipped = zip(ta,tb) 69 | print(zipped) 70 | 71 | # decompose 72 | na, nb = zip(*zipped) 73 | print(na, nb) 74 | ``` 75 | 76 | 77 | ##总结 78 | 79 | range() 80 | 81 | enumerate() 82 | 83 | zip() 84 | -------------------------------------------------------------------------------- /content/intermediate06.md: -------------------------------------------------------------------------------- 1 | #Python进阶06 循环对象 2 | 3 | 4 | 5 | 6 | 7 | 这一讲的主要目的是为了大家在读Python程序的时候对循环对象有一个基本概念。 8 | 9 | 循环对象的并不是随着Python的诞生就存在的,但它的发展迅速,特别是Python 3x的时代,循环对象正在成为循环的标准形式。 10 | 11 | 12 | 13 | ##什么是循环对象 14 | 15 | 循环对象是这样一个对象,它包含有一个next()方法(__next__()方法,在python 3x中), 这个方法的目的是进行到下一个结果,而在结束一系列结果之后,举出StopIteration错误。 16 | 17 | 当一个循环结构(比如for)调用循环对象时,它就会每次循环的时候调用next()方法,直到StopIteration出现,for循环接收到,就知道循环已经结束,停止调用next()。 18 | 19 | 假设我们有一个test.txt的文件: 20 | ```bash 21 | 1234 22 | abcd 23 | efg 24 | ``` 25 | 我们运行一下python命令行: 26 | ```python 27 | >>>f = open('test.txt') 28 | 29 | >>>f.next() 30 | 31 | >>>f.next() 32 | 33 | ... 34 | ``` 35 | 36 | 不断输入f.next(),直到最后出现StopIteration 37 | 38 | open()返回的实际上是一个循环对象,包含有next()方法。而该next()方法每次返回的就是新的一行的内容,到达文件结尾时举出StopIteration。这样,我们相当于手工进行了循环。 39 | 40 | 自动进行的话,就是: 41 | ```python 42 | for line in open('test.txt'): 43 | print line 44 | ``` 45 | 在这里,for结构自动调用next()方法,将该方法的返回值赋予给line。循环知道出现StopIteration的时候结束。 46 | 47 | 48 | 49 | 相对于序列,用循环对象的好处在于:不用在循环还没有开始的时候,就生成好要使用的元素。所使用的元素可以在循环过程中逐次生成。这样,节省了空间,提高了效率,编程更灵活。 50 | 51 | 52 | 53 | ##迭代器 54 | 55 | 从技术上来说,循环对象和for循环调用之间还有一个中间层,就是要将循环对象转换成迭代器(iterator)。这一转换是通过使用iter()函数实现的。但从逻辑层面上,常常可以忽略这一层,所以循环对象和迭代器常常相互指代对方。 56 | 57 | 58 | 59 | ##生成器 60 | 61 | 生成器(generator)的主要目的是构成一个用户自定义的循环对象。 62 | 63 | 生成器的编写方法和函数定义类似,只是在return的地方改为yield。生成器中可以有多个yield。当生成器遇到一个yield时,会暂停运行生成器,返回yield后面的值。当再次调用生成器的时候,会从刚才暂停的地方继续运行,直到下一个yield。生成器自身又构成一个循环器,每次循环使用一个yield返回的值。 64 | 65 | 66 | 67 | 下面是一个生成器: 68 | ```python 69 | def gen(): 70 | a = 100 71 | yield a 72 | a = a*8 73 | yield a 74 | yield 1000 75 | ``` 76 | 该生成器共有三个yield, 如果用作循环器时,会进行三次循环。 77 | ```python 78 | for i in gen(): 79 | print i 80 | ``` 81 | 82 | 再考虑如下一个生成器: 83 | 84 | def gen(): 85 | for i in range(4): 86 | yield i 87 | 它又可以写成生成器表达式(Generator Expression): 88 | 89 | G = (x for x in range(4)) 90 | 生成器表达式是生成器的一种简便的编写方式。读者可进一步查阅。 91 | 92 | 93 | 94 | ##表推导 95 | 96 | 表推导(list comprehension)是快速生成表的方法。它的语法简单,很有实用价值。 97 | 98 | 99 | 100 | 假设我们生成表L: 101 | ```python 102 | L = [] 103 | for x in range(10): 104 | L.append(x**2) 105 | ``` 106 | 以上产生了表L,但实际上有快捷的写法,也就是表推导的方式: 107 | ```python 108 | L = [x**2 for x in range(10)] 109 | ``` 110 | 这与生成器表达式类似,只不过用的是中括号。 111 | 112 | (表推导的机制实际上是利用循环对象,有兴趣可以查阅。) 113 | 114 | 115 | 116 | 练习 下面的表推导会生成什么? 117 | ```python 118 | xl = [1,3,5] 119 | yl = [9,12,13] 120 | L = [ x**2 for (x,y) in zip(xl,yl) if y > 10] 121 | ``` 122 | 123 | ##总结 124 | 125 | 循环对象 126 | 127 | 生成器 128 | 129 | 表推导 130 | 131 | 132 | -------------------------------------------------------------------------------- /content/intermediate07.md: -------------------------------------------------------------------------------- 1 | #Python进阶07 函数对象 2 | 3 | 4 | 5 | 6 | 7 | 秉承着一切皆对象的理念,我们再次回头来看函数(function)。函数也是一个对象,具有属性(可以使用dir()查询)。作为对象,它还可以赋值给其它对象名,或者作为参数传递。 8 | 9 | 10 | 11 | ##lambda函数 12 | 13 | 在展开之前,我们先提一下lambda函数。可以利用lambda函数的语法,定义函数。lambda例子如下: 14 | ```python 15 | func = lambda x,y: x + y 16 | print func(3,4) 17 | ``` 18 | lambda生成一个函数对象。该函数参数为x,y,返回值为x+y。函数对象赋给func。func的调用与正常函数无异。 19 | 20 | 21 | 22 | 以上定义可以写成以下形式: 23 | ```python 24 | def func(x, y): 25 | return x + y 26 | ``` 27 | 28 | ##函数作为参数传递 29 | 30 | 函数可以作为一个对象,进行参数传递。函数名(比如func)即该对象。比如说: 31 | ```python 32 | def test(f, a, b): 33 | print 'test' 34 | print f(a, b) 35 | 36 | test(func, 3, 5) 37 | ``` 38 | test函数的第一个参数f就是一个函数对象。将func传递给f,test中的f()就拥有了func()的功能。 39 | 40 | 41 | 42 | 我们因此可以提高程序的灵活性。可以使用上面的test函数,带入不同的函数参数。比如: 43 | ```python 44 | test((lambda x,y: x**2 + y), 6, 9) 45 | ``` 46 | 47 | ##map()函数 48 | 49 | map()是Python的内置函数。它的第一个参数是一个函数对象。 50 | ```python 51 | re = map((lambda x: x+3),[1,3,5,6]) 52 | ``` 53 | 这里,map()有两个参数,一个是lambda所定义的函数对象,一个是包含有多个元素的表。map()的功能是将函数对象依次作用于表的每一个元素,每次作用的结果储存于返回的表re中。map通过读入的函数(这里是lambda函数)来操作数据(这里“数据”是表中的每一个元素,“操作”是对每个数据加3)。 54 | 55 | 在Python 3.X中,map()的返回值是一个循环对象。可以利用list()函数,将该循环对象转换成表。 56 | 57 | 58 | 59 | 如果作为参数的函数对象有多个参数,可使用下面的方式,向map()传递函数参数的多个参数: 60 | ```python 61 | re = map((lambda x,y: x+y),[1,2,3],[6,7,9]) 62 | ``` 63 | map()将每次从两个表中分别取出一个元素,带入lambda所定义的函数。 64 | 65 | 66 | 67 | ##filter()函数 68 | 69 | filter函数的第一个参数也是一个函数对象。它也是将作为参数的函数对象作用于多个元素。如果函数对象返回的是True,则该次的元素被储存于返回的表中。filter通过读入的函数来筛选数据。同样,在Python 3.X中,filter返回的不是表,而是循环对象。 70 | 71 | 72 | 73 | filter函数的使用如下例: 74 | 75 | ```python 76 | def func(a): 77 | if a > 100: 78 | return True 79 | else: 80 | return False 81 | 82 | print filter(func,[10,56,101,500]) 83 | ``` 84 | 85 | 86 | ##reduce()函数 87 | 88 | reduce函数的第一个参数也是函数,但有一个要求,就是这个函数自身能接收两个参数。reduce可以累进地将函数作用于各个参数。如下例: 89 | ```python 90 | print reduce((lambda x,y: x+y),[1,2,5,7,9]) 91 | ``` 92 | reduce的第一个参数是lambda函数,它接收两个参数x,y, 返回x+y。 93 | 94 | reduce将表中的前两个元素(1和2)传递给lambda函数,得到3。该返回值(3)将作为lambda函数的第一个参数,而表中的下一个元素(5)作为lambda函数的第二个参数,进行下一次的对lambda函数的调用,得到8。依次调用lambda函数,每次lambda函数的第一个参数是上一次运算结果,而第二个参数为表中的下一个元素,直到表中没有剩余元素。 95 | 96 | 上面例子,相当于(((1+2)+5)+7)+9 97 | 98 | 99 | 100 | 根据mmufhy的提醒: reduce()函数在3.0里面不能直接用的,它被定义在了functools包里面,需要引入包,见评论区。 101 | 102 | 103 | 104 | ##总结 105 | 106 | 函数是一个对象 107 | 108 | 用lambda定义函数 109 | 110 | map() 111 | 112 | filter() 113 | 114 | reduce() 115 | -------------------------------------------------------------------------------- /content/intermediate08.md: -------------------------------------------------------------------------------- 1 | #Python进阶08 异常处理 2 | 3 | 4 | 5 | 6 | ##异常处理 7 | 8 | 在项目开发中,异常处理是不可或缺的。异常处理帮助人们debug,通过更加丰富的信息,让人们更容易找到bug的所在。异常处理还可以提高程序的容错性。 9 | 10 | 我们之前在讲循环对象的时候,曾提到一个StopIteration的异常,该异常是在循环对象穷尽所有元素时的报错。 11 | 12 | 我们以它为例,来说明基本的异常处理。 13 | 14 | 一个包含异常的程序: 15 | ```python 16 | re = iter(range(5)) 17 | 18 | for i in range(100): 19 | print re.next() 20 | 21 | print 'HaHaHaHa' 22 | ``` 23 | 首先,我们定义了一个循环对象re,该循环对象将进行5次循环,每次使用序列的一个元素。 24 | 25 | 在随后的for循环中,我们手工调用next()函数。当循环进行到第6次的时候,re.next()不会再返回元素,而是抛出(raise)StopIteration的异常。整个程序将会中断。 26 | 27 | 28 | 29 | 我们可以修改以上异常程序,直到完美的没有bug。但另一方面,如果我们在写程序的时候,知道这里可能犯错以及可能的犯错类型,我们可以针对该异常类型定义好”应急预案“。 30 | ```python 31 | re = iter(range(5)) 32 | 33 | try: 34 | for i in range(100): 35 | print re.next() 36 | except StopIteration: 37 | print 'here is end ',i 38 | 39 | print 'HaHaHaHa' 40 | ``` 41 | 在try程序段中,我们放入容易犯错的部分。我们可以跟上except,来说明如果在try部分的语句发生StopIteration时,程序该做的事情。如果没有发生异常,则except部分被跳过。 42 | 43 | 随后,程序将继续运行,而不是彻底中断。 44 | 45 | 46 | 47 | 完整的语法结构如下: 48 | 49 | ```python 50 | try: 51 | ... 52 | except exception1: 53 | ... 54 | except exception2: 55 | ... 56 | except: 57 | ... 58 | else: 59 | ... 60 | finally: 61 | ... 62 | ``` 63 | 64 | 65 | 如果try中有异常发生时,将执行异常的归属,执行except。异常层层比较,看是否是exception1, exception2...,直到找到其归属,执行相应的except中的语句。如果except后面没有任何参数,那么表示所有的exception都交给这段程序处理。比如: 66 | ```python 67 | try: 68 | print(a*2) 69 | except TypeError: 70 | print("TypeError") 71 | except: 72 | print("Not Type Error & Error noted") 73 | ``` 74 | 由于a没有定义,所以是NameError。异常最终被except:部分的程序捕捉。 75 | 76 | 77 | 78 | 如果无法将异常交给合适的对象,异常将继续向上层抛出,直到被捕捉或者造成主程序报错。比如下面的程序 79 | 80 | ```python 81 | def test_func(): 82 | try: 83 | m = 1/0 84 | except NameError: 85 | print("Catch NameError in the sub-function") 86 | 87 | try: 88 | test_func() 89 | except ZeroDivisionError: 90 | print("Catch error in the main program") 91 | ``` 92 | 子程序的try...except...结构无法处理相应的除以0的错误,所以错误被抛给上层的主程序。 93 | 94 | 95 | 96 | 如果try中没有异常,那么except部分将跳过,执行else中的语句。 97 | 98 | finally是无论是否有异常,最后都要做的一些事情。 99 | 100 | 流程如下, 101 | 102 | try->异常->except->finally 103 | 104 | try->无异常->else->finally 105 | 106 | 107 | 108 | ##抛出异常 109 | 110 | 我们也可以自己写一个抛出异常的例子: 111 | ```python 112 | print 'Lalala' 113 | raise StopIteration 114 | print 'Hahaha' 115 | ``` 116 | 这个例子不具备任何实际意义。只是为了说明raise语句的作用。 117 | 118 | StopIteration是一个类。抛出异常时,会自动有一个中间环节,就是生成StopIteration的一个对象。Python实际上抛出的,是这个对象。当然,也可以自行声场对象: 119 | ```python 120 | raise StopIteration() 121 | ``` 122 | 123 | ##总结 124 | ```python 125 | try: ... except exception: ... else: ... finally: ... 126 | raise exception 127 | ``` 128 | -------------------------------------------------------------------------------- /content/intermediate09.md: -------------------------------------------------------------------------------- 1 | #Python进阶09 动态类型 2 | 3 | 4 | 5 | 6 | 动态类型(dynamic typing)是Python另一个重要的核心概念。我们之前说过,Python的变量(variable)不需要声明,而在赋值时,变量可以重新赋值为任意值。这些都与动态类型的概念相关。 7 | 8 | 9 | 10 | ##动态类型 11 | 12 | 在我们接触的对象中,有一类特殊的对象,是用于存储数据的。常见的该类对象包括各种数字,字符串,表,词典。在C语言中,我们称这样一些数据结构为变量。而在Python中,这些是对象。 13 | 14 | 对象是储存在内存中的实体。但我们并不能直接接触到该对象。我们在程序中写的对象名,只是指向这一对象的引用(reference)。 15 | 16 | 17 | 18 | 引用和对象分离,是动态类型的核心。引用可以随时指向一个新的对象: 19 | ```python 20 | a = 3 21 | a = 'at' 22 | ``` 23 | 第一个语句中,3是储存在内存中的一个整数对象。通过赋值,引用a指向对象3。 24 | 25 | 第二个语句中,内存中建立对象‘at’,是一个字符串(string)。引用a指向了'at'。此时,对象3不再有引用指向它。Python会自动将没有引用指向的对象销毁(destruct),释放相应内存。 26 | 27 | (对于小的整数和短字符串,Python会缓存这些对象,而不是频繁的建立和销毁。) 28 | 29 | 30 | ```python 31 | a = 5 32 | b = a 33 | a = a + 2 34 | ``` 35 | 再看这个例子。通过前两个句子,我们让a,b指向同一个整数对象5(b = a的含义是让引用b指向引用a所指的那一个对象)。但第三个句子实际上对引用a重新赋值,让a指向一个新的对象7。此时a,b分别指向不同的对象。我们看到,即使是多个引用指向同一个对象,如果一个引用值发生变化,那么实际上是让这个引用指向一个新的引用,并不影响其他的引用的指向。从效果上看,就是各个引用各自独立,互不影响。 36 | 37 | 38 | 39 | 其它数据对象也是如此: 40 | ```python 41 | L1 = [1,2,3] 42 | L2 = L1 43 | L1 = 1 44 | ``` 45 | 46 | 但注意以下情况 47 | ```python 48 | L1 = [1,2,3] 49 | L2 = L1 50 | L1[0] = 10 51 | print L2 52 | ``` 53 | 在该情况下,我们不再对L1这一引用赋值,而是对L1所指向的表的元素赋值。结果是,L2也同时发生变化。 54 | 55 | 原因何在呢?因为L1,L2的指向没有发生变化,依然指向那个表。表实际上是包含了多个引用的对象(每个引用是一个元素,比如L1[0],L1[1]..., 每个引用指向一个对象,比如1,2,3), 。而L1[0] = 10这一赋值操作,并不是改变L1的指向,而是对L1[0], 也就是表对象的一部份(一个元素),进行操作,所以所有指向该对象的引用都受到影响。 56 | 57 | (与之形成对比的是,我们之前的赋值操作都没有对对象自身发生作用,只是改变引用指向。) 58 | 59 | 60 | 61 | 列表可以通过引用其元素,改变对象自身(in-place change)。这种对象类型,称为可变数据对象(mutable object),词典也是这样的数据类型。 62 | 63 | 而像之前的数字和字符串,不能改变对象本身,只能改变引用的指向,称为不可变数据对象(immutable object)。 64 | 65 | 我们之前学的元组(tuple),尽管可以调用引用元素,但不可以赋值,因此不能改变对象自身,所以也算是immutable object. 66 | 67 | 68 | 69 | ##从动态类型看函数的参数传递 70 | 71 | 函数的参数传递,本质上传递的是引用。比如说: 72 | 73 | ```python 74 | def f(x): 75 | x = 100 76 | print x 77 | 78 | a = 1 79 | f(a) 80 | print a 81 | ``` 82 | 83 | 参数x是一个新的引用,指向a所指的对象。如果参数是不可变(immutable)的对象,a和x引用之间相互独立。对参数x的操作不会影响引用a。这样的传递类似于C语言中的值传递。 84 | 85 | 86 | 87 | 如果传递的是可变(mutable)的对象,那么改变函数参数,有可能改变原对象。所有指向原对象的引用都会受影响,编程的时候要对此问题留心。比如说: 88 | 89 | ```python 90 | def f(x): 91 | x[0] = 100 92 | print x 93 | 94 | a = [1,2,3] 95 | f(a) 96 | print a 97 | ``` 98 | 99 | 100 | 动态类型是Python的核心机制之一。可以在应用中慢慢熟悉。 101 | 102 | 103 | 104 | ##总结 105 | 106 | 引用和对象的分离,对象是内存中储存数据的实体,引用指向对象。 107 | 108 | 可变对象,不可变对象 109 | 110 | 函数值传递 111 | -------------------------------------------------------------------------------- /content/深入01 特殊方法与多范式.md: -------------------------------------------------------------------------------- 1 | ##Python深入01 特殊方法与多范式 2 | 3 | 4 | 5 | 6 | Python一切皆对象,但同时,Python还是一个多范式语言(multi-paradigm),你不仅可以使用面向对象的方式来编写程序,还可以用面向过程的方式来编写相同功能的程序(还有函数式、声明式等,我们暂不深入)。Python的多范式依赖于Python对象中的特殊方法(special method)。 7 | 8 | 特殊方法名的前后各有两个下划线。特殊方法又被成为魔法方法(magic method),定义了许多Python语法和表达方式,正如我们在下面的例子中将要看到的。当对象中定义了特殊方法的时候,Python也会对它们有“特殊优待”。比如定义了__init__()方法的类,会在创建对象的时候自动执行__init__()方法中的操作。 9 | 10 | (可以通过dir()来查看对象所拥有的特殊方法,比如dir(1)) 11 | 12 | 13 | 14 | ##运算符 15 | 16 | Python的运算符是通过调用对象的特殊方法实现的。比如: 17 | ```python 18 | 'abc' + 'xyz' # 连接字符串 19 | ``` 20 | 实际执行了如下操作: 21 | ```python 22 | 'abc'.__add__('xyz') 23 | ``` 24 | 所以,在Python中,两个对象是否能进行加法运算,首先就要看相应的对象是否有__add__()方法。一旦相应的对象有__add__()方法,即使这个对象从数学上不可加,我们都可以用加法的形式,来表达obj.__add__()所定义的操作。在Python中,运算符起到简化书写的功能,但它依靠特殊方法实现。 25 | 26 | Python不强制用户使用面向对象的编程方法。用户可以选择自己喜欢的使用方式(比如选择使用+符号,还是使用更加面向对象的__add__()方法)。特殊方法写起来总是要更费事一点。 27 | 28 | 尝试下面的操作,看看效果,再想想它的对应运算符 29 | 30 | (1.8).__mul__(2.0) 31 | 32 | True.__or__(False) 33 | 34 | 35 | 36 | ##内置函数 37 | 38 | 与运算符类似,许多内置函数也都是调用对象的特殊方法。比如 39 | ```python 40 | len([1,2,3]) # 返回表中元素的总数 41 | ``` 42 | 实际上做的是 43 | ```python 44 | [1,2,3].__len__() 45 | ``` 46 | 相对与__len__(),内置函数len()也起到了简化书写的作用。 47 | 48 | 尝试下面的操作,想一下它的对应内置函数 49 | 50 | (-1).__abs__() 51 | 52 | (2.3).__int__() 53 | 54 | 55 | 56 | ##表(list)元素引用 57 | 58 | 下面是我们常见的表元素引用方式 59 | ```python 60 | li = [1, 2, 3, 4, 5, 6] 61 | print(li[3]) 62 | ``` 63 | 上面的程序运行到li[3]的时候,Python发现并理解[]符号,然后调用__getitem__()方法。 64 | ```python 65 | li = [1, 2, 3, 4, 5, 6] 66 | print(li.__getitem__(3)) 67 | ``` 68 | 尝试看下面的操作,想想它的对应 69 | 70 | li.__setitem__(3, 0) 71 | 72 | {'a':1, 'b':2}.__delitem__('a') 73 | 74 | 75 | ##函数 76 | 77 | 我们已经说过,在Python中,函数也是一种对象。实际上,任何一个有__call__()特殊方法的对象都被当作是函数。比如下面的例子: 78 | 79 | ```python 80 | class SampleMore(object): 81 | def __call__(self, a): 82 | return a + 5 83 | 84 | add = SampleMore() # A function object 85 | print(add(2)) # Call function 86 | map(add, [2, 4, 5]) # Pass around function object 87 | ``` 88 | 89 | add为SampleMore类的一个对象,当被调用时,add执行加5的操作。add还可以作为函数对象,被传递给map()函数。 90 | 91 | 当然,我们还可以使用更“优美”的方式,想想是什么。 92 | 93 | 94 | 95 | ##总结 96 | 97 | 对于内置的对象来说(比如整数、表、字符串等),它们所需要的特殊方法都已经在Python中准备好了。而用户自己定义的对象也可以通过增加特殊方法,来实现自定义的语法。特殊方法比较靠近Python的底层,许多Python功能的实现都要依赖于特殊方法。我们将在以后看到更多的例子。 98 | 99 | 100 | 101 | 102 | 103 | 大黄蜂,还是Camaro跑车 104 | 105 | Python的许多语法都是基于其面向对象模型的封装。对象模型是Python的骨架,是功能完备、火力强大的大黄蜂。但是Python也提供更加简洁的语法,让你使用不同的编程形态,从而在必要时隐藏一些面向对象的接口。正如我们看到的Camaro跑车,将自己威风的火药库收起来,提供方便人类使用的车门和座椅。 106 | -------------------------------------------------------------------------------- /content/深入02 上下文管理器.md: -------------------------------------------------------------------------------- 1 | #Python深入02 上下文管理器 2 | 3 | 4 | 上下文管理器(context manager)是Python2.5开始支持的一种语法,用于规定某个对象的使用范围。一旦进入或者离开该使用范围,会有特殊操作被调用 (比如为对象分配或者释放内存)。它的语法形式是with...as... 5 | 6 | 7 | 8 | ##关闭文件 9 | 10 | 我们会进行这样的操作:打开文件,读写,关闭文件。程序员经常会忘记关闭文件。上下文管理器可以在不需要文件的时候,自动关闭文件。 11 | 12 | 下面我们看一下两段程序: 13 | ```python 14 | # without context manager 15 | f = open("new.txt", "w") 16 | print(f.closed) # whether the file is open 17 | f.write("Hello World!") 18 | f.close() 19 | print(f.closed) 20 | ``` 21 | 以及: 22 | ```python 23 | # with context manager 24 | with open("new.txt", "w") as f: 25 | print(f.closed) 26 | f.write("Hello World!") 27 | print(f.closed) 28 | ``` 29 | 两段程序实际上执行的是相同的操作。我们的第二段程序就使用了上下文管理器 (with...as...)。上下文管理器有隶属于它的程序块。当隶属的程序块执行结束的时候(也就是不再缩进),上下文管理器自动关闭了文件 (我们通过f.closed来查询文件是否关闭)。我们相当于使用缩进规定了文件对象f的使用范围。 30 | 31 | 32 | 33 | 上面的上下文管理器基于f对象的__exit__()特殊方法(还记得我们如何利用特殊方法来实现各种语法?参看特殊方法与多范式)。当我们使用上下文管理器的语法时,我们实际上要求Python在进入程序块之前调用对象的__enter__()方法,在结束程序块的时候调用__exit__()方法。对于文件对象f来说,它定义了__enter__()和__exit__()方法(可以通过dir(f)看到)。在f的__exit__()方法中,有self.close()语句。所以在使用上下文管理器时,我们就不用明文关闭f文件了。 34 | 35 | 36 | 37 | ##自定义 38 | 39 | 任何定义了__enter__()和__exit__()方法的对象都可以用于上下文管理器。文件对象f是内置对象,所以f自动带有这两个特殊方法,不需要自定义。 40 | 41 | 下面,我们自定义用于上下文管理器的对象,就是下面的myvow: 42 | 43 | 44 | 45 | ```python 46 | # customized object 47 | 48 | class VOW(object): 49 | def __init__(self, text): 50 | self.text = text 51 | def __enter__(self): 52 | self.text = "I say: " + self.text # add prefix 53 | return self # note: return an object 54 | def __exit__(self,exc_type,exc_value,traceback): 55 | self.text = self.text + "!" # add suffix 56 | 57 | 58 | with VOW("I'm fine") as myvow: 59 | print(myvow.text) 60 | 61 | print(myvow.text) 62 | ``` 63 | 64 | 65 | 我们的运行结果如下: 66 | 67 | I say: I'm fine 68 | I say: I'm fine! 69 | 70 | 我们可以看到,在进入上下文和离开上下文时,对象的text属性发生了改变(最初的text属性是"I'm fine")。 71 | 72 | __enter__()返回一个对象。上下文管理器会使用这一对象作为as所指的变量,也就是myvow。在__enter__()中,我们为myvow.text增加了前缀 ("I say: ")。在__exit__()中,我们为myvow.text增加了后缀("!")。 73 | 74 | 注意: __exit__()中有四个参数。当程序块中出现异常(exception),__exit__()的参数中exc_type, exc_value, traceback用于描述异常。我们可以根据这三个参数进行相应的处理。如果正常运行结束,这三个参数都是None。在我们的程序中,我们并没有用到这一特性。 75 | 76 | 77 | 78 | ##总结: 79 | 80 | 通过上下文管理器,我们控制对象在程序不同区间的特性。上下文管理器(with EXPR as VAR)大致相当于如下流程: 81 | 82 | ```python 83 | # with EXPR as VAR: 84 | 85 | VAR = EXPR 86 | VAR = VAR.__enter__() 87 | try: 88 | BLOCK 89 | finally: 90 | VAR.__exit__() 91 | ``` 92 | 由于上下文管理器带来的便利,它是一个值得使用的工具。 93 | -------------------------------------------------------------------------------- /content/深入04 闭包.md: -------------------------------------------------------------------------------- 1 | #Python深入04 闭包 2 | 3 | 4 | 5 | 6 | 闭包(closure)是函数式编程的重要的语法结构。函数式编程是一种编程范式 (而面向过程编程和面向对象编程也都是编程范式)。在面向过程编程中,我们见到过函数(function);在面向对象编程中,我们见过对象(object)。函数和对象的根本目的是以某种逻辑方式组织代码,并提高代码的可重复使用性(reusability)。闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。 7 | 8 | 不同的语言实现闭包的方式不同。Python以函数对象为基础,为闭包这一语法结构提供支持的 (我们在特殊方法与多范式中,已经多次看到Python使用对象来实现一些特殊的语法)。Python一切皆对象,函数这一语法结构也是一个对象。在函数对象中,我们像使用一个普通对象一样使用函数对象,比如更改函数对象的名字,或者将函数对象作为参数进行传递。 9 | 10 | 11 | 12 | ##函数对象的作用域 13 | 14 | 和其他对象一样,函数对象也有其存活的范围,也就是函数对象的作用域。函数对象是使用def语句定义的,函数对象的作用域与def所在的层级相同。比如下面代码,我们在line_conf函数的隶属范围内定义的函数line,就只能在line_conf的隶属范围内调用。 15 | 16 | ```python 17 | def line_conf(): 18 | def line(x): 19 | return 2*x+1 20 | print(line(5)) # within the scope 21 | 22 | 23 | line_conf() 24 | print(line(5)) # out of the scope 25 | ``` 26 | line函数定义了一条直线(y = 2x + 1)。可以看到,在line_conf()中可以调用line函数,而在作用域之外调用line将会有下面的错误: 27 | 28 | NameError: name 'line' is not defined 29 | 30 | 说明这时已经在作用域之外。 31 | 32 | 33 | 34 | 同样,如果使用lambda定义函数,那么函数对象的作用域与lambda所在的层级相同。 35 | 36 | 37 | 38 | ##闭包 39 | 40 | 函数是一个对象,所以可以作为某个函数的返回结果。 41 | 42 | ```pyhton 43 | def line_conf(): 44 | def line(x): 45 | return 2*x+1 46 | return line # return a function object 47 | 48 | my_line = line_conf() 49 | print(my_line(5)) 50 | ``` 51 | 上面的代码可以成功运行。line_conf的返回结果被赋给line对象。上面的代码将打印11。 52 | 53 | 54 | 55 | 如果line()的定义中引用了外部的变量,会发生什么呢? 56 | 57 | ```python 58 | def line_conf(): 59 | b = 15 60 | def line(x): 61 | return 2*x+b 62 | return line # return a function object 63 | 64 | b = 5 65 | my_line = line_conf() 66 | print(my_line(5)) 67 | ``` 68 | 我们可以看到,line定义的隶属程序块中引用了高层级的变量b,但b信息存在于line的定义之外 (b的定义并不在line的隶属程序块中)。我们称b为line的环境变量。事实上,line作为line_conf的返回值时,line中已经包括b的取值(尽管b并不隶属于line)。 69 | 70 | 上面的代码将打印25,也就是说,line所参照的b值是函数对象定义时可供参考的b值,而不是使用时的b值。 71 | 72 | 73 | 74 | 一个函数和它的环境变量合在一起,就构成了一个闭包(closure)。在Python中,所谓的闭包是一个包含有环境变量取值的函数对象。环境变量取值被保存在函数对象的__closure__属性中。比如下面的代码: 75 | 76 | ```python 77 | def line_conf(): 78 | b = 15 79 | def line(x): 80 | return 2*x+b 81 | return line # return a function object 82 | 83 | b = 5 84 | my_line = line_conf() 85 | print(my_line.__closure__) 86 | print(my_line.__closure__[0].cell_contents) 87 | ``` 88 | __closure__里包含了一个元组(tuple)。这个元组中的每个元素是cell类型的对象。我们看到第一个cell包含的就是整数15,也就是我们创建闭包时的环境变量b的取值。 89 | 90 | 91 | 92 | 下面看一个闭包的实际例子: 93 | 94 | ```python 95 | def line_conf(a, b): 96 | def line(x): 97 | return ax + b 98 | return line 99 | 100 | line1 = line_conf(1, 1) 101 | line2 = line_conf(4, 5) 102 | print(line1(5), line2(5)) 103 | ``` 104 | 这个例子中,函数line与环境变量a,b构成闭包。在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个环境变量的取值,这样,我们就确定了函数的最终形式(y = x + 1和y = 4x + 5)。我们只需要变换参数a,b,就可以获得不同的直线表达函数。由此,我们可以看到,闭包也具有提高代码可复用性的作用。 105 | 106 | 如果没有闭包,我们需要每次创建直线函数的时候同时说明a,b,x。这样,我们就需要更多的参数传递,也减少了代码的可移植性。利用闭包,我们实际上创建了泛函。line函数定义一种广泛意义的函数。这个函数的一些方面已经确定(必须是直线),但另一些方面(比如a和b参数待定)。随后,我们根据line_conf传递来的参数,通过闭包的形式,将最终函数确定下来。 107 | 108 | 109 | 110 | ##闭包与并行运算 111 | 112 | 闭包有效的减少了函数所需定义的参数数目。这对于并行运算来说有重要的意义。在并行运算的环境下,我们可以让每台电脑负责一个函数,然后将一台电脑的输出和下一台电脑的输入串联起来。最终,我们像流水线一样工作,从串联的电脑集群一端输入数据,从另一端输出数据。这样的情境最适合只有一个参数输入的函数。闭包就可以实现这一目的。 113 | 114 | 并行运算正称为一个热点。这也是函数式编程又热起来的一个重要原因。函数式编程早在1950年代就已经存在,但应用并不广泛。然而,我们上面描述的流水线式的工作并行集群过程,正适合函数式编程。由于函数式编程这一天然优势,越来越多的语言也开始加入对函数式编程范式的支持。 115 | -------------------------------------------------------------------------------- /content/深入05 装饰器.md: -------------------------------------------------------------------------------- 1 | #Python深入05 装饰器 2 | 3 | 4 | 5 | 6 | 装饰器(decorator)是一种高级Python语法。装饰器可以对一个函数、方法或者类进行加工。在Python中,我们有多种方法对函数和类进行加工,比如在Python闭包中,我们见到函数对象作为某一个函数的返回结果。相对于其它方式,装饰器语法简单,代码可读性高。因此,装饰器在Python项目中有广泛的应用。 7 | 8 | 装饰器最早在Python 2.5中出现,它最初被用于加工函数和方法这样的可调用对象(callable object,这样的对象定义有__call__方法)。在Python 2.6以及之后的Python版本中,装饰器被进一步用于加工类。 9 | 10 | 11 | 12 | ##装饰函数和方法 13 | 14 | 我们先定义两个简单的数学函数,一个用来计算平方和,一个用来计算平方差: 15 | 16 | ```python 17 | # get square sum 18 | def square_sum(a, b): 19 | return a**2 + b**2 20 | 21 | # get square diff 22 | def square_diff(a, b): 23 | return a**2 - b**2 24 | 25 | print(square_sum(3, 4)) 26 | print(square_diff(3, 4)) 27 | ``` 28 | 在拥有了基本的数学功能之后,我们可能想为函数增加其它的功能,比如打印输入。我们可以改写函数来实现这一点: 29 | 30 | ```python 31 | # modify: print input 32 | 33 | # get square sum 34 | def square_sum(a, b): 35 | print("intput:", a, b) 36 | return a**2 + b**2 37 | 38 | # get square diff 39 | def square_diff(a, b): 40 | print("input", a, b) 41 | return a**2 - b**2 42 | 43 | print(square_sum(3, 4)) 44 | print(square_diff(3, 4)) 45 | ``` 46 | 我们修改了函数的定义,为函数增加了功能。 47 | 48 | 49 | 50 | 现在,我们使用装饰器来实现上述修改: 51 | 52 | ```python 53 | def decorator(F): 54 | def new_F(a, b): 55 | print("input", a, b) 56 | return F(a, b) 57 | return new_F 58 | 59 | # get square sum 60 | @decorator 61 | def square_sum(a, b): 62 | return a**2 + b**2 63 | 64 | # get square diff 65 | @decorator 66 | def square_diff(a, b): 67 | return a**2 - b**2 68 | 69 | print(square_sum(3, 4)) 70 | print(square_diff(3, 4)) 71 | ``` 72 | 装饰器可以用def的形式定义,如上面代码中的decorator。装饰器接收一个可调用对象作为输入参数,并返回一个新的可调用对象。装饰器新建了一个可调用对象,也就是上面的new_F。new_F中,我们增加了打印的功能,并通过调用F(a, b)来实现原有函数的功能。 73 | 74 | 定义好装饰器后,我们就可以通过@语法使用了。在函数square_sum和square_diff定义之前调用@decorator,我们实际上将square_sum或square_diff传递给decorator,并将decorator返回的新的可调用对象赋给原来的函数名(square_sum或square_diff)。 所以,当我们调用square_sum(3, 4)的时候,就相当于: 75 | ```python 76 | square_sum = decorator(square_sum) 77 | square_sum(3, 4) 78 | ``` 79 | 我们知道,Python中的变量名和对象是分离的。变量名可以指向任意一个对象。从本质上,装饰器起到的就是这样一个重新指向变量名的作用(name binding),让同一个变量名指向一个新返回的可调用对象,从而达到修改可调用对象的目的。 80 | 81 | 与加工函数类似,我们可以使用装饰器加工类的方法。 82 | 83 | 84 | 85 | 如果我们有其他的类似函数,我们可以继续调用decorator来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。 86 | 87 | 88 | 89 | ##含参的装饰器 90 | 91 | 在上面的装饰器调用中,比如@decorator,该装饰器默认它后面的函数是唯一的参数。装饰器的语法允许我们调用decorator时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。 92 | 93 | ```python 94 | # a new wrapper layer 95 | def pre_str(pre=''): 96 | # old decorator 97 | def decorator(F): 98 | def new_F(a, b): 99 | print(pre + "input", a, b) 100 | return F(a, b) 101 | return new_F 102 | return decorator 103 | 104 | # get square sum 105 | @pre_str('^_^') 106 | def square_sum(a, b): 107 | return a**2 + b**2 108 | 109 | # get square diff 110 | @pre_str('T_T') 111 | def square_diff(a, b): 112 | return a**2 - b**2 113 | 114 | print(square_sum(3, 4)) 115 | print(square_diff(3, 4)) 116 | ``` 117 | 上面的pre_str是允许参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有环境参量的闭包。当我们使用@pre_str('^_^')调用的时候,Python能够发现这一层的封装,并把参数传递到装饰器的环境中。该调用相当于: 118 | 119 | square_sum = pre_str('^_^') (square_sum) 120 | 121 | 122 | ##装饰类 123 | 124 | 在上面的例子中,装饰器接收一个函数,并返回一个函数,从而起到加工函数的效果。在Python 2.6以后,装饰器被拓展到类。一个装饰器可以接收一个类,并返回一个类,从而起到加工类的效果。 125 | 126 | ```python 127 | def decorator(aClass): 128 | class newClass: 129 | def __init__(self, age): 130 | self.total_display = 0 131 | self.wrapped = aClass(age) 132 | def display(self): 133 | self.total_display += 1 134 | print("total display", self.total_display) 135 | self.wrapped.display() 136 | return newClass 137 | 138 | @decorator 139 | class Bird: 140 | def __init__(self, age): 141 | self.age = age 142 | def display(self): 143 | print("My age is",self.age) 144 | 145 | eagleLord = Bird(5) 146 | for i in range(3): 147 | eagleLord.display() 148 | ``` 149 | 在decorator中,我们返回了一个新类newClass。在新类中,我们记录了原来类生成的对象(self.wrapped),并附加了新的属性total_display,用于记录调用display的次数。我们也同时更改了display方法。 150 | 151 | 通过修改,我们的Bird类可以显示调用display的次数了。 152 | 153 | 154 | 155 | ##总结 156 | 157 | 装饰器的核心作用是name binding。这种语法是Python多编程范式的又一个体现。大部分Python用户都不怎么需要定义装饰器,但有可能会使用装饰器。鉴于装饰器在Python项目中的广泛使用,了解这一语法是非常有益的。 158 | 159 | 160 | -------------------------------------------------------------------------------- /content/深入06 Python的内存管理.md: -------------------------------------------------------------------------------- 1 | #Python深入06 Python的内存管理 2 | 3 | 4 | 5 | 6 | 语言的内存管理是语言设计的一个重要方面。它是决定语言性能的重要因素。无论是C语言的手工管理,还是Java的垃圾回收,都成为语言最重要的特征。这里以Python语言为例子,说明一门动态类型的、面向对象的语言的内存管理方式。 7 | 8 | 9 | 10 | ##对象的内存使用 11 | 12 | 赋值语句是语言最常见的功能了。但即使是最简单的赋值语句,也可以很有内涵。Python的赋值语句就很值得研究。 13 | 14 | a = 1 15 | 整数1为一个对象。而a是一个引用。利用赋值语句,引用a指向对象1。Python是动态类型的语言(参考动态类型),对象与引用分离。Python像使用“筷子”那样,通过引用来接触和翻动真正的食物——对象。 16 | 17 | 18 | 19 | 为了探索对象在内存的存储,我们可以求助于Python的内置函数id()。它用于返回对象的身份(identity)。其实,这里所谓的身份,就是该对象的内存地址。 20 | ```pyhton 21 | a = 1 22 | 23 | print(id(a)) 24 | print(hex(id(a))) 25 | ``` 26 | 在我的计算机上,它们返回的是: 27 | 28 | 11246696 29 | '0xab9c68' 30 | 31 | 分别为内存地址的十进制和十六进制表示。 32 | 33 | 34 | 35 | 在Python中,整数和短小的字符,Python都会缓存这些对象,以便重复使用。当我们创建多个等于1的引用时,实际上是让所有这些引用指向同一个对象。 36 | ```pyhton 37 | a = 1 38 | b = 1 39 | 40 | print(id(a)) 41 | print(id(b)) 42 | ``` 43 | 上面程序返回 44 | 45 | 11246696 46 | 47 | 11246696 48 | 49 | 可见a和b实际上是指向同一个对象的两个引用。 50 | 51 | 52 | 53 | 为了检验两个引用指向同一个对象,我们可以用is关键字。is用于判断两个引用所指的对象是否相同。 54 | 55 | ```pyhton 56 | # True 57 | a = 1 58 | b = 1 59 | print(a is b) 60 | 61 | # True 62 | a = "good" 63 | b = "good" 64 | print(a is b) 65 | 66 | # False 67 | a = "very good morning" 68 | b = "very good morning" 69 | print(a is b) 70 | 71 | # False 72 | a = [] 73 | b = [] 74 | print(a is b) 75 | ``` 76 | 上面的注释为相应的运行结果。可以看到,由于Python缓存了整数和短字符串,因此每个对象只存有一份。比如,所有整数1的引用都指向同一对象。即使使用赋值语句,也只是创造了新的引用,而不是对象本身。长的字符串和其它对象可以有多个相同的对象,可以使用赋值语句创建出新的对象。 77 | 78 | 79 | 80 | 在Python中,每个对象都有存有指向该对象的引用总数,即引用计数(reference count)。 81 | 82 | 我们可以使用sys包中的getrefcount(),来查看某个对象的引用计数。需要注意的是,当使用某个引用作为参数,传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,getrefcount()所得到的结果,会比期望的多1。 83 | ```pyhton 84 | from sys import getrefcount 85 | 86 | a = [1, 2, 3] 87 | print(getrefcount(a)) 88 | 89 | b = a 90 | print(getrefcount(b)) 91 | ``` 92 | 由于上述原因,两个getrefcount将返回2和3,而不是期望的1和2。 93 | 94 | 95 | 96 | ##对象引用对象 97 | 98 | Python的一个容器对象(container),比如表、词典等,可以包含多个对象。实际上,容器对象中包含的并不是元素对象本身,是指向各个元素对象的引用。 99 | 100 | 我们也可以自定义一个对象,并引用其它对象: 101 | 102 | ```pyhton 103 | class from_obj(object): 104 | def __init__(self, to_obj): 105 | self.to_obj = to_obj 106 | 107 | b = [1,2,3] 108 | a = from_obj(b) 109 | print(id(a.to_obj)) 110 | print(id(b)) 111 | ``` 112 | 可以看到,a引用了对象b。 113 | 114 | 115 | 116 | 对象引用对象,是Python最基本的构成方式。即使是a = 1这一赋值方式,实际上是让词典的一个键值"a"的元素引用整数对象1。该词典对象用于记录所有的全局引用。该词典引用了整数对象1。我们可以通过内置函数globals()来查看该词典。 117 | 118 | 119 | 120 | 当一个对象A被另一个对象B引用时,A的引用计数将增加1。 121 | 122 | ```pyhton 123 | from sys import getrefcount 124 | 125 | a = [1, 2, 3] 126 | print(getrefcount(a)) 127 | 128 | b = [a, a] 129 | print(getrefcount(a)) 130 | ``` 131 | 由于对象b引用了两次a,a的引用计数增加了2。 132 | 133 | 134 | 135 | 容器对象的引用可能构成很复杂的拓扑结构。我们可以用objgraph包来绘制其引用关系,比如 136 | ```pyhton 137 | x = [1, 2, 3] 138 | y = [x, dict(key1=x)] 139 | z = [y, (x, y)] 140 | 141 | import objgraph 142 | objgraph.show_refs([z], filename='ref_topo.png') 143 | ``` 144 | 145 | 146 | 147 | 148 | 149 | objgraph是Python的一个第三方包。安装之前需要安装xdot。 150 | ```pyhton 151 | sudo apt-get install xdot 152 | sudo pip install objgraph 153 | objgraph官网 154 | ``` 155 | 156 | 157 | 两个对象可能相互引用,从而构成所谓的引用环(reference cycle)。 158 | ```pyhton 159 | a = [] 160 | b = [a] 161 | a.append(b) 162 | ``` 163 | 即使是一个对象,只需要自己引用自己,也能构成引用环。 164 | ```pyhton 165 | a = [] 166 | a.append(a) 167 | print(getrefcount(a)) 168 | ``` 169 | 引用环会给垃圾回收机制带来很大的麻烦,我将在后面详细叙述这一点。 170 | 171 | 172 | 173 | ##引用减少 174 | 175 | 某个对象的引用计数可能减少。比如,可以使用del关键字删除某个引用: 176 | 177 | ```pyhton 178 | from sys import getrefcount 179 | 180 | a = [1, 2, 3] 181 | b = a 182 | print(getrefcount(b)) 183 | 184 | del a 185 | print(getrefcount(b)) 186 | ``` 187 | 188 | 189 | del也可以用于删除容器元素中的元素,比如: 190 | ```pyhton 191 | a = [1,2,3] 192 | del a[0] 193 | print(a) 194 | ``` 195 | 196 | 197 | 198 | 如果某个引用指向对象A,当这个引用被重新定向到某个其他对象B时,对象A的引用计数减少: 199 | 200 | ```pyhton 201 | from sys import getrefcount 202 | 203 | a = [1, 2, 3] 204 | b = a 205 | print(getrefcount(b)) 206 | 207 | a = 1 208 | print(getrefcount(b)) 209 | ``` 210 | 211 | 212 | 213 | 214 | ##垃圾回收 215 | 216 | 吃太多,总会变胖,Python也是这样。当Python中的对象越来越多,它们将占据越来越大的内存。不过你不用太担心Python的体形,它会乖巧的在适当的时候“减肥”,启动垃圾回收(garbage collection),将没用的对象清除。在许多语言中都有垃圾回收机制,比如Java和Ruby。尽管最终目的都是塑造苗条的提醒,但不同语言的减肥方案有很大的差异 (这一点可以对比本文和Java内存管理与垃圾回收 217 | 218 | )。 219 | 220 | 221 | 222 | 223 | 224 | 从基本原理上,当Python的某个对象的引用计数降为0时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾了。比如某个新建对象,它被分配给某个引用,对象的引用计数变为1。如果引用被删除,对象的引用计数为0,那么该对象就可以被垃圾回收。比如下面的表: 225 | ```pyhton 226 | a = [1, 2, 3] 227 | del a 228 | ``` 229 | del a后,已经没有任何引用指向之前建立的[1, 2, 3]这个表。用户不可能通过任何方式接触或者动用这个对象。这个对象如果继续待在内存里,就成了不健康的脂肪。当垃圾回收启动时,Python扫描到这个引用计数为0的对象,就将它所占据的内存清空。 230 | 231 | 232 | 233 | 然而,减肥是个昂贵而费力的事情。垃圾回收时,Python不能进行其它的任务。频繁的垃圾回收将大大降低Python的工作效率。如果内存中的对象不多,就没有必要总启动垃圾回收。所以,Python只会在特定条件下,自动启动垃圾回收。当Python运行时,会记录其中分配对象(object allocation)和取消分配对象(object deallocation)的次数。当两者的差值高于某个阈值时,垃圾回收才会启动。 234 | 235 | 我们可以通过gc模块的get_threshold()方法,查看该阈值: 236 | 237 | import gc 238 | print(gc.get_threshold()) 239 | 返回(700, 10, 10),后面的两个10是与分代回收相关的阈值,后面可以看到。700即是垃圾回收启动的阈值。可以通过gc中的set_threshold()方法重新设置。 240 | 241 | 242 | 243 | 我们也可以手动启动垃圾回收,即使用gc.collect()。 244 | 245 | 246 | 247 | ##分代回收 248 | 249 | Python同时采用了分代(generation)回收的策略。这一策略的基本假设是,存活时间越久的对象,越不可能在后面的程序中变成垃圾。我们的程序往往会产生大量的对象,许多对象很快产生和消失,但也有一些对象长期被使用。出于信任和效率,对于这样一些“长寿”对象,我们相信它们的用处,所以减少在垃圾回收中扫描它们的频率。 250 | 251 | 252 | 253 | 254 | 255 | 小家伙要多检查 256 | 257 | 258 | 259 | Python将所有的对象分为0,1,2三代。所有的新建对象都是0代对象。当某一代对象经历过垃圾回收,依然存活,那么它就被归入下一代对象。垃圾回收启动时,一定会扫描所有的0代对象。如果0代经过一定次数垃圾回收,那么就启动对0代和1代的扫描清理。当1代也经历了一定次数的垃圾回收后,那么会启动对0,1,2,即对所有对象进行扫描。 260 | 261 | 这两个次数即上面get_threshold()返回的(700, 10, 10)返回的两个10。也就是说,每10次0代垃圾回收,会配合1次1代的垃圾回收;而每10次1代的垃圾回收,才会有1次的2代垃圾回收。 262 | 263 | 同样可以用set_threshold()来调整,比如对2代对象进行更频繁的扫描。 264 | 265 | import gc 266 | gc.set_threshold(700, 10, 5) 267 | 268 | 269 | ##孤立的引用环 270 | 271 | 引用环的存在会给上面的垃圾回收机制带来很大的困难。这些引用环可能构成无法使用,但引用计数不为0的一些对象。 272 | ```pyhton 273 | a = [] 274 | b = [a] 275 | a.append(b) 276 | 277 | del a 278 | del b 279 | ``` 280 | 上面我们先创建了两个表对象,并引用对方,构成一个引用环。删除了a,b引用之后,这两个对象不可能再从程序中调用,就没有什么用处了。但是由于引用环的存在,这两个对象的引用计数都没有降到0,不会被垃圾回收。 281 | 282 | 283 | 284 | 孤立的引用环 285 | 286 | 287 | 288 | 为了回收这样的引用环,Python复制每个对象的引用计数,可以记为gc_ref。假设,每个对象i,该计数为gc_ref_i。Python会遍历所有的对象i。对于每个对象i引用的对象j,将相应的gc_ref_j减1。 289 | 290 | 291 | 292 | 293 | 294 | 遍历后的结果 295 | 296 | 297 | 298 | 在结束遍历后,gc_ref不为0的对象,和这些对象引用的对象,以及继续更下游引用的对象,需要被保留。而其它的对象则被垃圾回收。 299 | 300 | 301 | 302 | ##总结 303 | 304 | Python作为一种动态类型的语言,其对象和引用分离。这与曾经的面向过程语言有很大的区别。为了有效的释放内存,Python内置了垃圾回收的支持。Python采取了一种相对简单的垃圾回收机制,即引用计数,并因此需要解决孤立引用环的问题。Python与其它语言既有共通性,又有特别的地方。对该内存管理机制的理解,是提高Python性能的重要一步。 305 | 306 | 307 | 308 | 欢迎继续阅读“Python快速教程” 309 | -------------------------------------------------------------------------------- /content/网络01 原始Python服务器.md: -------------------------------------------------------------------------------- 1 | #Python网络01 原始Python服务器 2 | 3 | 4 | 5 | 6 | 之前我的Python教程中有人留言,表示只学Python没有用,必须学会一个框架(比如Django和web.py)才能找到工作。而我的想法是,掌握一个类似于框架的高级工具是有用的,但是基础的东西可以让你永远不被淘汰。不要被工具限制了自己的发展。今天,我在这里想要展示的,就是不使用框架,甚至不使用Python标准库中的高级包,只使用标准库中的socket接口(我不是很明白套接字这个翻译,所以使用socket的英文名字),写一个Python服务器。 7 | 8 | 9 | 10 | 在当今Python服务器框架 (framework, 比如Django, Twisted, web.py等等) 横行的时代,从底层的socket开始写服务器似乎是一个出力不讨好的笨方法。框架的意义在于掩盖底层的细节,提供一套对于开发人员更加友好的API,并处理诸如MVC的布局问题。框架允许我们快速的构建一个成型而且成熟的Python服务器。然而,框架本身也是依赖于底层(比如socket)。对于底层socket的了解,不仅可以帮助我们更好的使用框架,更可以让我们明白框架是如何设计的。更进一步,如果拥有良好的底层socket编程知识和其他系统编程知识,你完全可以设计并开发一款自己的框架。如果你可以从底层socket开始,实现一个完整的Python服务器,支持用户层的协议,并处理好诸如MVC(Model-View-Control)、多线程(threading)等问题,并整理出一套清晰的函数或者类,作为接口(API)呈现给用户,你就相当于设计了一个框架。 11 | 12 | 13 | 14 | socket接口是实际上是操作系统提供的系统调用。socket的使用并不局限于Python语言,你可以用C或者JAVA来写出同样的socket服务器,而所有语言使用socket的方式都类似(Apache就是使用C实现的服务器)。而你不能跨语言的使用框架。框架的好处在于帮你处理了一些细节,从而实现快速开发,但同时受到Python本身性能的限制。我们已经看到,许多成功的网站都是利用动态语言(比如Python, Ruby或者PHP,比如twitter和facebook)快速开发,在网站成功之后,将代码转换成诸如C和JAVA这样一些效率比较高的语言,从而让服务器能更有效率的面对每天亿万次的请求。在这样一些时间,底层的重要性,就远远超过了框架。 15 | 16 | 17 | 18 | 下面的一篇文章虽然是在谈JAVA,但我觉得也适用于Python的框架之争。 19 | 20 | http://yakovfain.com/2012/10/11/the-degradation-of-java-developers/ 21 | 22 | 23 | 24 | ##TCP/IP和socket 25 | 26 | 我们需要对网络传输,特别是TCP/IP协议和socket有一定的了解。socket是进程间通信的一种方法 (参考Linux进程间通信),它是基于网络传输协议的上层接口。socket有许多种类型,比如基于TCP协议或者UDP协议(两种网络传输协议)。其中又以TCP socket最为常用。TCP socket与双向管道(duplex PIPE)有些类似,一个进程向socket的一端写入或读取文本流,而另一个进程可以从socket的另一端读取或写入,比较特别是,这两个建立socket通信的进程可以分别属于两台不同的计算机。所谓的TCP协议,就是规定了一些通信的守则,以便在网络环境下能够有效实现上述进程间通信过程。双向管道(duplex PIPE)存活于同一台电脑中,所以不必区分两个进程的所在计算机的地址,而socket必须包含有地址信息,以便实现网络通信。一个socket包含四个地址信息: 两台计算机的IP地址和两个进程所使用的端口(port)。IP地址用于定位计算机,而port用于定位进程 (一台计算机上可以有多个进程分别使用不同的端口)。 27 | 28 | 29 | 30 | 31 | 32 | 一个TCP socket连接的网络 33 | 34 | 35 | 36 | ##TCP socket 37 | 38 | 在互联网上,我们可以让某台计算机作为服务器。服务器开放自己的端口,被动等待其他计算机连接。当其他计算机作为客户,主动使用socket连接到服务器的时候,服务器就开始为客户提供服务。 39 | 40 | 41 | 42 | 在Python中,我们使用标准库中的socket包来进行底层的socket编程。 43 | 44 | 首先是服务器端,我们使用bind()方法来赋予socket以固定的地址和端口,并使用listen()方法来被动的监听该端口。当有客户尝试用connect()方法连接的时候,服务器使用accept()接受连接,从而建立一个连接的socket: 45 | 46 | ```python 47 | # Written by Vamei 48 | # Server side 49 | import socket 50 | 51 | # Address 52 | HOST = '' 53 | PORT = 8000 54 | 55 | reply = 'Yes' 56 | 57 | # Configure socket 58 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 59 | s.bind((HOST, PORT)) 60 | 61 | # passively wait, 3: maximum number of connections in the queue 62 | s.listen(3) 63 | # accept and establish connection 64 | conn, addr = s.accept() 65 | # receive message 66 | request = conn.recv(1024) 67 | 68 | print 'request is: ',request 69 | print 'Connected by', addr 70 | # send message 71 | conn.sendall(reply) 72 | # close connection 73 | conn.close() 74 | ``` 75 | socket.socket()创建一个socket对象,并说明socket使用的是IPv4(AF_INET,IP version 4)和TCP协议(SOCK_STREAM)。 76 | 77 | 78 | 79 | 然后用另一台电脑作为客户,我们主动使用connect()方法来搜索服务器端的IP地址(在Linux中,你可以用$ifconfig来查询自己的IP地址)和端口,以便客户可以找到服务器,并建立连接: 80 | 81 | ```python 82 | # Written by Vamei 83 | # Client side 84 | import socket 85 | 86 | # Address 87 | HOST = '172.20.202.155' 88 | PORT = 8000 89 | 90 | request = 'can you hear me?' 91 | 92 | # configure socket 93 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 94 | s.connect((HOST, PORT)) 95 | 96 | # send message 97 | s.sendall(request) 98 | # receive message 99 | reply = s.recv(1024) 100 | print 'reply is: ',reply 101 | # close connection 102 | s.close() 103 | ``` 104 | 在上面的例子中,我们对socket的两端都可以调用recv()方法来接收信息,调用sendall()方法来发送信息。这样,我们就可以在分处于两台计算机的两个进程间进行通信了。当通信结束的时候,我们使用close()方法来关闭socket连接。 105 | 106 | (如果没有两台计算机做实验,也可以将客户端IP想要connect的IP改为"127.0.0.1",这是个特殊的IP地址,用来连接当地主机。) 107 | 108 | 109 | 110 | ##基于TCP socket的HTTP服务器 111 | 112 | 上面的例子中,我们已经可以使用TCP socket来为两台远程计算机建立连接。然而,socket传输自由度太高,从而带来很多安全和兼容的问题。我们往往利用一些应用层的协议(比如HTTP协议)来规定socket使用规则,以及所传输信息的格式。 113 | 114 | 115 | 116 | HTTP协议利用请求-回应(request-response)的方式来使用TCP socket。客户端向服务器发一段文本作为request,服务器端在接收到request之后,向客户端发送一段文本作为response。在完成了这样一次request-response交易之后,TCP socket被废弃。下次的request将建立新的socket。request和response本质上说是两个文本,只是HTTP协议对这两个文本都有一定的格式要求。 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | request-response cycle 125 | 126 | 127 | 128 | 现在,我们写出一个HTTP服务器端: 129 | ```python 130 | # Written by Vamei 131 | 132 | import socket 133 | 134 | # Address 135 | HOST = '' 136 | PORT = 8000 137 | 138 | # Prepare HTTP response 139 | text_content = '''HTTP/1.x 200 OK 140 | Content-Type: text/html 141 | 142 | 143 | WOW 144 | 145 | 146 |

Wow, Python Server

147 | 148 | 149 | ''' 150 | 151 | # Read picture, put into HTTP format 152 | f = open('test.jpg','rb') 153 | pic_content = '''HTTP/1.x 200 OK 154 | Content-Type: image/jpg 155 | 156 | ''' 157 | pic_content = pic_content + f.read() 158 | f.close() 159 | 160 | # Configure socket 161 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 162 | s.bind((HOST, PORT)) 163 | 164 | # infinite loop, server forever 165 | while True: 166 | # 3: maximum number of requests waiting 167 | s.listen(3) 168 | conn, addr = s.accept() 169 | request = conn.recv(1024) 170 | method = request.split(' ')[0] 171 | src = request.split(' ')[1] 172 | 173 | # deal with GET method 174 | if method == 'GET': 175 | # ULR 176 | if src == '/test.jpg': 177 | content = pic_content 178 | else: content = text_content 179 | 180 | print 'Connected by', addr 181 | print 'Request is:', request 182 | conn.sendall(content) 183 | # close connection 184 | conn.close() 185 | ``` 186 | 187 | ##深入HTTP服务器程序 188 | 189 | 如我们上面所看到的,服务器会根据request向客户传输的两条信息text_content和pic_content中的一条,作为response文本。整个response分为起始行(start line), 头信息(head)和主体(body)三部分。起始行就是第一行: 190 | 191 | HTTP/1.x 200 OK 192 | 它实际上又由空格分为三个片段,HTTP/1.x表示所使用的HTTP版本,200表示状态(status code),200是HTTP协议规定的,表示服务器正常接收并处理请求,OK是供人来阅读的status code。 193 | 194 | 195 | 196 | 头信息跟随起始行,它和主体之间有一个空行。这里的text_content或者pic_content都只有一行的头信息,text_content用来表示主体信息的类型为html文本: 197 | 198 | Content-Type: text/html 199 | 而pic_content的头信息(Content-Type: image/jpg)说明主体的类型为jpg图片(image/jpg)。 200 | 201 | 202 | 203 | 主体信息为html或者jpg文件的内容。 204 | 205 | (注意,对于jpg文件,我们使用'rb'模式打开,是为了与windows兼容。因为在windows下,jpg被认为是二进制(binary)文件,在UNIX系统下,则不需要区分文本文件和二进制文件。) 206 | 207 | 208 | 209 | 我们并没有写客户端程序,后面我们会用浏览器作为客户端。request由客户端程序发给服务器。尽管request也可以像response那样分为三部分,request的格式与response的格式并不相同。request由客户发送给服务器,比如下面是一个request: 210 | 211 | GET /test.jpg HTTP/1.x 212 | Accept: text/* 213 | 214 | 215 | 起始行可以分为三部分,第一部分为请求方法(request method),第二部分是URL,第三部分为HTTP版本。request method可以有GET, PUT, POST, DELETE, HEAD。最常用的为GET和POST。GET是请求服务器发送资源给客户,POST是请求服务器接收客户送来的数据。当我们打开一个网页时,我们通常是使用GET方法;当我们填写表格并提交时,我们通常使用POST方法。第二部分为URL,它通常指向一个资源(服务器上的资源或者其它地方的资源)。像现在这样,就是指向当前服务器的当前目录的test.jpg。 216 | 217 | 按照HTTP协议的规定,服务器需要根据请求执行一定的操作。正如我们在服务器程序中看到的,我们的Python程序先检查了request的方法,随后根据URL的不同,来生成不同的response(text_content或者pic_content)。随后,这个response被发送回给客户端。 218 | 219 | 220 | 221 | ##使用浏览器实验 222 | 223 | 为了配合上面的服务器程序,我已经在放置Python程序的文件夹里,保存了一个test.jpg图片文件。我们在终端运行上面的Python程序,作为服务器端,再打开一个浏览器作为客户端。(如果有时间,你也完全可以用Python写一个客户端。原理与上面的TCP socket的客户端程序相类似。) 224 | 225 | 在浏览器的地址栏输入: 226 | 227 | 127.0.0.1:8000 228 | 229 | 230 | (当然,你也可以用令一台电脑,并输入服务器的IP地址。) 我得到下面的结果: 231 | 232 | 233 | 234 | 235 | 236 | OK,我已经有了一个用Python实现的,并从socket写起的服务器了。 237 | 238 | 从终端,我们可以看到,浏览器实际上发出了两个请求。第一个请求为 (关键信息在起始行,这一个请求的主体为空): 239 | 240 | ```bash 241 | GET / HTTP/1.1 242 | Host: 127.0.0.1:8000 243 | User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1 244 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 245 | Accept-Language: en-us,en;q=0.5 246 | Accept-Encoding: gzip, deflate 247 | Connection: keep-alive 248 | ``` 249 | 250 | 我们的Python程序根据这个请求,发送给服务器text_content的内容。 251 | 252 | 253 | 254 | 浏览器接收到text_content之后,发现正文的html文本中有,知道需要获得text.jpg文件来补充为图片,立即发出了第二个请求: 255 | 256 | ```bash 257 | GET /test.jpg HTTP/1.1 258 | Host: 127.0.0.1:8000 259 | User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1 260 | Accept: image/png,image/*;q=0.8,*/*;q=0.5 261 | Accept-Language: en-us,en;q=0.5 262 | Accept-Encoding: gzip, deflate 263 | Connection: keep-alive 264 | Referer: http://127.0.0.1:8000/ 265 | ``` 266 | 267 | 我们的Python程序分析过起始行之后,发现/test.jpg符合if条件,所以将pic_content发送给客户。 268 | 269 | 最后,浏览器根据html语言的语法,将html文本和图画以适当的方式显示出来。(html可参考http://www.w3schools.com/html/default.asp) 270 | 271 | 272 | 273 | ##探索的方向 274 | 275 | 1) 在我们上面的服务器程序中,我们用while循环来让服务器一直工作下去。实际上,我们还可以根据我之前介绍的多线程的知识,将while循环中的内容改为多进程或者多线程工作。(参考Python多线程与同步,Python多进程初步,Python多进程探索) 276 | 277 | 2) 我们的服务器程序还不完善,我们还可以让我们的Python程序调用Python的其他功能,以实现更复杂的功能。比如说制作一个时间服务器,让服务器向客户返回日期和时间。你还可以使用Python自带的数据库,来实现一个完整的LAMP服务器。 278 | 279 | 3) socket包是比较底层的包。Python标准库中还有高层的包,比如SocketServer,SimpleHTTPServer,CGIHTTPServer,cgi。这些都包都是在帮助我们更容易的使用socket。如果你已经了解了socket,那么这些包就很容易明白了。利用这些高层的包,你可以写一个相当成熟的服务器。 280 | 281 | 4) 在经历了所有的辛苦和麻烦之后,你可能发现,框架是那么的方便,所以决定去使用框架。或者,你已经有了参与到框架开发的热情。 282 | 283 | 284 | 285 | 更多内容 286 | 287 | TCP/IP和port参考: TCP/IP illustrated http://book.douban.com/subject/1741925/ 288 | 289 | socket参考: UNIX Network Programming http://book.douban.com/subject/1756533/ 290 | 291 | Python socket 官方文档 http://docs.python.org/2/library/socket.html 292 | 293 | HTTP参考: HTTP, the definitive guide http://book.douban.com/subject/1440226/ 294 | 295 | -------------------------------------------------------------------------------- /content/网络02 Python服务器进化.md: -------------------------------------------------------------------------------- 1 | #Python网络02 Python服务器进化 2 | 3 | 4 | **注意,在Python 3.x中,BaseHTTPServer, SimpleHTTPServer, CGIHTTPServer整合到http.server包,SocketServer改名为socketserver,请注意查阅官方文档。 5 | 6 | 在上一篇文章中(用socket写一个Python服务器),我使用socket接口,制作了一个处理HTTP请求的Python服务器。任何一台装有操作系统和Python解释器的计算机,都可以作为HTTP服务器使用。我将在这里不断改写上一篇文章中的程序,引入更高级的Python包,以写出更成熟的Python服务器。 7 | 8 | 9 | 10 | ##支持POST 11 | 12 | 我首先增加该服务器的功能。这里增添了表格,以及处理表格提交数据的"POST"方法。如果你已经读过用socket写一个Python服务器,会发现这里只是增加很少的一点内容。 13 | 14 | 原始程序: 15 | 16 | ```python 17 | # Written by Vamei 18 | # A messy HTTP server based on TCP socket 19 | 20 | import socket 21 | 22 | # Address 23 | HOST = '' 24 | PORT = 8000 25 | 26 | text_content = '''HTTP/1.x 200 OK 27 | Content-Type: text/html 28 | 29 | 30 | WOW 31 | 32 | 33 |

Wow, Python Server

34 | 35 |
36 | First name:
37 | 38 |
39 | 40 | ''' 41 | 42 | f = open('test.jpg','rb') 43 | pic_content = '''HTTP/1.x 200 OK 44 | Content-Type: image/jpg 45 | 46 | ''' 47 | pic_content = pic_content + f.read() 48 | 49 | # Configure socket 50 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 51 | s.bind((HOST, PORT)) 52 | 53 | # Serve forever 54 | while True: 55 | s.listen(3) 56 | conn, addr = s.accept() 57 | request = conn.recv(1024) # 1024 is the receiving buffer size 58 | method = request.split(' ')[0] 59 | src = request.split(' ')[1] 60 | 61 | print 'Connected by', addr 62 | print 'Request is:', request 63 | 64 | # if GET method request 65 | if method == 'GET': 66 | # if ULR is /test.jpg 67 | if src == '/test.jpg': 68 | content = pic_content 69 | else: content = text_content 70 | # send message 71 | conn.sendall(content) 72 | # if POST method request 73 | if method == 'POST': 74 | form = request.split('\r\n') 75 | idx = form.index('') # Find the empty line 76 | entry = form[idx:] # Main content of the request 77 | 78 | value = entry[-1].split('=')[-1] 79 | conn.sendall(text_content + '\n

' + value + '

') 80 | ###### 81 | # More operations, such as put the form into database 82 | # ... 83 | ###### 84 | # close connection 85 | conn.close() 86 | ``` 87 | 服务器进行的操作很简单,即从POST请求中提取数据,再显示在屏幕上。 88 | 89 | 运行上面Python服务器,像上一篇文章那样,使用一个浏览器打开。 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 页面新增了表格和提交(submit)按钮。在表格中输入aa并提交,页面显示出aa。 98 | 99 | 100 | 101 | 我下一步要用一些高级包,来简化之前的代码。 102 | 103 | 104 | 105 | 106 | 107 | ##使用SocketServer 108 | 109 | 首先使用SocketServer包来方便的架设服务器。在上面使用socket的过程中,我们先设置了socket的类型,然后依次调用bind(),listen(),accept(),最后使用while循环来让服务器不断的接受请求。上面的这些步骤可以通过SocketServer包来简化。 110 | 111 | SocketServer: 112 | 113 | ```python 114 | # Written by Vamei 115 | # use TCPServer 116 | 117 | import SocketServer 118 | 119 | HOST = '' 120 | PORT = 8000 121 | 122 | text_content = '''HTTP/1.x 200 OK 123 | Content-Type: text/html 124 | 125 | 126 | WOW 127 | 128 | 129 |

Wow, Python Server

130 | 131 |
132 | First name:
133 | 134 |
135 | 136 | ''' 137 | 138 | f = open('test.jpg','rb') 139 | pic_content = '''HTTP/1.x 200 OK 140 | Content-Type: image/jpg 141 | 142 | ''' 143 | pic_content = pic_content + f.read() 144 | 145 | # This class defines response to each request 146 | class MyTCPHandler(SocketServer.BaseRequestHandler): 147 | def handle(self): 148 | # self.request is the TCP socket connected to the client 149 | request = self.request.recv(1024) 150 | 151 | print 'Connected by',self.client_address[0] 152 | print 'Request is', request 153 | 154 | method = request.split(' ')[0] 155 | src = request.split(' ')[1] 156 | 157 | if method == 'GET': 158 | if src == '/test.jpg': 159 | content = pic_content 160 | else: content = text_content 161 | self.request.sendall(content) 162 | 163 | if method == 'POST': 164 | form = request.split('\r\n') 165 | idx = form.index('') # Find the empty line 166 | entry = form[idx:] # Main content of the request 167 | 168 | value = entry[-1].split('=')[-1] 169 | self.request.sendall(text_content + '\n

' + value + '

') 170 | ###### 171 | # More operations, such as put the form into database 172 | # ... 173 | ###### 174 | 175 | 176 | # Create the server 177 | server = SocketServer.TCPServer((HOST, PORT), MyTCPHandler) 178 | # Start the server, and work forever 179 | server.serve_forever() 180 | ``` 181 | 182 | 183 | 我建立了一个TCPServer对象,即一个使用TCP socket的服务器。在建立TCPServe的同时,设置该服务器的IP地址和端口。使用server_forever()方法来让服务器不断工作(就像原始程序中的while循环一样)。 184 | 185 | 我们传递给TCPServer一个MyTCPHandler类。这个类定义了如何操作socket。MyTCPHandler继承自BaseRequestHandler。改写handler()方法,来具体规定不同情况下服务器的操作。 186 | 187 | 在handler()中,通过self.request来查询通过socket进入服务器的请求 (正如我们在handler()中对socket进行recv()和sendall()操作),还使用self.address来引用socket的客户端地址。 188 | 189 | 190 | 191 | 经过SocketServer的改造之后,代码还是不够简单。 我们上面的通信基于TCP协议,而不是HTTP协议。因此,我们必须手动的解析HTTP协议。我们将建立基于HTTP协议的服务器。 192 | 193 | 194 | 195 | ##SimpleHTTPServer: 使用静态文件来回应请求 196 | 197 | HTTP协议基于TCP协议,但增加了更多的规范。这些规范,虽然限制了TCP协议的功能,但大大提高了信息封装和提取的方便程度。 198 | 199 | 对于一个HTTP请求(request)来说,它包含有两个重要信息:请求方法和URL。 200 | 201 | 请求方法(request method) URL 操作 202 | 203 | GET / 发送text_content 204 | 205 | GET /text.jpg 发送pic_content 206 | 207 | POST / 分析request主体中包含的value(实际上是我们填入表格的内容); 发送text_content和value 208 | 209 | 210 | 211 | 根据请求方法和URL的不同,一个大型的HTTP服务器可以应付成千上万种不同的请求。在Python中,我们可以使用SimpleHTTPServer包和CGIHTTPServer包来规定针对不同请求的操作。其中,SimpleHTTPServer可以用于处理GET方法和HEAD方法的请求。它读取request中的URL地址,找到对应的静态文件,分析文件类型,用HTTP协议将文件发送给客户。 212 | 213 | 214 | 215 | 216 | 217 | SimpleHTTPServer 218 | 219 | 220 | 221 | 我们将text_content放置在index.html中,并单独存储text.jpg文件。如果URL指向index_html的母文件夹时,SimpleHTTPServer会读取该文件夹下的index.html文件。 222 | 223 | 224 | 225 | 我在当前目录下生成index.html文件: 226 | 227 | ```python 228 | 229 | WOW 230 | 231 | 232 |

Wow, Python Server

233 | 234 |
235 | First name:
236 | 237 |
238 | 239 | ``` 240 | 241 | 242 | 改写Python服务器程序。使用SimpleHTTPServer包中唯一的类SimpleHTTPRequestHandler: 243 | 244 | ```python 245 | # Written by Vamei 246 | # Simple HTTPsERVER 247 | 248 | import SocketServer 249 | import SimpleHTTPServer 250 | 251 | HOST = '' 252 | PORT = 8000 253 | 254 | # Create the server, SimpleHTTPRequestHander is pre-defined handler in SimpleHTTPServer package 255 | server = SocketServer.TCPServer((HOST, PORT), SimpleHTTPServer.SimpleHTTPRequestHandler) 256 | # Start the server 257 | server.serve_forever() 258 | ``` 259 | 260 | 261 | 这里的程序不能处理POST请求。我会在后面使用CGI来弥补这个缺陷。值得注意的是,Python服务器程序变得非常简单。将内容存放于静态文件,并根据URL为客户端提供内容,这让内容和服务器逻辑分离。每次更新内容时,我可以只修改静态文件,而不用停止整个Python服务器。 262 | 263 | 这些改进也付出代价。在原始程序中,request中的URL只具有指导意义,我可以规定任意的操作。在SimpleHTTPServer中,操作与URL的指向密切相关。我用自由度,换来了更加简洁的程序。 264 | 265 | 266 | 267 | ##CGIHTTPServer:使用静态文件或者CGI来回应请求 268 | 269 | CGIHTTPServer包中的CGIHTTPRequestHandler类继承自SimpleHTTPRequestHandler类,所以可以用来代替上面的例子,来提供静态文件的服务。此外,CGIHTTPRequestHandler类还可以用来运行CGI脚本。 270 | 271 | 272 | 273 | CGIHTTPServer 274 | 275 | 276 | 277 | 先看看什么是CGI (Common Gateway Interface)。CGI是服务器和应用脚本之间的一套接口标准。它的功能是让服务器程序运行脚本程序,将程序的输出作为response发送给客户。总体的效果,是允许服务器动态的生成回复内容,而不必局限于静态文件。 278 | 279 | 支持CGI的服务器程接收到客户的请求,根据请求中的URL,运行对应的脚本文件。服务器会将HTTP请求的信息和socket信息传递给脚本文件,并等待脚本的输出。脚本的输出封装成合法的HTTP回复,发送给客户。CGI可以充分发挥服务器的可编程性,让服务器变得“更聪明”。 280 | 281 | 服务器和CGI脚本之间的通信要符合CGI标准。CGI的实现方式有很多,比如说使用Apache服务器与Perl写的CGI脚本,或者Python服务器与shell写的CGI脚本。 282 | 283 | 284 | 285 | 为了使用CGI,我们需要使用BaseHTTPServer包中的HTTPServer类来构建服务器。Python服务器的改动很简单。 286 | 287 | CGIHTTPServer: 288 | 289 | ```python 290 | # Written by Vamei 291 | # A messy HTTP server based on TCP socket 292 | 293 | import BaseHTTPServer 294 | import CGIHTTPServer 295 | 296 | HOST = '' 297 | PORT = 8000 298 | 299 | # Create the server, CGIHTTPRequestHandler is pre-defined handler 300 | server = BaseHTTPServer.HTTPServer((HOST, PORT), CGIHTTPServer.CGIHTTPRequestHandler) 301 | # Start the server 302 | server.serve_forever() 303 | ``` 304 | 305 | 306 | CGIHTTPRequestHandler默认当前目录下的cgi-bin和ht-bin文件夹中的文件为CGI脚本,而存放于其他地方的文件被认为是静态文件。因此,我们需要修改一下index.html,将其中form元素指向的action改为cgi-bin/post.py。 307 | 308 | ```python 309 | 310 | WOW 311 | 312 | 313 |

Wow, Python Server

314 | 315 |
316 | First name:
317 | 318 |
319 | 320 | ``` 321 | 322 | 323 | 我创建一个cgi-bin的文件夹,并在cgi-bin中放入如下post.py文件,也就是我们的CGI脚本: 324 | 325 | ```python 326 | #!/usr/bin/env python 327 | # Written by Vamei 328 | import cgi 329 | form = cgi.FieldStorage() 330 | 331 | # Output to stdout, CGIHttpServer will take this as response to the client 332 | print "Content-Type: text/html" # HTML is following 333 | print # blank line, end of headers 334 | print "

Hello world!

" # Start of content 335 | print "

" + repr(form['firstname']) + "

" 336 | ``` 337 | (post.py需要有执行权限,见评论区) 338 | 339 | 第一行说明了脚本所使用的语言,即Python。 cgi包用于提取请求中包含的表格信息。脚本只负责将所有的结果输出到标准输出(使用print)。CGIHTTPRequestHandler会收集这些输出,封装成HTTP回复,传送给客户端。 340 | 341 | 对于POST方法的请求,它的URL需要指向一个CGI脚本(也就是在cgi-bin或者ht-bin中的文件)。CGIHTTPRequestHandler继承自SimpleHTTPRequestHandler,所以也可以处理GET方法和HEAD方法的请求。此时,如果URL指向CGI脚本时,服务器将脚本的运行结果传送到客户端;当此时URL指向静态文件时,服务器将文件的内容传送到客户端。 342 | 343 | 更进一步,我可以让CGI脚本执行数据库操作,比如将接收到的数据放入到数据库中,以及更丰富的程序操作。相关内容从略。 344 | 345 | 346 | 347 | ##总结 348 | 349 | 我使用了Python标准库中的一些高级包简化了Python服务器。最终的效果分离静态内容、CGI应用和服务器,降低三者之间的耦合,让代码变得简单而容易维护。 350 | 351 | 希望你享受在自己的电脑上架设服务器的过程。 352 | -------------------------------------------------------------------------------- /content/补充02 Python小技巧.md: -------------------------------------------------------------------------------- 1 | ##Python补充02 Python小技巧 2 | 3 | 4 | 5 | 6 | 在这里列举一些我使用Python时积累的小技巧。这些技巧是我在使用Python过程中经常使用的。之前很零碎的记在笔记本中,现在整理出来,和大家分享,也作为Python快速教程的一个补充。 7 | 8 | 9 | 10 | ##import模块 11 | 12 | 在Python经常使用import声明,以使用其他模块(也就是其它.py文件)中定义的对象。 13 | 14 | 1) 使用__name__ 15 | 16 | 当我们编写Python库模块的时候,我们往往运行一些测试语句。当这个程序作为库被import的时候,我们并不需要运行这些测试语句。一种解决方法是在import之前,将模块中的测试语句注释掉。Python有一种更优美的解决方法,就是使用__name__。 17 | 18 | 下面是一个简单的库程序TestLib.py。当直接运行TestLib.py时,__name__为"__main__"。如果被import的话,__name__为"TestLib"。 19 | 20 | ```python 21 | def lib_func(a): 22 | return a + 10 23 | 24 | def lib_func_another(b): 25 | return b + 20 26 | 27 | if __name__ == '__main__': 28 | test = 101 29 | print(lib_func(test)) 30 | ``` 31 | 我们在user.py中import上面的TestLib。 32 | 33 | ```python 34 | import TestLib 35 | print(TestLib.lib_func(120)) 36 | ``` 37 | 你可以尝试不在TestLib.py中使用if __name__=='__main__', 并对比运行结果。 38 | 39 | 40 | 41 | 2) 更多import使用方式 42 | 43 | import TestLib as test # 引用TestLib模块,并将它改名为t 44 | 45 | 比如: 46 | ```pyhton 47 | import TestLib as t 48 | print(t.lib_func(120)) 49 | from TestLib import lib_func # 只引用TestLib中的lib_func对象,并跳过TestLib引用字段 50 | ``` 51 | 这样的好处是减小所引用模块的内存占用。 52 | 53 | 比如: 54 | ```ython 55 | from TestLib import lib_func 56 | print(lib_func(120)) 57 | from TestLib import * # 引用所有TestLib中的对象,并跳过TestLib引用字段 58 | ``` 59 | 比如: 60 | ```python 61 | from TestLib import * 62 | print(lib_func(120)) 63 | ``` 64 | 65 | ##查询 66 | 67 | 1) 查询函数的参数 68 | 69 | 当我们想要知道某个函数会接收哪些参数的时候,可以使用下面方法查询。 70 | ```python 71 | import inspect 72 | print(inspect.getargspec(func)) 73 | ``` 74 | 75 | 2) 查询对象的属性 76 | 77 | 除了使用dir()来查询对象的属性之外,我们可以使用下面内置(built-in)函数来确认一个对象是否具有某个属性: 78 | 79 | hasattr(obj, attr_name) # attr_name是一个字符串 80 | 81 | 例如: 82 | ```python 83 | a = [1,2,3] 84 | print(hasattr(a,'append')) 85 | ``` 86 | 87 | 2) 查询对象所属的类和类名称 88 | ```python 89 | a = [1, 2, 3] 90 | print a.__class__ 91 | print a.__class__.__name__ 92 | ``` 93 | 94 | 3) 查询父类 95 | 96 | 我们可以用__base__属性来查询某个类的父类: 97 | 98 | cls.__base__ 99 | 100 | 例如: 101 | ```python 102 | print(list.__base__) 103 | ``` 104 | 105 | ##使用中文(以及其它非ASCII编码) 106 | 107 | 在Python程序的第一行加入#coding=utf8,例如: 108 | ```python 109 | #coding=utf8 110 | print("你好吗?") 111 | ``` 112 | 也能用以下方式: 113 | ```python 114 | #-*- coding: UTF-8 -*- 115 | print("你好吗?") 116 | ``` 117 | 118 | ##表示2进制,8进制和16进制数字 119 | 120 | 在2.6以上版本,以如下方式表示 121 | ```python 122 | print(0b1110) # 二进制,以0b开头 123 | print(0o10) # 八进制,以0o开头 124 | print(0x2A) # 十六进制,以0x开头 125 | ``` 126 | 如果是更早版本,可以用如下方式: 127 | ```python 128 | print(int("1110", 2)) 129 | print(int("10", 8)) 130 | print(int("2A", 16)) 131 | ``` 132 | 133 | ##注释 134 | 135 | 一行内的注释可以以#开始 136 | 137 | 多行的注释可以以'''开始,以'''结束,比如 138 | 139 | ```python 140 | ''' 141 | This is demo 142 | ''' 143 | 144 | def func(): 145 | # print something 146 | print("Hello world!") # use print() function 147 | 148 | # main 149 | func() 150 | ``` 151 | 注释应该和所在的程序块对齐。 152 | 153 | 154 | 155 | ##搜索路径 156 | 157 | 当我们import的时候,Python会在搜索路径中查找模块(module)。比如上面import TestLib,就要求TestLib.py在搜索路径中。 158 | 159 | 我们可以通过下面方法来查看搜索路径: 160 | ```python 161 | import sys 162 | print(sys.path) 163 | ``` 164 | 我们可以在Python运行的时候增加或者删除sys.path中的元素。另一方面,我们可以通过在shell中增加PYTHONPATH环境变量,来为Python增加搜索路径。 165 | 166 | 下面我们增加/home/vamei/mylib到搜索路径中: 167 | 168 | $export PYTHONPATH=$PYTHONPATH:/home/vamei/mylib 169 | 170 | 你可以将正面的这行命令加入到~/.bashrc中。这样,我们就长期的改变了搜索路径。 171 | 172 | 173 | 174 | ##脚本与命令行结合 175 | 176 | 可以使用下面方法运行一个Python脚本,在脚本运行结束后,直接进入Python命令行。这样做的好处是脚本的对象不会被清空,可以通过命令行直接调用。 177 | ```python 178 | $python -i script.py 179 | ``` 180 | 181 | 182 | ##安装非标准包 183 | 184 | Python的标准库随着Python一起安装。当我们需要非标准包时,就要先安装。 185 | 186 | 1) 使用Linux repository (Linux环境) 187 | 188 | 这是安装Python附加包的一个好的起点。你可以在Linux repository中查找可能存在的Python包 (比如在Ubuntu Software Center中搜索matplot)。 189 | 190 | 2) 使用pip。pip是Python自带的包管理程序,它连接Python repository,并查找其中可能存在的包。 191 | 192 | 比如使用如下方法来安装、卸载或者升级web.py: 193 | 194 | $pip install web.py 195 | 196 | $pip uninstall web.py 197 | 198 | $pip install --upgrade web.py 199 | 200 | 如果你的Python安装在一个非标准的路径(使用$which python来确认python可执行文件的路径)中,比如/home/vamei/util/python/bin中,你可以使用下面方法设置pip的安装包的路径: 201 | 202 | $pip install --install-option="--prefix=/home/vamei/util/" web.py 203 | 204 | 3) 从源码编译 205 | 206 | 如果上面方法都没法找到你想要的库,你可能需要从源码开始编译。Google往往是最好的起点。 207 | -------------------------------------------------------------------------------- /content/补充03 Python内置函数清单.md: -------------------------------------------------------------------------------- 1 | 2 | ##Python补充03 Python内置函数清单 3 | 4 | 5 | 6 | 7 | Python内置(built-in)函数随着python解释器的运行而创建。在Python的程序中,你可以随时调用这些函数,不需要定义。最常见的内置函数是: 8 | 9 | print("Hello World!") 10 | 11 | 在Python教程中,我们已经提到下面一些内置函数: 12 | 13 | 基本数据类型 type() 14 | 15 | 反过头来看看 dir() help() len() 16 | 17 | 词典 len() 18 | 19 | 文本文件的输入输出 open() 20 | 21 | 循环设计 range() enumerate() zip() 22 | 23 | 循环对象 iter() 24 | 25 | 函数对象 map() filter() reduce() 26 | 27 | 28 | 29 | 下面我采取的都是实际的参数,你可以直接在命令行尝试效果。 30 | 31 | ##数学运算 32 | 33 | abs(-5) # 取绝对值,也就是5 34 | 35 | round(2.6) # 四舍五入取整,也就是3.0 36 | 37 | pow(2, 3) # 相当于2**3,如果是pow(2, 3, 5),相当于2**3 % 5 38 | 39 | cmp(2.3, 3.2) # 比较两个数的大小 40 | 41 | divmod(9,2) # 返回除法结果和余数 42 | 43 | max([1,5,2,9]) # 求最大值 44 | 45 | min([9,2,-4,2]) # 求最小值 46 | 47 | sum([2,-1,9,12]) # 求和 48 | 49 | 50 | 51 | ##类型转换 52 | 53 | int("5") # 转换为整数 integer 54 | 55 | float(2) # 转换为浮点数 float 56 | 57 | long("23") # 转换为长整数 long integer 58 | 59 | str(2.3) # 转换为字符串 string 60 | 61 | complex(3, 9) # 返回复数 3 + 9i 62 | 63 | 64 | 65 | ord("A") # "A"字符对应的数值 66 | 67 | chr(65) # 数值65对应的字符 68 | 69 | unichr(65) # 数值65对应的unicode字符 70 | 71 | 72 | 73 | bool(0) # 转换为相应的真假值,在Python中,0相当于False 74 | 75 | 在Python中,下列对象都相当于False: [], (), {}, 0, None, 0.0, '' 76 | 77 | 78 | 79 | bin(56) # 返回一个字符串,表示56的二进制数 80 | 81 | hex(56) # 返回一个字符串,表示56的十六进制数 82 | 83 | oct(56) # 返回一个字符串,表示56的八进制数 84 | 85 | 86 | 87 | list((1,2,3)) # 转换为表 list 88 | 89 | tuple([2,3,4]) # 转换为定值表 tuple 90 | 91 | slice(5,2,-1) # 构建下标对象 slice 92 | 93 | dict(a=1,b="hello",c=[1,2,3]) # 构建词典 dictionary 94 | 95 | 96 | 97 | ##序列操作 98 | 99 | all([True, 1, "hello!"]) # 是否所有的元素都相当于True值 100 | 101 | any(["", 0, False, [], None]) # 是否有任意一个元素相当于True值 102 | 103 | 104 | 105 | 106 | 107 | sorted([1,5,3]) # 返回正序的序列,也就是[1,3,5] 108 | 109 | reversed([1,5,3]) # 返回反序的序列,也就是[3,5,1] 110 | 111 | 112 | 113 | ##类,对象,属性 114 | 115 | ```python 116 | # define class 117 | class Me(object): 118 | def test(self): 119 | print "Hello!" 120 | 121 | def new_test(): 122 | print "New Hello!" 123 | 124 | me = Me() 125 | ``` 126 | hasattr(me, "test") # 检查me对象是否有test属性 127 | 128 | getattr(me, "test") # 返回test属性 129 | 130 | setattr(me, "test", new_test) # 将test属性设置为new_test 131 | 132 | delattr(me, "test") # 删除test属性 133 | 134 | isinstance(me, Me) # me对象是否为Me类生成的对象 (一个instance) 135 | 136 | issubclass(Me, object) # Me类是否为object类的子类 137 | 138 | 139 | 140 | ##编译,执行 141 | 142 | repr(me) # 返回对象的字符串表达 143 | 144 | compile("print('Hello')",'test.py','exec') # 编译字符串成为code对象 145 | 146 | eval("1 + 1") # 解释字符串表达式。参数也可以是compile()返回的code对象 147 | 148 | exec("print('Hello')") # 解释并执行字符串,print('Hello')。参数也可以是compile()返回的code对象 149 | 150 | 151 | 152 | ##s其他 153 | 154 | input("Please input:") # 等待输入 155 | 156 | 157 | 158 | globals() # 返回全局命名空间,比如全局变量名,全局函数名 159 | 160 | locals() # 返回局部命名空间 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /content/补充05 字符串格式化 (%操作符).md: -------------------------------------------------------------------------------- 1 | #Python补充05 字符串格式化 (%操作符) 2 | 3 | 4 | 5 | 6 | 在许多编程语言中都包含有格式化字符串的功能,比如C和Fortran语言中的格式化输入输出。Python中内置有对字符串进行格式化的操作%。 7 | 8 | 9 | 10 | ##模板 11 | 12 | 格式化字符串时,Python使用一个字符串作为模板。模板中有格式符,这些格式符为真实值预留位置,并说明真实数值应该呈现的格式。Python用一个tuple将多个值传递给模板,每个值对应一个格式符。 13 | 14 | 比如下面的例子: 15 | ```python 16 | print("I'm %s. I'm %d year old" % ('Vamei', 99)) 17 | ``` 18 | 上面的例子中, 19 | 20 | "I'm %s. I'm %d year old" 为我们的模板。%s为第一个格式符,表示一个字符串。%d为第二个格式符,表示一个整数。('Vamei', 99)的两个元素'Vamei'和99为替换%s和%d的真实值。 21 | 在模板和tuple之间,有一个%号分隔,它代表了格式化操作。 22 | 23 | 整个"I'm %s. I'm %d year old" % ('Vamei', 99) 实际上构成一个字符串表达式。我们可以像一个正常的字符串那样,将它赋值给某个变量。比如: 24 | ```python 25 | a = "I'm %s. I'm %d year old" % ('Vamei', 99) 26 | print(a) 27 | ``` 28 | 29 | 我们还可以用词典来传递真实值。如下: 30 | ```python 31 | print("I'm %(name)s. I'm %(age)d year old" % {'name':'Vamei', 'age':99} 32 | ``` 33 | 可以看到,我们对两个格式符进行了命名。命名使用()括起来。每个命名对应词典的一个key。 34 | 35 | 36 | 37 | 格式符 38 | 39 | 格式符为真实值预留位置,并控制显示的格式。格式符可以包含有一个类型码,用以控制显示的类型,如下: 40 | 41 | %s 字符串 (采用str()的显示) 42 | 43 | %r 字符串 (采用repr()的显示) 44 | 45 | %c 单个字符 46 | 47 | %b 二进制整数 48 | 49 | %d 十进制整数 50 | 51 | %i 十进制整数 52 | 53 | %o 八进制整数 54 | 55 | %x 十六进制整数 56 | 57 | %e 指数 (基底写为e) 58 | 59 | %E 指数 (基底写为E) 60 | 61 | %f 浮点数 62 | 63 | %F 浮点数,与上相同 64 | 65 | %g 指数(e)或浮点数 (根据显示长度) 66 | 67 | %G 指数(E)或浮点数 (根据显示长度) 68 | 69 | 70 | 71 | %% 字符"%" 72 | 73 | 74 | 75 | 可以用如下的方式,对格式进行进一步的控制: 76 | 77 | %[(name)][flags][width].[precision]typecode 78 | 79 | (name)为命名 80 | 81 | flags可以有+,-,' '或0。+表示右对齐。-表示左对齐。' '为一个空格,表示在正数的左侧填充一个空格,从而与负数对齐。0表示使用0填充。 82 | 83 | width表示显示宽度 84 | 85 | precision表示小数点后精度 86 | 87 | 88 | 89 | 比如: 90 | ```python 91 | print("%+10x" % 10) 92 | print("%04d" % 5) 93 | print("%6.3f" % 2.3) 94 | ``` 95 | 96 | 上面的width, precision为两个整数。我们可以利用*,来动态代入这两个量。比如: 97 | ```python 98 | print("%.*f" % (4, 1.2)) 99 | ``` 100 | Python实际上用4来替换*。所以实际的模板为"%.4f"。 101 | 102 | 103 | 104 | ##总结 105 | 106 | Python中内置的%操作符可用于格式化字符串操作,控制字符串的呈现格式。Python中还有其他的格式化字符串的方式,但%操作符的使用是最方便的。 107 | -------------------------------------------------------------------------------- /content/补充06 Python之道.md: -------------------------------------------------------------------------------- 1 | #Python补充06 Python之道 2 | 3 | 4 | 5 | 6 | Python有一个彩蛋,用下面语句调出: 7 | ```python 8 | import this 9 | ``` 10 | 该彩蛋的文档记录于PEP 20。 11 | 12 | 语句执行之后,终端将显示一首"The Zen of Python"的诗,即“Python之道”。这首诗总结了Python的风格,可以指导Python程序员的编程。Python程序员会自豪宣称自己"Pythonic"。Pythonic的一个基本标准,是写出合乎“Python之道”的代码。 13 | 14 | 15 | 16 | 下面是“Python之道”的全文,以及我附加的翻译。 17 | 18 | The Zen of Python, by Tim Peters 19 | 20 | Python之道 21 | 22 | 23 | Beautiful is better than ugly. 24 | 25 | 美观胜于丑陋。 26 | 27 | 28 | Explicit is better than implicit. 29 | 30 | 显示胜于隐式。 31 | 32 | 33 | Simple is better than complex. 34 | 35 | 简单胜于复杂。 36 | 37 | 38 | Complex is better than complicated. 39 | 40 | 复杂胜于过度复杂。 41 | 42 | 43 | Flat is better than nested. 44 | 45 | 平面胜于嵌套。 46 | 47 | 48 | Sparse is better than dense. 49 | 50 | 稀少胜于稠密。 51 | 52 | 53 | Readability counts. 54 | 55 | 可读性需要考虑。 56 | 57 | 58 | Special cases aren't special enough to break the rules. 59 | 60 | 即使情况特殊,也不应打破原则, 61 | 62 | 63 | Although practicality beats purity. 64 | 65 | 尽管实用胜于纯净。 66 | 67 | 68 | Errors should never pass silently. 69 | 70 | 错误不应悄无声息的通过, 71 | 72 | 73 | Unless explicitly silenced. 74 | 75 | 除非特意这么做。 76 | 77 | 78 | In the face of ambiguity, refuse the temptation to guess. 79 | 80 | 当有混淆时,拒绝猜测(深入的搞明白问题)。 81 | 82 | 83 | There should be one-- and preferably only one --obvious way to do it. 84 | 85 | 总有一个,且(理想情况下)只有一个,明显的方法来处理问题。 86 | 87 | 88 | Although that way may not be obvious at first unless you're Dutch. 89 | 90 | 尽管那个方法可能并不明显,除非你是荷兰人。(Python的作者Guido是荷兰人,这是在致敬) 91 | 92 | 93 | Now is better than never. 94 | 95 | 现在开始胜过永远不开始, 96 | 97 | 98 | Although never is often better than *right* now. 99 | 100 | 尽管永远不开始经常比仓促立即开始好。 101 | 102 | 103 | If the implementation is hard to explain, it's a bad idea. 104 | 105 | 如果程序实现很难解释,那么它是个坏主意。 106 | 107 | 108 | If the implementation is easy to explain, it may be a good idea. 109 | 110 | 如果程序实现很容易解释,那么它可能是个好主意。 111 | 112 | 113 | Namespaces are one honking great idea -- let's do more of those! 114 | 115 | 命名空间是个绝好的主意,让我们多利用它。 116 | 117 | 118 | 119 | "Python之道"强调美观、简单、可读和实用,拒绝复杂或模糊。 120 | 121 | 122 | 123 | ##历史 124 | 125 | Tim Peters于June 4, 1999的Python邮件列表,以"The Python Way"为标题,发表了“Python之道”,得到许多Python程序员的认同。另一方面,2001年的International Python Conference #10 (IPC10, IPC是PyCon的前身)会议,主办方希望在文化衫上印标语,而这一标语要能代表Python文化。到会的Python程序员们创作了500多条。组织者选了"import this"。后来,Python的作者Guido增加了this.py模块,让这个语句可以在解释器中执行,打印“Python之道”。 126 | 127 | Python社区很幽默。 128 | 129 | 130 | 131 | 此外,PyCon是Python爱好者的集会,可以去给别人展示自己的Python项目,或者听讲座。2012年的PyCon是在北京和上海同时举行。 132 | -------------------------------------------------------------------------------- /content/被解放的姜戈01 初试天涯.md: -------------------------------------------------------------------------------- 1 | #被解放的姜戈01 初试天涯 2 | 3 | 4 | 5 | 6 | Django是Python下的一款网络服务器框架。Python下有许多款不同的框架。Django是重量级选手中最有代表性的一位。许多成功的网站和APP都基于Django。虽然Django之于Python,达不到Rail之于Ruby的一统江湖的地位,但Django无疑也是Python在网络应用方面的一位主将。 7 | 8 | 9 | 10 | 11 | 12 | 向昆汀的致敬,“被解放的姜戈” 13 | 14 | 15 | 16 | Django意外的和昆汀的电影重名。这大大提高了Django的知名度。另外利用电影,宣传了这个奇怪的词的发音。 17 | 18 | 19 | 20 | 下面是姜戈的初体验。 21 | 22 | 23 | 24 | ##安装Django 25 | 26 | 启动计算机中的Python,尝试载入Django模块。如果可以成功载入,那么说明Django已经安装好: 27 | ```python 28 | import django 29 | print(django.VERSION) 30 | ``` 31 | 32 | 如果Django还没有安装,可以在命令行,尝试使用pip安装: 33 | ```python 34 | sudo pip install django 35 | ``` 36 | 或者使用easy_install: 37 | ```python 38 | sudo easy_install django 39 | ``` 40 | 41 | 黑夜中,姜戈和镣铐说再见。 42 | 43 | 44 | 45 | ##启动 46 | 47 | 使用下面的命令创建项目: 48 | ```python 49 | django-admin.py startproject mysite 50 | ``` 51 | 52 | 在当前目录下,将生成mysite文件夹。其文件树结构如下: 53 | 54 | ```python 55 | mysite 56 | ├── manage.py 57 | └── mysite 58 | ├── __init__.py 59 | ├── settings.py 60 | ├── urls.py 61 | └── wsgi.py 62 | 63 | 1 directory, 5 files 64 | ``` 65 | 66 | 67 | 进入mysite,启动服务器: 68 | ```python 69 | python manage.py runserver 8000 70 | ``` 71 | 上面的8000为端口号。如果不说明,那么端口号默认为8000。 72 | 73 | 74 | 75 | 打开浏览器,访问http://127.0.0.1:8000,可以看到服务器已经在运行: 76 | 77 | 78 | 79 | 虽然有一个能跑的服务器,但什么内容都没有。 80 | 81 | 82 | 83 | “赏金?猎人?” 姜戈满脸困惑。 84 | 85 | 86 | 87 | ##第一个网页 88 | 89 | 在http协议中可以看到,网络服务器是“请求-回应”的工作模式。客户向URL发送请求,服务器根据请求,开动后厨,并最终为客人上菜。Django采用的MVC结构,即点单、厨房、储藏室分离。 90 | 91 | 92 | 93 | 我们需要一个指挥员,将URL对应分配给某个对象处理,这需要在mysite/mysite下的urls.py设定。Python会根据该程序,将URL请求分给某个厨师。 94 | 95 | 复制代码 96 | ```python 97 | ├── manage.py 98 | └── mysite 99 | ├── __init__.py 100 | ├── settings.py 101 | ├── urls.py 102 | └── wsgi.py 103 | 104 | 1 directory, 5 files 105 | ``` 106 | 107 | 108 | 将urls.py修改为: 109 | 110 | ```python 111 | from django.conf.urls import patterns, include, url 112 | 113 | from django.contrib import admin 114 | admin.autodiscover() 115 | 116 | urlpatterns = patterns('', 117 | # Examples: 118 | # url(r'^$', 'mysite.views.home', name='home'), 119 | # url(r'^blog/', include('blog.urls')), 120 | 121 | url(r'^admin/', include(admin.site.urls)), 122 | url(r'^$', 'mysite.views.first_page'), 123 | ) 124 | ``` 125 | 我们添加了最后一行。它将根目录的URL分配给一个对象进行处理,这个对象是mysite.views.first_page。 126 | 127 | 用以处理HTTP请求的这一对象还不存在,我们在mysite/mysite下创建views.py,并在其中定义first_page函数: 128 | ```python 129 | # -*- coding: utf-8 -*- 130 | 131 | from django.http import HttpResponse 132 | 133 | def first_page(request): 134 | return HttpResponse("

世界好

") 135 | ``` 136 | 第一行说明字符编码为utf-8,为下面使用中文做准备。first_page函数的功能,是返回http回复,即这里的

世界好

。first_page有一个参数request,该参数包含有请求的具体信息,比如请求的类型等,这里并没有用到。 137 | 138 | 139 | 140 | 141 | 姜戈接过枪,一枪射出去。“天哪!” 德国人惊呆了。 142 | 143 | 144 | 145 | ##增加app 146 | 147 | 一个网站可能有多个功能。我们可以在Django下,以app为单位,模块化的管理,而不是将所有的东西都丢到一个文件夹中。在mysite下,运行manange.py,创建新的app: 148 | 149 | $python manage.py startapp west 150 | 这个新的app叫做west,用来处理西餐。 151 | 152 | 153 | 154 | 我们的根目录下,出现了一个新的叫做west的文件夹。 155 | 156 | ```python 157 | mysite/ 158 | ├── manage.py 159 | ├── mysite 160 | │ ├── __init__.py 161 | │ ├── __init__.pyc 162 | │ ├── settings.py 163 | │ ├── settings.pyc 164 | │ ├── urls.py 165 | │ ├── views.py 166 | │ └── wsgi.py 167 | └── west 168 | ├── admin.py 169 | ├── __init__.py 170 | ├── models.py 171 | ├── tests.py 172 | └── views.py 173 | ``` 174 | 175 | 我们还需要修改项目设置,说明我们要使用west。在mysite/setting.py中,在INSTALLED_APPS中,增加"west": 176 | 177 | ```python 178 | INSTALLED_APPS = ( 179 | 'django.contrib.admin', 180 | 'django.contrib.auth', 181 | 'django.contrib.contenttypes', 182 | 'django.contrib.sessions', 183 | 'django.contrib.messages', 184 | 'django.contrib.staticfiles', 185 | 'west', 186 | ) 187 | ``` 188 | 可以看到,除了新增加的west,Django已经默认加载了一些功能性的app,比如用户验证、会话管理、显示静态文件等。我们将在以后讲解它们的用途。 189 | 190 | 191 | 192 | 姜戈看到曾经的工头们,眼中充满怒火。 193 | 194 | 195 | 196 | ##增加APP页面 197 | 198 | 我们下面为APP增加首页。我们之前是在mysite/urls.py中设置的URL访问对象。依然采用类似的方式设置。 199 | 200 | 另一方面,为了去耦合,实现模块化,我们应该在west/urls.py中设置URL访问对象。具体如下: 201 | 202 | 203 | 204 | 首先,修改mysite/urls.py: 205 | 206 | ```python 207 | from django.conf.urls import patterns, include, url 208 | 209 | from django.contrib import admin 210 | admin.autodiscover() 211 | 212 | urlpatterns = patterns('', 213 | # Examples: 214 | # url(r'^$', 'mysite.views.home', name='home'), 215 | # url(r'^blog/', include('blog.urls')), 216 | 217 | url(r'^admin/', include(admin.site.urls)), 218 | url(r'^$', 'mysite.views.first_page'), 219 | url(r'^west/', include('west.urls')), 220 | ) 221 | ``` 222 | 注意新增加的最后一行。这里,我们提醒指挥员,对于west/的访问,要参考west/urls.py。 223 | 224 | 225 | 226 | 随后,我们创建west/urls.py,添加内容: 227 | ```python 228 | from django.conf.urls import patterns, include, url 229 | 230 | urlpatterns = patterns('', 231 | url(r'^$', 'west.views.first_page'), 232 | ) 233 | ``` 234 | 将URL对应west下,views.py中的first_page函数。 235 | 236 | 237 | 238 | 最后,在west下,修改views.py为: 239 | ```python 240 | # -*- coding: utf-8 -*- 241 | 242 | from django.http import HttpResponse 243 | 244 | def first_page(request): 245 | return HttpResponse("

西餐

") 246 | ``` 247 | 访问http://127.0.0.1:8000/west,查看效果。 248 | 249 | 250 | 251 | “你们这些混蛋,我一个都不会放过!” 姜戈大吼。 252 | 253 | 254 | 255 | ##总结 256 | 257 | 可以看到,Django的创建过程非常简单。但这只是初次尝试Django。为了创建一个完整功能的网站,还需要调用Django许多其它的功能。 258 | 259 | 260 | 261 | 姜戈的解放,才刚刚开始。 262 | -------------------------------------------------------------------------------- /content/被解放的姜戈02 庄园疑云.md: -------------------------------------------------------------------------------- 1 | #被解放的姜戈02 庄园疑云 2 | 3 | 4 | 5 | 6 | 上一回说到,姜戈的江湖初体验:如何架设服务器,如何回复http请求,如何创建App。这一回,我们要走入糖果庄园。 7 | 8 | 9 | 10 | 数据库是一所大庄园,藏着各种宝贝。一个没有数据库的网站,所能提供的功能会非常有限。 11 | 12 | 13 | 14 | 15 | 16 | 为了找到心爱的人,姜戈决定一探这神秘的糖果庄园。 17 | 18 | 19 | 20 | ##连接数据库 21 | 22 | Django为多种数据库后台提供了统一的调用API。根据需求不同,Django可以选择不同的数据库后台。MySQL算是最常用的数据库。我们这里将Django和MySQL连接。 23 | 24 | 25 | 26 | 在Linux终端下启动mysql: 27 | 28 | $mysql -u root -p 29 | 30 | 31 | 在MySQL中创立Django项目的数据库: 32 | 33 | mysql> CREATE DATABASE villa DEFAULT CHARSET=utf8; 34 | 这里使用utf8作为默认字符集,以便支持中文。 35 | 36 | 37 | 38 | 在MySQL中为Django项目创立用户,并授予相关权限: 39 | 40 | mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES ON villa.* TO 'vamei'@'localhost' IDENTIFIED BY 'vameiisgood'; 41 | 42 | 43 | 在settings.py中,将DATABASES对象更改为: 44 | 45 | ```python 46 | DATABASES = { 47 | 'default': { 48 | 'ENGINE': 'django.db.backends.mysql', 49 | 'NAME': 'villa', 50 | 'USER': 'vamei', 51 | 'PASSWORD': 'vameiisgood', 52 | 'HOST':'localhost', 53 | 'PORT':'3306', 54 | } 55 | } 56 | ``` 57 | 后台类型为mysql。上面包含数据库名称和用户的信息,它们与MySQL中对应数据库和用户的设置相同。Django根据这一设置,与MySQL中相应的数据库和用户连接起来。此后,Django就可以在数据库中读写了。 58 | 59 | 60 | 61 | 姜戈略一迟疑,旋即走入了庄园的大门。 62 | 63 | 64 | 65 | ##创立模型 66 | 67 | MySQL是关系型数据库。但在Django的帮助下,我们不用直接编写SQL语句。Django将关系型的表(table)转换成为一个类(class)。而每个记录(record)是该类下的一个对象(object)。我们可以使用基于对象的方法,来操纵关系型的MySQL数据库。 68 | 69 | 70 | 71 | 在传统的MySQL中,数据模型是表。在Django下,一个表为一个类。表的每一列是该类的一个属性。在models.py中,我们创建一个只有一列的表,即只有一个属性的类: 72 | ```python 73 | from django.db import models 74 | 75 | class Character(models.Model): 76 | name = models.CharField(max_length=200) 77 | def __unicode__(self): 78 | return self.name 79 | ``` 80 | 类Character定义了数据模型,它需要继承自models.Model。在MySQL中,这个类实际上是一个表。表只有一列,为name。可以看到,name属性是字符类型,最大长度为200。 81 | 82 | 类Character有一个__unicode__()方法,用来说明对象的字符表达方式。如果是Python 3,定义__str__()方法,实现相同的功能。 83 | 84 | 85 | 86 | 命令Django同步数据库。Django根据models.py中描述的数据模型,在MySQL中真正的创建各个关系表: 87 | ```python 88 | $python manage.py syncdb 89 | ``` 90 | 91 | 同步数据库后,Django将建立相关的MySQL表格,并要求你创建一个超级用户: 92 | 93 | 94 | 95 | Creating tables ... 96 | Creating table django_admin_log 97 | Creating table auth_permission 98 | Creating table auth_group_permissions 99 | Creating table auth_group 100 | Creating table auth_user_groups 101 | Creating table auth_user_user_permissions 102 | Creating table auth_user 103 | Creating table django_content_type 104 | Creating table django_session 105 | Creating table west_character 106 | 107 | 108 | 109 | You just installed Django's auth system, which means you don't have any superusers defined. 110 | Would you like to create one now? (yes/no): yes 111 | Username (leave blank to use 'tommy'): vamei 112 | Email address: vamei@vamei.com 113 | Password: 114 | Password (again): 115 | Superuser created successfully. 116 | Installing custom SQL ... 117 | Installing indexes ... 118 | Installed 0 object(s) from 0 fixture(s) 119 | 120 | 121 | 122 | 数据模型建立了。打开MySQL命令行: 123 | ```python 124 | $mysql -u vamei -p 125 | ``` 126 | 127 | 查看数据模型: 128 | ```python 129 | USE villa; 130 | SHOW TABLES; 131 | SHOW COLUMNS FROM west_character; 132 | ``` 133 | 最后一个命令返回Character类的对应表格: 134 | 135 | +-------+--------------+------+-----+---------+----------------+ 136 | | Field | Type | Null | Key | Default | Extra | 137 | +-------+--------------+------+-----+---------+----------------+ 138 | | id | int(11) | NO | PRI | NULL | auto_increment | 139 | | name | varchar(200) | NO | | NULL | | 140 | +-------+--------------+------+-----+---------+----------------+ 141 | 2 rows in set (0.00 sec) 142 | 143 | 可以看到,Django还自动增加了一个id列,作为记录的主键(Primary Key)。 144 | 145 | 146 | 147 | 这富丽堂皇的别墅中,姜戈隐隐闻到凶险的味道。 148 | 149 | 150 | 151 | ##显示数据 152 | 153 | 数据模型虽然建立了,但还没有数据输入。为了简便,我们手动添加记录。打开MySQL命令行,并切换到相应数据库。添加记录: 154 | 155 | INSERT INTO west_character (name) Values ('Vamei'); 156 | INSERT INTO west_character (name) Values ('Django'); 157 | INSERT INTO west_character (name) Values ('John'); 158 | 查看记录: 159 | 160 | SELECT * FROM west_character; 161 | 162 | 可以看到,三个名字已经录入数据库。 163 | 164 | 165 | 166 | 下面我们从数据库中取出数据,并返回给http请求。在west/views.py中,添加视图。对于对应的请求,我们将从数据库中读取所有的记录,然后返回给客户端: 167 | 168 | ```python 169 | # -*- coding: utf-8 -*- 170 | 171 | from django.http import HttpResponse 172 | 173 | from west.models import Character 174 | 175 | def staff(request): 176 | staff_list = Character.objects.all() 177 | staff_str = map(str, staff_list) 178 | return HttpResponse("

" + ' '.join(staff_str) + "

") 179 | ``` 180 | 可以看到,我们从west.models中引入了Character类。通过操作该类,我们可以读取表格中的记录 181 | 182 | 183 | 184 | 为了让http请求能找到上面的程序,在west/urls.py增加url导航: 185 | ```python 186 | from django.conf.urls import patterns, include, url 187 | 188 | urlpatterns = patterns('', 189 | url(r'^staff/','west.views.staff'), 190 | ) 191 | ``` 192 | 193 | 运行服务器。在浏览器中输入URL: 194 | 195 | 127.0.0.1:8000/west/staff 196 | 197 | 查看效果: 198 | 199 | 200 | 201 | 202 | 203 | 从数据库读出数据,显示在页面 204 | 205 | 206 | 207 | “我心爱的人,原来你在这里。” 姜戈强自镇定,嘴角忍不住颤动。 208 | 209 | 210 | 211 | ##总结 212 | 213 | Django使用类和对象接口,来操纵底层的数据库。 214 | 215 | 有了数据库,就有了站点内容的大本营。 216 | 217 | 218 | 219 | 姜戈,风雨欲来。 220 | 221 | 222 | 223 | 欢迎阅读“被解放的姜戈”系列文章。 224 | -------------------------------------------------------------------------------- /content/被解放的姜戈03 所谓伊人.md: -------------------------------------------------------------------------------- 1 | #被解放的姜戈03 所谓伊人 2 | 3 | 4 | 5 | 6 | 在之前的程序中,我们直接生成一个字符串,作为http回复,返回给客户端。这一过程中使用了django.http.HttpResponse()。 7 | 8 | 在这样的一种回复生成过程中,我们实际上将数据和视图的格式混合了到上面的字符串中。看似方便,却为我们的管理带来困难。想像一个成熟的网站,其显示格式会有许多重复的地方。如果可以把数据和视图格式分离,就可以重复使用同一视图格式了。 9 | 10 | Django中自带的模板系统,可以将视图格式分离出来,作为模板使用。这样,不但视图可以容易修改,程序也会显得美观大方。 11 | 12 | 13 | 14 | “她是我心中最美的人”,姜戈对德国人说。 15 | 16 | 17 | 18 | ##模板初体验 19 | 20 | 我们拿一个独立的templay.html文件作为模板。它放在templates/west/文件夹下。文件系统的结构现在是: 21 | 22 | mysite/ 23 | ├── mysite 24 | ├── templates 25 | │ └── west 26 | └── west 27 | 28 | 29 | templay.html文件的内容是: 30 | 31 |

{{ label }}

32 | 可以看到,这个文件中,有一个奇怪的双括号包起来的陌生人。这就是我们未来数据要出现的地方。而相关的格式控制,即

标签,则已经标明在该模板文件中。 33 | 34 | 35 | 36 | 我们需要向Django说明模板文件的搜索路径,修改mysite/settings.py,添加: 37 | 38 | # Template dir 39 | TEMPLATE_DIRS = ( 40 | os.path.join(BASE_DIR, 'templates/west/'), 41 | ) 42 | 43 | 如果还有其它的路径用于存放模板,可以增加该元组中的元素,以便Django可以找到需要的模板。 44 | 45 | 46 | 47 | 我们现在修改west/views.py,增加一个新的对象,用于向模板提交数据: 48 | 49 | ```python 50 | # -*- coding: utf-8 -*- 51 | 52 | #from django.http import HttpResponse 53 | from django.shortcuts import render 54 | 55 | def templay(request): 56 | context = {} 57 | context['label'] = 'Hello World!' 58 | return render(request, 'templay.html', context) 59 | ``` 60 | 可以看到,我们这里使用render来替代之前使用的HttpResponse。render还使用了一个词典context作为参数。这就是我们的数据。 61 | 62 | context中元素的键值为'label',正对应刚才的“陌生人”的名字。这样,该context中的‘label’元素值,就会填上模板里的坑,构成一个完整的http回复。 63 | 64 | 65 | 66 | 作为上节内容的一个小练习,自行修改west/urls.py,让http://127.0.0.1:8000/west/templay的URL请求可以找到相应的view对象。 67 | 68 | 69 | 70 | 访问http://127.0.0.1:8000/west/templay,可以看到页面: 71 | 72 | 73 | 74 | “我给你讲个故事吧,勇士拯救公主的故事”,德国人说。 75 | 76 | 77 | 78 | ##流程 79 | 80 | 再来回顾一下整个流程。west/views.py中的templay()在返回时,将环境数据context传递给模板templay.html。Django根据context元素中的键值,将相应数据放入到模板中的对应位置,生成最终的http回复。 81 | 82 | 83 | 84 | 这一模板系统可以与Django的其它功能相互合作。上一回,我们从数据库中提取出了数据。如果将数据库中的数据放入到context中,那么就可以将数据库中的数据传送到模板。 85 | 86 | 修改上次的west/views.py中的staff: 87 | ```python 88 | def staff(request): 89 | staff_list = Character.objects.all() 90 | staff_str = map(str, staff_list) 91 | context = {'label': ' '.join(staff_str)} 92 | return render(request, 'templay.html', context) 93 | ``` 94 | 练习: 显示上面的staff页面。 95 | 96 | 97 | 98 | “勇士翻过高山,并非因为他不害怕。” 99 | 100 | 101 | 102 | ##循环与选择 103 | 104 | Django实际上提供了丰富的模板语言,可以在模板内部有限度的编程,从而更方便的编写视图和传送数据。 105 | 106 | 我们下面体验一下最常见的循环与选择。 107 | 108 | 109 | 110 | 上面的staff中的数据实际上是一个数据容器,有三个元素。刚才我们将三个元素连接成一个字符串传送。 111 | 112 | 实际上,利用模板语言,我们可以直接传送数据容器本身,再循环显示。修改staff()为: 113 | ```python 114 | def staff(request): 115 | staff_list = Character.objects.all() 116 | return render(request, 'templay.html', {'staffs': staff_list}) 117 | ``` 118 | 从数据库中查询到的三个对象都在staff_list中。我们直接将staff_list传送给模板。 119 | 120 | 将模板templay.html修改为: 121 | 122 | {% for item in staffs %} 123 |

{{ item.id }}, {{item}}

124 | {% endfor %} 125 | 我们以类似于Python中for循环的方式来定义模板中的for,以显示staffs中的每个元素。 126 | 127 | 还可以看到,对象.属性名的引用方式可以直接用于模板中。 128 | 129 | 130 | 131 | 选择结构也与Python类似。根据传送来的数据是否为True,Django选择是否显示。使用方式如下: 132 | 133 | ```python 134 | {% if condition1 %} 135 | ... display 1 136 | {% elif condiiton2 %} 137 | ... display 2 138 | {% else %} 139 | ... display 3 140 | {% endif %} 141 | ``` 142 | 其中的elif和else和Python中一样,是可以省略的。 143 | 144 | 145 | 146 | “勇士屠杀恶龙,并非因为他不恐惧。” 147 | 148 | 149 | 150 | 模板继承 151 | 152 | 模板可以用继承的方式来实现复用。我们下面用templay.html来继承base.html。这样,我们可以使用base.html的主体,只替换掉特定的部分。 153 | 154 | 新建templates/west/base.html: 155 | 156 | ```python 157 | 158 | 159 | templay 160 | 161 | 162 | 163 |

come from base.html

164 | {% block mainbody %} 165 |

original

166 | {% endblock %} 167 | 168 | 169 | ``` 170 | 该页面中,名为mainbody的block标签是可以被继承者们替换掉的部分。 171 | 172 | 173 | 174 | 我们在下面的templay.html中继承base.html,并替换特定block: 175 | 176 | ```python 177 | {% extends "base.html" %} 178 | 179 | {% block mainbody %} 180 | 181 | {% for item in staffs %} 182 |

{{ item.id }},{{ item.name }}

183 | {% endfor %} 184 | 185 | {% endblock %} 186 | ``` 187 | 第一句说明templay.html继承自base.html。可以看到,这里相同名字的block标签用以替换base.html的相应block。 188 | 189 | 190 | 191 | “勇士穿过地狱火焰,因为,她值得。” 192 | 193 | 194 | 195 | ##总结 196 | 197 | 使用模板实现视图分离。 198 | 199 | 数据传递,模板变量,模板循环与选择,模板继承。 200 | 201 | 202 | 203 | 姜戈静静的说,“我懂得他的感受。” 204 | -------------------------------------------------------------------------------- /content/被解放的姜戈04 各取所需.md: -------------------------------------------------------------------------------- 1 | #被解放的姜戈04 各取所需 2 | 3 | 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明。谢谢! 4 | 5 | 6 | 7 | 我们在庄园疑云中讲到了服务器上的数据。当时我们是用手动的方式,直接在数据库插入数据。我们将允许客户向服务器传递数据。 8 | 9 | 表格是客户向服务器传数据的经典方式。我们先会看到传统的表格提交,然后了解Django的表格对象。 10 | 11 | 12 | 13 | “我可不做赔本的买卖”,庄主对姜戈说。 14 | 15 | 16 | 17 | ##html表格 18 | 19 | HTTP协议以“请求-回复”的方式工作。客户发送请求时,可以在请求中附加数据。服务器通过解析请求,就可以获得客户传来的数据,并根据URL来提供特定的服务。 20 | 21 | (http协议的运作方式,详见http协议) 22 | 23 | 24 | 25 | HTML文件中可以包含表格标签。HTML表格的目的是帮助用户构成HTTP请求,把数据用GET或者POST的方法,传递给某一URL地址。下面是一个表格的例子: 26 | ```python 27 |
28 | 29 | 30 |
31 | ``` 32 | 这里的form标签有两个属性。action用于说明URL地址,method说明请求的方法。 33 | 34 | 表格中还包含有两个input标签,即两个输入栏目。根据type的不同,第一个为一个文本框,第二个为一个提交按钮。name为输入栏的名字。服务器在解析数据时,将以name为索引。 35 | 36 | 37 | 38 | 我们可以将上面的表格直接存入模板form.html,并在west/views.py中定义一个视图form()来显示表格: 39 | ```python 40 | from django.shortcuts import render 41 | 42 | def form(request): 43 | return render(request, 'form.html') 44 | ``` 45 | 设置urls.py,让对[site]/west/form/的访问,指向该视图。 46 | 47 | 48 | 49 | 最后,我们在west/views.py中定义investigate()来处理该表格提交的数据: 50 | ```python 51 | from django.shortcuts import render 52 | 53 | def investigate(request): 54 | rlt = request.GET['staff'] 55 | return HttpResponse(rlt) 56 | ``` 57 | 可以看到,HTTP请求的相关信息,包括请求的方法,提交的数据,都包含在request参数中。 58 | 59 | 表格是通过GET方法提交的。我们可以通过request.GET['staff'],来获得name为staff的输入栏的数据。该数据是一个字符串。investigate()将直接显示该字符串。 60 | 61 | 设置urls.py,让该处理函数对应action的URL([site]/west/investigate/)。 62 | 63 | 64 | 65 | 当我们访问http://127.0.0.1:8000/west/form时,将显示: 66 | 67 | 68 | 69 | 提交表格后,页面将转到[site]/west/investigate。investigate()读取字符串后,在页面上显示出来。 70 | 71 | 72 | 73 | 姜戈舔舔嘴唇,“这就是你最好的决斗士?我觉得它们不值。” 74 | 75 | 76 | 77 | ##POST方法 78 | 79 | 上面我们使用了GET方法。视图显示和请求处理分成两个函数处理。 80 | 81 | 提交数据时更常用POST方法。我们下面使用该方法,并用一个URL和处理函数,同时显示视图和处理请求。 82 | 83 | 84 | 85 | 先创建模板investigate.html 86 | 87 | ```python 88 |
89 | {% csrf_token %} 90 | 91 | 92 |
93 | 94 |

{{ rlt }}

95 | ``` 96 | 我们修改提交表格的方法为post。在模板的末尾,我们增加一个rlt记号,为表格处理结果预留位置。 97 | 98 | 表格后面还有一个{% csrf_token %}的标签。csrf全称是Cross Site Request Forgery。这是Django提供的防止伪装提交请求的功能。POST方法提交的表格,必须有此标签。 99 | 100 | 101 | 102 | 在west/views.py中,用investigate()来处理表格: 103 | 104 | ```python 105 | from django.shortcuts import render 106 | from django.core.context_processors import csrf 107 | 108 | def investigate(request): 109 | ctx ={} 110 | ctx.update(csrf(request)) 111 | if request.POST: 112 | ctx['rlt'] = request.POST['staff'] 113 | return render(request, "investigate.html", ctx) 114 | ``` 115 | 这里的csrf()是和上面的{% csrf_token %}对应。我们在这里不深究。 116 | 117 | 看程序的其它部分。对于该URL,可能有GET或者POST方法。if的语句有POST方法时,额外的处理,即提取表格中的数据到环境变量。 118 | 119 | 120 | 121 | 最终效果如下: 122 | 123 | 124 | 125 | “哦,是吗,我可是有更好的货色”,庄主似乎胸有成竹。 126 | 127 | 128 | 129 | ##存储数据 130 | 131 | 我们还可以让客户提交的数据存入数据库。使用庄园疑云中创建的模型。我们将客户提交的字符串存入模型Character。 132 | 133 | 134 | 135 | 修改west/views.py的investigate(): 136 | 137 | ```python 138 | from django.shortcuts import render 139 | from django.core.context_processors import csrf 140 | 141 | from west.models import Character 142 | 143 | def investigate(request): 144 | if request.POST: 145 | submitted = request.POST['staff'] 146 | new_record = Character(name = submitted) 147 | new_record.save() 148 | ctx ={} 149 | ctx.update(csrf(request)) 150 | all_records = Character.objects.all() 151 | ctx['staff'] = all_records 152 | return render(request, "investigate.html", ctx) 153 | ``` 154 | 在POST的处理部分,我们调用Character类创建新的对象,并让该对象的属性name等于用户提交的字符串。通过save()方法,我们让该记录入库。 155 | 156 | 随后,我们从数据库中读出所有的对象,并传递给模板。 157 | 158 | 159 | 160 | 我们还需要修改模板investigate.html,以更好的显示: 161 | 162 | ```python 163 |
164 | {% csrf_token %} 165 | 166 | 167 |
168 | 169 | {% for person in staff %} 170 |

{{ person }}

171 | {% endfor %} 172 | ``` 173 | 我们使用模板语言的for,来显示所有的记录。 174 | 175 | 176 | 177 | 效果如下: 178 | 179 | 180 | 181 | “他只是勉强够看罢了”,姜戈摇摇头,德国人也赶快跟着摇摇头。 182 | 183 | 184 | 185 | ##表格对象 186 | 187 | 客户提交数据后,服务器往往需要对数据做一些处理。比如检验数据,看是否符合预期的长度和数据类型。在必要的时候,还需要对数据进行转换,比如从字符串转换成整数。这些过程通常都相当的繁琐。 188 | 189 | Django提供的数据对象可以大大简化这一过程。该对象用于说明表格所预期的数据类型和其它的一些要求。这样Django在获得数据后,可以自动根据该表格对象的要求,对数据进行处理。 190 | 191 | 192 | 193 | 修改west/views.py: 194 | 195 | ```python 196 | from django.shortcuts import render 197 | from django.core.context_processors import csrf 198 | 199 | from west.models import Character 200 | 201 | from django import forms 202 | 203 | class CharacterForm(forms.Form): 204 | name = forms.CharField(max_length = 200) 205 | 206 | def investigate(request): 207 | if request.POST: 208 | form = CharacterForm(request.POST) 209 | if form.is_valid(): 210 | submitted = form.cleaned_data['name'] 211 | new_record = Character(name = submitted) 212 | new_record.save() 213 | 214 | form = CharacterForm() 215 | ctx ={} 216 | ctx.update(csrf(request)) 217 | all_records = Character.objects.all() 218 | ctx['staff'] = all_records 219 | ctx['form'] = form 220 | return render(request, "investigate.html", ctx) 221 | ``` 222 | 上面定义了CharacterForm类,并通过属性name,说明了输入栏name的类型为字符串,最大长度为200。 223 | 224 | 在investigate()函数中,我们根据POST,直接创立form对象。该对象可以直接判断输入是否有效,并对输入进行预处理。空白输入被视为无效。 225 | 226 | 后面,我们再次创建一个空的form对象,并将它交给模板显示。 227 | 228 | 229 | 230 | 在模板investigate.html中,我们可以直接显示form对象: 231 | 232 | ```python 233 |
234 | {% csrf_token %} 235 | {{ form.as_p }} 236 | 237 |
238 | 239 | {% for person in staff %} 240 |

{{ person }}

241 | {% endfor %} 242 | ``` 243 | 如果有多个输入栏,我们可以用相同的方式直接显示整个form,而不是加入许多个标签。 244 | 245 | 246 | 247 | 效果如下: 248 | 249 | 250 | 251 | 252 | 253 | 庄主看看德国人,再看看女仆,脸上露出狡猾的笑容。 254 | 255 | 256 | 257 | ##总结 258 | 259 | GET和POST 260 | 261 | 表格提交 262 | 263 | 数据库入库 264 | 265 | 表格对象 266 | 267 | 268 | 269 | “哈,那个德国人似乎看上了这个黑女仆呢”,庄主心里打着算盘。 270 | -------------------------------------------------------------------------------- /content/被解放的姜戈05 黑面管家.md: -------------------------------------------------------------------------------- 1 | #被解放的姜戈05 黑面管家 2 | 3 | 4 | 5 | 6 | Django提供一个管理数据库的app,即django.contrib.admin。这是Django最方便的功能之一。通过该app,我们可以直接经由web页面,来管理我们的数据库。这一工具,主要是为网站管理人员使用。 7 | 8 | 这个app通常已经预装好,你可以在mysite/settings.py中的INSTALLED_APPS看到它。 9 | 10 | 11 | 12 | “这庄园里的事情,都逃不过我的眼睛”,管家放下账本,洋洋得意。 13 | 14 | 15 | 16 | ##默认界面 17 | 18 | admin界面位于[site]/admin这个URL。这通常在mysite/urls.py中已经设置好。比如,下面是我的urls.py: 19 | 20 | ```python 21 | from django.conf.urls import patterns, include, url 22 | 23 | from django.contrib import admin 24 | 25 | admin.autodiscover() # admin 26 | 27 | urlpatterns = patterns('', 28 | url(r'^admin/', include(admin.site.urls)), # admin 29 | url(r'^west/', include('west.urls')), 30 | ) 31 | ``` 32 | 33 | 34 | 为了让admin界面管理某个数据模型,我们需要先注册该数据模型到admin。比如,我们之前在west中创建的模型Character。修改west/admin.py: 35 | 36 | from django.contrib import admin 37 | from west.models import Character 38 | 39 | # Register your models here. 40 | admin.site.register(Character) 41 | 42 | 43 | 访问http://127.0.0.1:8000/admin,登录后,可以看到管理界面: 44 | 45 | 46 | 47 | 这个页面除了west.characters外,还有用户和组信息。它们来自Django预装的Auth模块。我们将在以后处理用户管理的问题。 48 | 49 | 50 | 51 | “我已经管理这个庄园几十年了。” 52 | 53 | 54 | 55 | ##复杂模型 56 | 57 | 管理页面的功能强大,完全有能力处理更加复杂的数据模型。 58 | 59 | 60 | 61 | 先在west/models.py中增加一个更复杂的数据模型: 62 | 63 | ```python 64 | from django.db import models 65 | 66 | # Create your models here. 67 | class Contact(models.Model): 68 | name = models.CharField(max_length=200) 69 | age = models.IntegerField(default=0) 70 | email = models.EmailField() 71 | def __unicode__(self): 72 | return self.name 73 | 74 | class Tag(models.Model): 75 | contact = models.ForeignKey(Contact) 76 | name = models.CharField(max_length=50) 77 | def __unicode__(self): 78 | return self.name 79 | ``` 80 | 这里有两个表。Tag以Contact为外部键。一个Contact可以对应多个Tag。 81 | 82 | 我们还可以看到许多在之前没有见过的属性类型,比如IntegerField用于存储整数。 83 | 84 | 85 | 86 | 同步数据库: 87 | 88 | $python manage.py syncdb 89 | 90 | 91 | 在west/admin.py注册多个模型并显示: 92 | ```python 93 | from django.contrib import admin 94 | from west.models import Character,Contact,Tag 95 | 96 | # Register your models here. 97 | admin.site.register([Character, Contact, Tag]) 98 | ``` 99 | 100 | 模型将在管理页面显示。比如Contact的添加条目的页面如下: 101 | 102 | 103 | 104 | “这些黑鬼在想什么,我一清二楚。” 105 | 106 | 107 | 108 | ##自定义页面 109 | 110 | 我们可以自定义管理页面,来取代默认的页面。比如上面的"add"页面。我们想只显示name和email部分。修改west/admin.py: 111 | 112 | ```python 113 | from django.contrib import admin 114 | from west.models import Character,Contact,Tag 115 | 116 | # Register your models here. 117 | class ContactAdmin(admin.ModelAdmin): 118 | fields = ('name', 'email') 119 | 120 | admin.site.register(Contact, ContactAdmin) 121 | admin.site.register([Character, Tag]) 122 | ``` 123 | 124 | 125 | 上面定义了一个ContactAdmin类,用以说明管理页面的显示格式。里面的fields属性,用以说明要显示的输入栏。我们没有让"age"显示。由于该类对应的是Contact数据模型,我们在注册的时候,需要将它们一起注册。显示效果如下: 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 我们还可以将输入栏分块,给每一块输入栏以自己的显示格式。修改west/admin.py为: 134 | 135 | ```python 136 | from django.contrib import admin 137 | from west.models import Character,Contact,Tag 138 | 139 | # Register your models here. 140 | class ContactAdmin(admin.ModelAdmin): 141 | fieldsets = ( 142 | ['Main',{ 143 | 'fields':('name','email'), 144 | }], 145 | ['Advance',{ 146 | 'classes': ('collapse',), # CSS 147 | 'fields': ('age',), 148 | }] 149 | ) 150 | 151 | admin.site.register(Contact, ContactAdmin) 152 | admin.site.register([Character, Tag]) 153 | ``` 154 | 155 | 156 | 上面的栏目分为了Main和Advance两部分。classes说明它所在的部分的CSS格式。这里让Advance部分收敛起来: 157 | 158 | 159 | 160 | Advance部分旁边有一个Show按钮,用于展开。 161 | 162 | 163 | 164 | “这两个客人,似乎没有那么简单。” 165 | 166 | 167 | 168 | ##Inline显示 169 | 170 | 上面的Contact是Tag的外部键,所以有外部参考的关系。而在默认的页面显示中,将两者分离开来,无法体现出两者的从属关系。我们可以使用Inline显示,让Tag附加在Contact的编辑页面上显示。 171 | 172 | 173 | 174 | 修改west/admin.py: 175 | 176 | ```python 177 | from django.contrib import admin 178 | from west.models import Character,Contact,Tag 179 | 180 | # Register your models here. 181 | class TagInline(admin.TabularInline): 182 | model = Tag 183 | 184 | class ContactAdmin(admin.ModelAdmin): 185 | inlines = [TagInline] # Inline 186 | fieldsets = ( 187 | ['Main',{ 188 | 'fields':('name','email'), 189 | }], 190 | ['Advance',{ 191 | 'classes': ('collapse',), 192 | 'fields': ('age',), 193 | }] 194 | 195 | ) 196 | 197 | admin.site.register(Contact, ContactAdmin) 198 | admin.site.register([Character]) 199 | ``` 200 | 201 | 202 | 效果如下: 203 | 204 | 205 | 206 | 207 | 208 | “但我也不是好惹的。” 209 | 210 | 211 | 212 | ##列表页的显示 213 | 214 | 在Contact输入数条记录后,Contact的列表页看起来如下: 215 | 216 | 217 | 218 | 219 | 220 | 我们也可以自定义该页面的显示,比如在列表中显示更多的栏目,只需要在ContactAdmin中增加list_display属性: 221 | 222 | ```python 223 | from django.contrib import admin 224 | from west.models import Character,Contact,Tag 225 | 226 | # Register your models here. 227 | class ContactAdmin(admin.ModelAdmin): 228 | list_display = ('name','age', 'email') # list 229 | 230 | admin.site.register(Contact, ContactAdmin) 231 | admin.site.register([Character, Tag]) 232 | ``` 233 | 234 | 235 | 列表页新的显示效果如下: 236 | 237 | 238 | 239 | 240 | 241 | 我们还可以为该列表页增加搜索栏。搜索功能在管理大量记录时非常有用。使用search_fields说明要搜索的属性: 242 | 243 | ```python 244 | from django.contrib import admin 245 | from west.models import Character,Contact,Tag 246 | 247 | # Register your models here. 248 | class ContactAdmin(admin.ModelAdmin): 249 | list_display = ('name','age', 'email') 250 | search_fields = ('name',) 251 | 252 | admin.site.register(Contact, ContactAdmin) 253 | admin.site.register([Character]) 254 | ``` 255 | 256 | 257 | 效果如下: 258 | 259 | 260 | 261 | “我要替小主人留心了。” 262 | 263 | 264 | 265 | ##总结 266 | 267 | Django的管理页面有很丰富的数据库管理功能,并可以自定义显示方式,是非常值得使用的工具。 268 | 269 | 270 | 271 | “谁,也逃不出我的眼睛!” 272 | -------------------------------------------------------------------------------- /content/被解放的姜戈06 假作真时.md: -------------------------------------------------------------------------------- 1 | #被解放的姜戈06 假作真时 2 | 3 | 4 | 5 | 6 | 之前了解了: 7 | 8 | 创建Django项目 9 | 数据库 10 | 模板 11 | 表格提交 12 | admin管理页面 13 | 上面的功能模块允许我们做出一个具有互动性的站点,但无法验证用户的身份。我们这次了解用户验证部分。通过用户验证,我们可以根据用户的身份,提供不同的服务。 14 | 15 | 16 | 17 | 一个Web应用的用户验证是它的基本组成部分。我们在使用一个应用时,总是从“登录”开始,到“登出”结束。另一方面,用户验证又和网站安全、数据库安全息息相关。HTTP协议是无状态的,但我们可以利用储存在客户端的cookie或者储存在服务器的session来记录用户的访问。 18 | 19 | Django有管理用户的模块,即django.contrib.auth。你可以在mysite/settings.py里看到,这个功能模块已经注册在INSTALLED_APPS中。利用该模块,你可以直接在逻辑层面管理用户,不需要为用户建立模型,也不需要手工去实现会话。 20 | 21 | “为了救你的爱人出来,我们要演一场戏。” 22 | 23 | 24 | 25 | ##创建用户 26 | 27 | 你可以在admin页面直接看到用户管理的对话框,即Users。从这里,你可以在这里创建、删除和修改用户。点击Add增加用户daddy,密码为daddyiscool。 28 | 29 | 在admin页面下,我们还可以控制不同用户组对数据库的访问权限。我们可以在Groups中增加用户组,设置用户组对数据库的访问权限,并将用户加入到某个用户组中。 30 | 31 | 在这一章节中,我们创立一个新的app,即users。下文的模板和views.py,都针对该app。 32 | 33 | 34 | 35 | "你这套新衣服,还真像那么回事",德国人说。 36 | 37 | 38 | 39 | 用户登录 40 | 41 | 我们建立一个简单的表格。用户通过该表格来提交登陆信息,并在Django服务器上验证。如果用户名和密码正确,那么登入用户。 42 | 43 | 我们首先增加一个登录表格: 44 | 45 | ```python 46 |
47 | 48 | 49 | 50 | 51 | 52 |
53 | ``` 54 | 55 | 56 | 我们在views.py中,定义处理函数user_login(),来登入用户: 57 | 58 | ```python 59 | # -*- coding: utf-8 -*- 60 | from django.shortcuts import render, redirect 61 | from django.core.context_processors import csrf 62 | from django.contrib.auth import * 63 | 64 | def user_login(request): 65 | ''' 66 | login 67 | ''' 68 | if request.POST: 69 | username = password = '' 70 | username = request.POST.get('username') 71 | password = request.POST.get('password') 72 | user = authenticate(username=username, password=password) 73 | if user is not None and user.is_active: 74 | login(request, user) 75 | return redirect('/') 76 | ctx = {} 77 | ctx.update(csrf(request)) 78 | return render(request, 'login.html',ctx) 79 | ``` 80 | 上面的authenticate()函数,可以根据用户名和密码,验证用户信息。而login()函数则将用户登入。它们来自于django.contrib.auth。 81 | 82 | 83 | 84 | 作为替换,我们可以使用特别的form对象,而不自行定义表格。这将让代码更简单,而且提供一定的完整性检验。 85 | 86 | 练习. 使用contrib.auth.forms.AuthenticationForm。来简化上面的模板和处理函数。 87 | 88 | 89 | 90 | 德国人还是不忘一再叮嘱,"记住,我们可不是什么赏金猎人。" 91 | 92 | 93 | 94 | ##登出 95 | 96 | 有时用户希望能销毁会话。我们可以提供一个登出的URL,即/users/logout。登入用户访问该URL,即可登出。在views.py中,增加该URL的处理函数: 97 | 98 | ```python 99 | # -*- coding: utf-8 -*- 100 | from django.shortcuts import redirect 101 | 102 | def user_logout(request): 103 | ''' 104 | logout 105 | URL: /users/logout 106 | ''' 107 | logout(request) 108 | return redirect('/') 109 | ``` 110 | 我们修改urls.py,让url对应user_logout()。访问http://127.0.0.1/users/logout,就可以登出用户。 111 | 112 | 113 | 114 | 德国人压低声音,“哦,我是来救你的,我们要演一出戏。” 115 | 116 | 117 | 118 | ##views.py中的用户 119 | 120 | 上面说明了如何登入和登出用户,但还没有真正开始享受用户验证带来的好处。用户登陆的最终目的,就是为了让服务器可以区别对待不同的用户。比如说,有些内容只能让登陆用户看到,有些内容则只能让特定登陆用户看到。我们下面将探索如何实现这些效果。 121 | 122 | 123 | 124 | 在Django中,对用户身份的检验,主要是在views.py中进行。views.py是连接模型和视图的中间层。HTTP请求会转给views.py中的对应处理函数处理,并发回回复。在views.py的某个处理函数准备HTTP回复的过程中,我们可以检验用户是否登陆。根据用户是否登陆,我们可以给出不同的回复。最原始的方式,是使用if式的选择结构: 125 | 126 | ```python 127 | # -*- coding: utf-8 -*- 128 | from django.http import HttpResponse 129 | 130 | def diff_response(request): 131 | if request.user.is_authenticated(): 132 | content = "

my dear user

" 133 | else: 134 | content = "

you wired stranger

" 135 | return HttpResponse(content) 136 | ``` 137 | 138 | 139 | 可以看到,用户的登录信息包含在request.user中,is_authenticated()方法用于判断用户是否登录,如果用户没有登录,那么该方法将返回false。该user对象属于contrib.auth.user类型,还有其它属性可供使用,比如 140 | 141 | 属性 功能 142 | get_username() 返回用户名 143 | set_password() 设置密码 144 | get_fullname() 返回姓名 145 | last_login 上次登录时间 146 | date_joined   账户创建时间 147 | 练习. 实验上面的处理函数的效果。 148 | 149 | 150 | 151 | 在Django中,我们还可以利用装饰器,根据用户的登录状况,来决定views.py中处理函数的显示效果。相对于上面的if结构,装饰器使用起来更加方便。下面的user_only()是views.py中的一个处理函数。 152 | 153 | from django.contrib.auth.decorators import login_required 154 | from django.http import HttpResponse 155 | 156 | @login_required 157 | def user_only(request): 158 | return HttpResponse("

This message is for logged in user only.

") 159 | 注意上面的装饰器login_required,它是Django预设的装饰器。user_only()的回复结果只能被登录用户看到,而未登录用户将被引导到其他页面。 160 | 161 | 162 | 163 | Django中还有其它的装饰器,用于修饰处理函数。相应的http回复,只能被特殊的用户看到。比如user_passes_test,允许的用户必须满足特定标准,而这一标准是可以用户自定义的。比如下面,在views.py中增添: 164 | 165 | ```python 166 | from django.contrib.auth.decorators import user_passes_test 167 | from django.http import HttpResponse 168 | def name_check(user): 169 | return user.get_username() == 'vamei' 170 | 171 | @user_passes_test(name_check) 172 | def specific_user(request): 173 | return HttpResponse("

for Vamei only

") 174 | ``` 175 | 176 | 177 | 装饰器带有一个参数,该参数是一个函数对象name_check。当name_check返回真值,即用户名为vamei时,specific_user的结果才能被用户看到。 178 | 179 | 180 | 181 | 德国人羞涩的笑笑,“我确实对她有那么点好感。” 182 | 183 | 184 | 185 | ##模板中的用户 186 | 187 | 进一步,用户是否登陆这一信息,也可以直接用于模板。比较原始的方式是把用户信息直接作为环境数据,提交给模板。然而,这并不是必须的。事实上,Django为此提供了捷径:我们可以直接在模板中调用用户信息。比如下面的模板: 188 | 189 | {% if user.is_authenticated %} 190 |

Welcome, my genuine user, my true love.

191 | {% else %} 192 |

Sorry, not login, you are not yet my sweetheart.

193 | {% endif %} 194 | 不需要环境变量中定义,我们就可以直接在模板中引用user。这里,模板中调用了user的一个方法,is_authenticated,将根据用户的登录情况,返回真假值。需要注意,和正常的Python程序不同,在Django模板中调用方法并不需要后面的括号。 195 | 196 | 197 | 198 | 练习. 增加处理函数,显示该模板,然后查看不同登录情况下的显示结果。 199 | 200 | 201 | 202 | 管家冷不丁的说,“你认识他们?!” 203 | 204 | 205 | 206 | ##用户注册 207 | 208 | 我们上面利用了admin管理页面来增加和删除用户。这是一种简便的方法,但并不能用于一般的用户注册的情境。我们需要提供让用户自主注册的功能。这可以让站外用户提交自己的信息,生成自己的账户,并开始作为登陆用户使用网站。 209 | 210 | 用户注册的基本原理非常简单,即建立一个提交用户信息的表格。表格中至少包括用户名和密码。相应的处理函数提取到这些信息后,建立User对象,并存入到数据库中。 211 | 212 | 我们可以利用Django中的UserCreationForm,比较简洁的生成表格,并在views.py中处理表格: 213 | 214 | ```python 215 | from django.contrib.auth.forms import UserCreationForm 216 | from django.shortcuts import render, redirect 217 | from django.core.context_processors import csrf 218 | 219 | def register(request): 220 | if request.method == 'POST': 221 | form = UserCreationForm(request.POST) 222 | if form.is_valid(): 223 | new_user = form.save() 224 | return redirect("/") 225 | else: 226 | form = UserCreationForm() 227 | ctx = {'form': form} 228 | ctx.update(csrf(request)) 229 | return render(request, "register.html", ctx) 230 | ``` 231 | 232 | 233 | 相应的模板register.html如下: 234 | 235 |
236 | {% csrf_token %} 237 | {{ form.as_p }} 238 | 239 |
240 | 241 | 242 | “骗子,你们这些骗子”,庄园主怒吼着。 243 | 244 | 245 | 246 | ##总结 247 | 248 | 正如我们上面提到的,用户登陆系统的最大功能是区分登入和未登入用户,向他们提供不同的内容和服务。 249 | 250 | 我们看到了用户验证的基本流程,也看到了如何在views.py和模板中区分用户。 251 | 252 | 253 | 254 | 两杆枪,一支指着德国人,一支指着姜戈。 255 | -------------------------------------------------------------------------------- /content/被解放的姜戈07 马不停蹄.md: -------------------------------------------------------------------------------- 1 | #被解放的姜戈07 马不停蹄 2 | 3 | 4 | 5 | 6 | 前面的文章研究了Django最主要的几个方面:数据库,模板,动态生成页面等。但都是使用python manage.py runserver来运行服务器。这是一个实验性的web服务器,不适用于正常的站点运行。我们需要一个可以稳定而持续的服务器。这个服务器负责监听http端口,将收到的请求交给Django处理,将Django的回复发还给客户端。 7 | 8 | 这样的持续性服务器可以有很多选择,比如apache, Nginx, lighttpd等。这里将使用最常见的apache服务器。服务器和Django之间通过Python的web服务接口WSGI连接,因此我们同样需要apache下的mod_wsgi模块。 9 | 10 | 下面的配置和说明,是在Ubuntu 13.10下进行的。在其它系统下将有所差别。 11 | 12 | 13 | 14 | 15 | 16 | 姜戈大喝一声,掉转马头狂奔。 17 | 18 | 19 | 20 | ##安装 21 | 22 | 首先需要安装apache2和mod_wsgi。在ubuntu下,我们可以使用apt-get安装: 23 | 24 | sudo apt-get install apache2 25 | sudo apt-get install libapache2-mod-wsgi 26 | mod_wsgi也可以在google code下载,自行编译安装。 27 | 28 | 29 | 30 | 在apache的配置文件/etc/apache2/apache2.conf中增加下面的配置: 31 | 32 | ```python 33 | # Django 34 | WSGIScriptAlias / /home/vamei/mysite/mysite/wsgi.py 35 | WSGIPythonPath /home/vamei/mysite 36 | 37 | 38 | 39 | Order deny,allow 40 | Require all granted 41 | 42 | 43 | ``` 44 | 上面的配置中/home/ubuntu/mysite是Django项目所在的位置。而/home/ubuntu/mysite/mysite/wsgi.py是Django项目中z自动创建的文件。 45 | 46 | 可以看到,利用WSGIScriptAlias,我们实际上将URL /对应了wsgi接口程序。这样,当我们访问根URL时,访问请求会经由WSGI接口,传递给Django项目mysite。 47 | 48 | 49 | 50 | 配置好后,重启apache2 51 | 52 | sudo /etc/init.d/apache2 restart 53 | 54 | 55 | 使用浏览器,可以检查效果: 56 | 57 | 58 | 59 | 60 | 61 | 想起逝去的德国人,姜戈心中一痛。 62 | 63 | 64 | 65 | ##静态文件 66 | 67 | Django的主要功能是动态的生成HTTP回复。很多媒体文件是静态存储的,如.js文件,.css文件和图片文件。这些文件变动的频率较小。我们希望静态的提供这些文件,而不是动态的生成。这样既可以减小服务器的负担,也便于在浏览器缓存,提高用户体验。 68 | 69 | 70 | 71 | 我们可以在apache2.conf中添加如下配置: 72 | 73 | ```python 74 | Alias /media/ /home/vamei/media/ 75 | Alias /static/ /home/vamei/static/ 76 | 77 | 78 | Order deny,allow 79 | Require all granted 80 | 81 | 82 | 83 | Order deny,allow 84 | Require all granted 85 | 86 | 87 | # Django 88 | WSGIScriptAlias / /home/vamei/mysite/mysite/wsgi.py 89 | WSGIPythonPath /home/vamei/mysite 90 | 91 | 92 | 93 | Order deny,allow 94 | Require all granted 95 | 96 | 97 | ``` 98 | 这样,/static/和/media/这两个URL的访问将引导向存有静态文件的/home/vamei/static/和/home/vamei/media/,apache将直接向客户提供这两个文件夹中的静态文件。而剩下的URL访问,将导向WSGI接口,由Django动态处理。 99 | 100 | 101 | 102 | 在/home/vamei/static/中放入文件revenge.jpg,访问http://localhost/static/revenge: 103 | 104 | 105 | 106 | 107 | 108 | 姜戈想到爱人身陷囹圄,忧伤顿时化为愤怒。 109 | 110 | 111 | 112 | ##其它 113 | 114 | 云平台或者服务器的部署是一个大的课题,这里无法深入到所有的细节。幸运的是,在网上有丰富的资料。你可以根据自己的平台和问题,搜索相应的资料。 115 | 116 | 在Django的debug模式下,我们可以在app文件夹中建立static目录,放入静态文件。Django将自动搜索到其中的静态文件。但这一方法有很大的安全隐患,只适用于开发。 117 | 118 | 119 | 120 | 马蹄声疾,电光火石之间,姜戈已经把护园家丁撂倒在地。 121 | 122 | 123 | 124 | ##总结 125 | 126 | apache + mod_wsgi 127 | 128 | 静态文件 129 | 130 | 131 | 132 | “你在外面等我,我要跟他们算总帐”,姜戈对爱人说。 133 | -------------------------------------------------------------------------------- /内容/Python标准库——走马观花: -------------------------------------------------------------------------------- 1 | #Python标准库——走马观花 2 | 3 | 4 | 5 | 6 | 7 | Python的一大好处在于它有一套很有用的标准库(standard library)。标准库是随着Python一起安装在你的电脑中的,是Python的一部分 (当然也有特殊情况。有些场合会因为系统安全性的要求,不使用全部的标准库,比如说Google App Engine)。 8 | 9 | 10 | 11 | 利用已有的类(class)和函数(function)进行开发,可以省去你从头写所有程序的苦恼。这些标准库就是盖房子已经烧好的砖,要比你自己去烧砖来得便捷得多。 12 | 13 | 14 | 15 | 我将根据我个人的使用经验中,先挑选出标准库下面三个方面的包(package)介绍,以说明标准库的强大功能: 16 | 17 | Python增强 18 | 19 | 系统互动 20 | 21 | 网络 22 | 23 | 24 | 25 | ##第一类:Python增强 26 | 27 | Python自身的已有的一些功能可以随着标准库的使用而得到增强。 28 | 29 | 1) 文字处理 30 | 31 | Python的string类提供了对字符串进行处理的方法。但Python并不止步于此。通过标准库中的re包,Python实现了对正则表达式(regular expression)的支持。Python的正则表达式可以和Perl以及Linux bash的正则表达相媲美。 32 | 33 | (正则表达式通过自定义的模板在文本中搜索或替换符合该模板的字符串。比如你可以搜索一个文本中所有的数字。正则表达式的关键在于根据自己的需要构成模板。) 34 | 35 | 此外,Python标准库还为字符串的输出提供更加丰富的格式, 比如: string包,textwrap包。 36 | 37 | 38 | 39 | 2) 数据对象 40 | 41 | 我们之前的快速教程介绍了表(list), 字典(dictionary)等数据对象。它们各自有不同的特征,适用于不同场合的对数据的组织和管理。Python的标准库定义了更多的数据对象,比如说数组(array),队列(Queue)。这些数据对象也分别有各自的特点和功能。一个熟悉数据结构(data structure)的Python用户可以在这些包中找到自己需要的数据结构。 42 | 43 | 此外,我们也会经常使用copy包,以复制对象。 44 | 45 | 46 | 47 | 3) 日期和时间 48 | 49 | 日期和时间的管理并不复杂,但容易犯错。Python的标准库中对日期和时间的管理颇为完善(利用time包管理时间,利用datetime包管理日期和时间),你不仅可以进行日期时间的查询和变换(比如:2012年7月18日对应的是星期几),还可以对日期时间进行运算(比如2000.1.1 13:00的378小时之后是什么日期,什么时间)。通过这些标准库,还可以根据需要控制日期时间输出的文本格式(比如:输出’2012-7-18‘还是'18 Jul 2012') 50 | 51 | 52 | 53 | 4) 数学运算 54 | 55 | 标准库中,Python定义了一些新的数字类型(decimal包, fractions包), 以弥补之前的数字类型(integer, float)可能的不足。标准库还包含了random包,用于处理随机数相关的功能(产生随机数,随机取样等)。math包补充了一些重要的数学常数和数学函数,比如pi,三角函数等等。 56 | 57 | (尽管numpy并不是标准库中的包,但它的数组运算的良好支持,让它在基于Python的科研和计算方面得到相当广泛的应用,可以适当关注。) 58 | 59 | 60 | 61 | 5) 存储 62 | 63 | 之前我们的快速教程中,只提及了文本的输入和输出。实际上,Python可以输入或输出任意的对象。这些对象可以通过标准库中的pickle包转换成为二进制格式(binary),然后存储于文件之中,也可以反向从二进制文件中读取对象。 64 | 65 | 此外,标准库中还支持基本的数据库功能(sqlite3包)。XML和csv格式的文件也有相应的处理包。 66 | 67 |    68 | 69 | ##第二类:系统互动 70 | 71 | 系统互动,主要指Python和操作系统(operate system)、文件系统(file system)的互动。Python可以实现一个操作系统的许多功能。它能够像bash脚本那样管理操作系统,这也是Python有时被成为脚本语言的原因。 72 | 73 | 74 | 75 | 1) Python运行控制 76 | 77 | sys包被用于管理Python自身的运行环境。Python是一个解释器(interpreter), 也是一个运行在操作系统上的程序。我们可以用sys包来控制这一程序运行的许多参数,比如说Python运行所能占据的内存和CPU, Python所要扫描的路径等。另一个重要功能是和Python自己的命令行互动,从命令行读取命令和参数。 78 | 79 | 80 | 81 | 2) 操作系统 82 | 83 | 如果说Python构成了一个小的世界,那么操作系统就是包围这个小世界的大世界。Python与操作系统的互动可以让Python在自己的小世界里管理整个大世界。 84 | 85 | os包是Python与操作系统的接口。我们可以用os包来实现操作系统的许多功能,比如管理系统进程,改变当前路径(相当于’cd‘),改变文件权限等,建立。但要注意,os包是建立在操作系统的平台上的,许多功能在Windows系统上是无法实现的。另外,在使用os包中,要注意其中的有些功能已经被其他的包取代。 86 | 87 | 我们通过文件系统来管理磁盘上储存的文件。查找、删除,复制文件,以及列出文件列表等都是常见的文件操作。这些功能经常可以在操作系统中看到(比如ls, mv, cp等Linux命令),但现在可以通过Python标准库中的glob包、shutil包、os.path包、以及os包的一些函数等,在Python内部实现。 88 | 89 | subprocess包被用于执行外部命令,其功能相当于我们在操作系统的命令行中输入命令以执行,比如常见的系统命令'ls'或者'cd',还可以是任意可以在命令行中执行的程序。 90 | 91 | 92 | 93 | 4) 线程与进程 94 | 95 | Python支持多线程(threading包)运行和多进程(multiprocessing包)运行。通过多线程和多进程,可以提高系统资源的利用率,提高计算机的处理速度。Python在这些包中,附带有相关的通信和内存管理工具。此外,Python还支持类似于UNIX的signal系统,以实现进程之间的粗糙的信号通信。 96 | 97 | 98 | 99 | ##第三类:网络 100 | 101 | 现在,网络功能的强弱很大程度上决定了一个语言的成功与否。从Ruby, JavaScript, php身上都可以感受到这一点。Python的标准库对互联网开发的支持并不充分,这也是Django等基于Python的项目的出发点: 增强Python在网络方面的应用功能。这些项目取得了很大的成功,也是许多人愿意来学习Python的一大原因。但应注意到,这些基于Python的项目也是建立在Python标准库的基础上的。 102 | 103 | 104 | 105 | 1) 基于socket层的网络应用 106 | 107 | socket是网络可编程部分的底层。通过socket包,我们可以直接管理socket,比如说将socket赋予给某个端口(port),连接远程端口,以及通过连接传输数据。我们也可以利用SocketServer包更方便地建立服务器。 108 | 109 | 通过与多线程和多进程配合,建立多线程或者多进程的服务器,可以有效提高服务器的工作能力。此外,通过asyncore包实现异步处理,也是改善服务器性能的一个方案。 110 | 111 | 112 | 113 | 2) 互联网应用 114 | 115 | 在实际应用中,网络的很多底层细节(比如socket)都是被高层的协议隐藏起来的。建立在socket之上的http协议实际上更容易也更经常被使用。http通过request/responce的模式建立连接并进行通信,其信息内容也更容易理解。Python标准库中有http的服务器端和客户端的应用支持(BaseHTTPServer包; urllib包, urllib2包), 并且可以通过urlparse包对URL(URL实际上说明了网络资源所在的位置)进行理解和操作。 116 | 117 | 118 | 119 | 以上的介绍比较粗糙,只希望能为大家提供一个了解标准库的入口。欢迎大家一起分享标准库的使用经验。 120 | --------------------------------------------------------------------------------