最近心血来潮想了解下Python,反正是兴趣使然的学习,对它的好感比Node.js还高一些。这里记录一些流水账,算是个学习笔记。

学习资料:廖雪峰Python教程

再此由衷感谢各位大大的分享。

关于Python

Python是一种想当高级的语言,这意味着他已经自带了许多功能,编写一个任务所需要的代码量大大减少,相应的,运行速度相比C和java就要慢一些。但其实对用户来说没什么区别。
另外,Python代码无法加密,如果发布了你的Python程序,实际上就是发布源码,相当于被动开源。但是其实没人会看你的辣鸡代码的。

安装Python

Windows上下载安装Python时记得勾选  Add Python 3.5 to PATH即可。
Python有许多不同的解释器,我们这里安装好之后会自带官方的解释器CPython。

Hello Python

在Windows命令行下输入python进入`Python`交互模式。交互模式下可以直接执行Python语句。
在Windows命令行下输入python + 文件名.py 可以直接执行python文件。

也可以使用gitbush来运行,打开gitbush,输入`winpty py`进入python交互模式即可执行python语句。
在gitbush中,输入python + 文件名.py  也可以直接执行python文件。
也可以在Sublime Text中编辑后缀为 .py 的Python文件。然后在Windows中,切换到该文件所在目录,输入 `python 文件名.py` 来执行Python文件。 
实际编写Python代码的时候可以尝试一边开始编辑器写,一边在命令行的Python交互模式中验证你的语句。
输出:`print('my name is', name)`
输入:`input('input your name')`
python声明变量直接写变量名:`name = input('input your name')`
**注意input()返回的数据类型为str,如果需要数字,要调用改变数据类型,如int()**

Python基础

Python采用缩进的语法,一眼看上去有点蛋疼,感觉还是js的分号和大括号稳妥一些。这里应该始终使用**4个空格**的缩进,在Sublime Text中,右下角可以很方便的调整一个Tab Size,设置为4个缩进就可以直接用Tab了。缩进还有个坏处就是‘复制-粘贴’的时候要格外注意。
另外,Python使用#开头作为注释,Sublime Text快捷键就行了。
**Python和javaScript一样是大小写敏感的**

数据类型和变量

Python中常用的整数,浮点数数据类型跟js基本一致。字符串类型中,也可以使用`\`来进行转义,`\n`表示换行,`\t`表示制表符`\`也可以转义`\`字符本身。
若一个字符串中有多个字符都要转义,可以简化为`r''`表示,`''`中的字符串默认不转义。
若一个字符串内部有很多换行,用`\n`写在一行不好阅读,可以用`'''...'''`表示多行内容。这种方式也可以前边加上`r`
Python中的布尔值,为`True`和`False`,并且首字母必须大写否则报错。布尔值可以用`and`,`or`,`not`来进行与或非运算。
Python中还有空值`None`。
此外Python还有列表,字典等多种数据类型,还允许创建自定义数据类型,后边会提到。
Python跟js一样,是动态语言,变量没有固定的数据类型。Python中声明变量的时候直接写变量名即可。使用`=`来为变量赋值。
a = 'abc'  #此时a指向abc
b = a  #此时a,b都指向abc
a = 'xyz'  #此时a指向xyz,b还是指向abc
print(b)   #输出的是abc
Python一般用全部大写表示常量。
Python中有两种除法,一种是`/`,这里 10/3 = 3.33333333333; `/`除法的计算结果是浮点数,即使能够整除,结果也是浮点数,例如   9/3 = 3.0;
还有一种除法称为地板除 `//`,地板除的结果永远是整数,**取结果的整数部分(去尾法)。10//3 = 3**。
Python的整数和浮点数都没有大小限制。超过一定范围就直接表示为`inf`,即无限大。

字符串和编码

Python3版本中字符串以Unicode编码,支持多语言。
对于单个字符的编码,Python使用`ord()`函数获取字符的整数表示,`chr()`函数把编码转换为对应的字符。
一般我们会在`.py`文件开头加上下边两行:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
第一行告诉Lunux/OS X系统,这是一个Python可执行程序,Windows会忽略这个注释;
第二行告诉Python解释器,按照UTF-8编码读取源代码,否则可能会有乱码。
申明了UTF-8编码并不意味着你的`.py`文件就是UTF-8编码的,必须要确保文本编辑器正在使用UTF-8编码。

格式化,Python中的格式化跟C语言一直,采用`%`实现。在字符串内部,常见的占位符有:
     - %d    整数
     - %f     浮点数
     - %s     字符串
     - %x     十六进制整数

        当真的需要使用百分号的时候,使用`%%` 来表示。

使用list和tuple

Python内置的一种数据类型是列表,应该就是js里边的数组。
len(list)函数获得list长度。
list[0]用索引访问list内的元素。当索引超出范围时,Python会报错。list最后一个元素可以用list[len(list)-1] 或者 list[-1]
append()插入末尾;pop()末尾删除;pop(i)删除索引为i的元素;

Python还有另一种有序列表叫元组:tuple。tuple和list非常相似,但是tuple一旦初始化就不能修改。tuple用圆括号`a = ('a','b')` ;现在a这个tuple就不能改变了。他也没有append()等修改的方法。但是能够正常读取元素和使用`len()`方法。
不可变的tuple的意义在于让代码更安全,能用tuple的就尽量用tuple。
**注意:因为无法修改,所以tuple定义时就必须写入所有元素,当tuple只有一个元素的时候,要多写一个逗号,不然解释器会把圆括号当做运算符。例如:`t = (1,)`是tuple,而`t = (1)`是数字1。Python在显示只有一个元素的tuple时,也会多一个逗号,以免你误解。**
**tuple元素不能变,指的也是元素的指向。而在tuple中的list里边的元素,是可以改动的。因为tuple指向的list还是那个list,只是list指向的元素变了。这跟tuple中的元素不变这句话,并不冲突。如下例:**
>>> t = ('a', 'b', ['A', 'B'])  #t 是tuple,t不能改变
>>> t[2][0] = 'X'     #但是t[2]是个list,list里边的元素是可以改变的。
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])

条件判断

Python中条件判断使用`if a = 0:`,`elif a = 1:`,`else:`。注意这里的'elif'就是else if的缩写,这里注意后边有个冒号。判断条件后边的语句使用缩进。

循环

1.`for item in items:`
使用函数`range(n)`可以生成一个从0到n-1的整数序列。再使用`list()`函数还可以转化为list。
2.while 

break        结束循环
continue     跳过当前循环

使用dict和set

dict全称dictionary,即字典。看起来相当于js的对象Obj。
d = {'name':'creabine','age':18}  #dict
d[name] = Creabine  #可以通过key来指定value
'sex' in d   #可以通过  in  来判断key是否存在,这里不存在所以返回False
d.get('sex') #通过get()函数取key,key不存在时返回 None。此时Python交互式命令行不显示结果
d.get('sex',-1) #通过get()函数取key,也可以指定key不存在时要返回的值,这里指定了-1
可以使用pop(key)方法来删除一个dict中的key和对应的value。
dict相比list,查找和插入的速度更快,但是占内存更多。dict的key不能改变。


set和dict类似,也是一组key的集合,但不储存value。
创建一个set需要提供一个list作为输入集合:`s = set([1,2,3])`,这里传入的参数是一个list,打印s将显示`{1,2,3}`表示内部有这三个元素,顺序并不是有序的。
另外,list中重复的元素在set中将自动被过滤。
通过`add(key)`可以将元素添加到set中。重复添加无效
通过`remove(key)`可以删除元素
**set可以看做数学意义上的无序和无重复元素的集合。因此两个set可以做数学意义上的交集(使用`&`),并集(使用`|`)等操作。**

**set和dist的唯一区别仅在于没有储存对应的value,但是其原理是一致的。**

**之前讲过str是不变对象,list是可变对象。对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容。相反,这些方法会创建新的对象并返回,这样就保证了不可变对象本身永远是不可变的。如下例:**
>>> a = 'abc'
>>> b = a.replace('a', 'A')  #这里实际上是创建了一个新的‘Abc’对象给了b。a本身没变。
>>> b
'Abc'
>>> a
'abc'

函数

Python有许多内置函数。调用函数的时候,如果传入的参数数量或者类型有误,则会报错。
abs()求绝对值。max()求一组数中的最大值。

定义函数

Python中使用`def`语句来定义函数,用`return`来返回值。例如:
# test.py文件
def my_abs(x)
    if x >= 0:
        return x
    else:
        return -x
该函数可以直接在交互模式下定义,也可以写在文件中。然后在当前目录下启动python解释器,使用`from test import my_abs`来导入`my_abs()`函数,注意这里的test是文件名,不带`.py`扩展名。

如果想定义一个什么也不做的空函数,可以使用`pass`语句:
def donothing():
    pass
既然`pass`语句什么都不做,那他有什么用呢?他的作用是占位,比如还没想好怎么写的函数,使用pass,让代码能运行起来。缺少了pass,代码运行就会报错。
调用函数时,参数个数不对的时候,python解释器会自动报错,抛出`TypeError`。但是参数类型不对的时候,需要自己在函数中写上类型检查。例如刚才的my_abs()函数。
deg my_abs(x):
    if not isinstance(x,(int,float)):
        raise TypeError('bad operand type')
    if x >= 0:
        return x
    else:
        return -x
这样添加了参数检查之后,若传入错误的参数类型,函数就可以抛出一个错误。

函数还可以返回多个值。此时,返回值实际上是一个tuple。在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋值给对应的变量。
x,y = return2val()
print(x,y)

函数的参数

Python的函数定义非常简单,但灵活度非常大,除了正常定义的必选参数外,还可以使用默认参数,可变参数和关键字参数。使函数定义出来的接口不但能处理复杂的参数,还可以简化调用者的代码。例如:
#有一个求x的n次方的函数:
def power(x,n):
    s = 1
    while n>o:
        n = n - 1
        s = s * n
    return s
若对于该函数,我们常用它求2次方,则可以给他一个默认参数:
def power(x,n=2):
    s = 1
    while n>o:
        n = n - 1
        s = s * n
    return s
# 有默认参数x=2,此时调用 power(5)则相当于power(5,2)。对于n不为2的其他情况,就必须明确的传入参数,如power(5,4)
默认参数可以简化函数的调用。这里要注意几点:**1.设置默认参数时,必选参数在前,默认参数在后。否则解释器难以判断会报错。2.当函数有多个参数时,把变化大的参数放前面,变化小的参数放后边。变化小的参数就可以作为默认参数。**
这里有个坑:Python函数在定义的时候,默认参数的值就已经被计算出来了。如果在函数运行的过程中,改变了默认参数的值,就会出现问题,例如:
def add_end(L=[]):
    L.append('END')
    return L
# 多次调用改变了默认参数的值:
>>> add_end()
['END']
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']
也就是说,默认参数必须指定为不可变对象。所以当遇到上边这种情况时,可以使用none来实现:
def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L
# 这样不论调用多少次,都没问题了。
**可变参数**
在参数前加一个`*`号,可以把参数的数量定义为可变的。例如:`def a(*n)`。
另外,如果传入的参数是一个list或者tuple,Python允许你在list或者tuple前加一个`*`号,把list或tuple的元素变成可变参数填进去。
**可变参数由于数量未知,所以函数中通常会使用`for a in n`这样的语句来遍历传入的参数。**

**关键字参数**
可变参数允许传入0或任意个参数,在函数调用时这些参数自动组装为一个tuple。而关键字参数允许传入0或任意个参数,这些参数在函数中自动组装为一个dict。关键字参数前边加上`**`来表示。例如:
def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)
# 可以传入任意多个关键字参数:
>>> person('Michael', 30)   # 0个关键字参数
name: Michael age: 30 other: {}
>>> person('Bob', 35, city='Beijing')  # 1个关键字参数
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')  # 2个关键字参数
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}
关键字参数能扩展函数的功能。关键字参数也可以把预先组装的dict作为关键字传进去。这里传进去的参数,是预先组装的dict的拷贝,改变他,之前的dict不会变。

**命名关键字参数**
对于关键字参数,若想要限制关键字参数的名字,就可以使用命名关键字参数。例如:
 # 命名关键字参数,用 * 分隔
def person(name,age,*,city,job):
    print(name,age,city,job)
 # 命名关键字参数,若已经有一个可变参数,后边跟着的命名关键字参数就不需要 * 了
def person(name,age,*args,city,job):
    print(name,age,city,job)
#  调用方式:
>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer
和关键字参数`**kw`不同,命名关键字参数用特殊的分隔符`*`,`*`后边的参数被视为命名关键字参数。命名关键字参数必须传入参数名。命名关键字参数可以有默认值,此时可以不传入有默认值的参数。


**参数组合**
在Python中定义函数,可以用必选参数,默认参数,可变参数,关键字参数和命名关键字参数,这5中参数都可以组合使用。**但是注意,参数定义的顺序必须是:必选参数,默认参数,可变参数,命名关键字参数和关键字参数。**

递归函数

**在函数内部,可以调用其他函数,如果在一个函数内部调用函数本身,这个函数就是递归函数。**
例如要计算阶乘:`n! = 1 * 2 * 3 * ... * n`,用函数fact(n)表示,可以看出:`fact(n) = n! = 1 * 2 * 3 * ... * (n-1) * n = (n-1)! *n = fact(n-1) * n`,于是,求阶乘的函数可以写成:
 #求阶乘的递归函数
def fact(n):
    if n = 1:
        return 1
    return n * fact(n-1)
递归函数的有点事定义简单,逻辑清晰。**理论上,所有递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。**
使用递归函数时要注意防止栈溢出,也就是不能递归太多次。解决调用栈溢出的方法是通过尾递归优化。尾递归就是在函数返回的时候,调用它本身,并且return语句不能包含表达式。这样不论递归多少次,都只占用一个栈帧,不会出现栈溢出。
将上边的递归改为尾递归:
def fact(n):
    return fact_iter(n, 1)

def fact_iter(num, product):
    if num == 1:
        return product
    return fact_iter(num - 1, num * product)
可以看到,return fact_iter(num - 1, num * product)仅返回递归函数本身,num - 1和num * product在函数调用前就会被计算,不影响函数调用。
**遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。**

..........所以说了这么多你在逗我吗

高级特性

人生苦短,我用Python。
利用Python的高级特性,可以用最少的代码来完成开发,提高效率。

切片

去list或tuple中的几个元素,使用切片(Slice)。
L = list(range(100))
L = [0,1,2,3,...,99]
# 切片示例:
L[0:3] == L[:3] == [0,1,2]  #`L[0:3]`表示从索引0开始取,取到索引3位置,但不包括索引3。并且如果第一个索引是0,还可以省略,即`L[:3]`。
L[-3:0] == L[-3:] == [97,98,99]  #同时,索引支持负数取倒数的元素,倒数第一个元素的索引是 -1 。若要取最后3个元素,则`L[-3:]`
L[10:20] == [10.11.12.13.14.15.16.17.18.19]
L[:10:2] == [0,2,4,6,8]  #前10个数,每两个取一个
L[::5] == [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]  # 所有数,每5个取一个。
L[:] == [1,2,3,...,99]  # 什么都不写,复制一个list。
tuple也是一种list,唯一的区别是tuple中的元素不可变,因此tuple也可以用切片操作,操作的结果仍是tuple。
字符串`'xxx'`也可以看成是一种list,每个元素就是一个字符,因此,字符串也可以用切片操作,操作的结果仍是字符串。

在很多编程语言中,针对字符串提供了各种截取操作,Python没有针对字符串的截取,统一使用切片操作。

迭代

若给定一个list或tuple,可以通过for循环来遍历,这种遍历我们称为迭代(Iteration)。在Python中通过`for...in`来完成。在其他语言,如c,java,js中则是通过for循环下标来实现的。
Python的迭代不仅可以迭代list和tuple。还可以迭代dict。因为dict的存储不是按照list的方式顺序排列,所以,迭代出的结果顺序可能不一样。默认情况下dict迭代的是key。若要迭代value,可以用`for value in d.values()`若要同时迭代key和value,可以用`for k,v in d.items()`。
字符串也是可迭代对象。也可以这样迭代。
**那么如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断:**
from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False
**最后一个问题,若相对list实现类似js那样的下标循环怎么办?使用Python的内置函数`enumerate`可以把list变成索引-元素对,这样就可以再for循环中迭代索引和元素本身:**
>>> for i, value in enumerate(['A', 'B', 'C']):
...     print(i, value)
...
0 A
1 B
2 C
上边的for循环里,同时引用两个变量,这在Python中是很常见的,如下:
>>> for x, y in [(1, 1), (2, 4), (3, 9)]:
...     print(x, y)
...
1 1
2 4
3 9

列表生成式

列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。例如:
list(range(1,11)) == [1,2,3,...,10]
[x * x for x in range(1,11)] == [1*1,2*2,3*3,...,10*10]
[x * x for x in range(1,11) if x%2 == 0] == [2*2,4*4,...,10*10]
# 两层循环,左边的for循环在外边,右边的for循环嵌套在里边
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
# 同时使用多个变量
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> for k, v in d.items():
...     print(k, '=', v)
...
y = B
x = A
z = C
# 用两个变量生成list
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']
# 把list中所有字符串变成小写
>>> L = ['Hello', 'World', 'IBM', 'Apple']
>>> [s.lower() for s in L]
['hello', 'world', 'ibm', 'apple']

生成器

通过列表生成式创建列表会受到内存的限制,容量有限。若要创建含很多元素的列表,不仅要占用很大的存储空间,如果我们只访问前几个,后边的很多元素就浪费了。所以,如果列表元素可以按照某种算法推算出来,我们就不必穿件完整的list,而在循环的过程中计算出后续元素。在Python中,这种机制称为生成器:generator

创建一个generator,有很多方法。第一种方法很简单,只要把列表生成式的 [] 改成 () ,就创建了一个generator。
>>> L = [x * x for x in range(10)]
>>> L   #创建了一个list
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g   #创建了一个generator
<generator object <genexpr> at 0x1022ef630>
我们可以直接打印出list的每一个元素。当我们想打印generator的元素,如果要一个一个打印出来,可以使用`next(g)`获得generator的下一个返回值。
刚才说过generator保存的是算法,每次调用`next(g)`就计算出 g 的下一个元素的值,值到最后一个元素。没有更多元素的时候,抛出`StopIteration`的错误。
不想多次调用next的时候,可以使用for循环:
>>> g = (x * x for x in range(10))
>>> for n in g:    #用for循环打印g
...     print(n)
... 
0
1
4
...
创建了一个generator之后,基本上永远都不会用next(),而是通过for来迭代他,并且不需要关心StopIteration的错误。
generator非常强大,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。比如斐波拉契数列(Fibonacci),除第一个和第二个数之外,任意一个数都由前两个数相加得到。斐波拉契数列用列表生成式写不出来,但是用函数把他打印出来却很容易。
def fib(max):
    n, a, b = 0, 0, 1   #等价于  n=0  a=0  b=1
    while n < max:
        print(b)
        a, b = b, a + b  #等价于 t=(b,a+b) a=t[0]  b=t[1] 
        n = n + 1
    return 'done'
上边的`a,b = b,a+b`的好处是,不用显式的写出临时变量tuple1这个tuple也可以实现赋值。

显然,函数fib实际上是定义了斐波拉契数列的推算规则,非常类似generator。要把fib函数变成generator,只需要把`print(b)`改成`yield b`
这就是定义generator的另一种方法,**如果一个函数定义中包含`yield`关键字,那么这个函数就不再是一个普通函数,而是一个generator。**


**这里要注意,generator和函数的执行顺序也是不一样的。函数时顺序执行,遇到return或者最后一句返回。而generator的函数,在每次调用next()的时候执行,遇到yield语句返回。再次执行时从上次返回的yield语句处继续执行。**例如:
#定义generator
def odd():
    print('step 1')
    yield 1
    print('step 2')
    yield(3)
    print('step 3')
    yield(5)
#执行
>>> o = odd()
>>> next(o)
step 1
1
>>> next(o)
step 2
3
>>> next(o)
step 3
5
>>> next(o)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
其实在把函数改成generator后,也基本不用next()来获取下一个返回值而使用for循环。
>>> for n in fib(6):
...     print(n)
...
1
1
...
但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:
>>> g = fib(6)
>>> while True:
...     try:
...         x = next(g)
...         print('g:', x)
...     except StopIteration as e:
...         print('Generator return value:', e.value)
...         break
...
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done
练习:生成杨辉三角:
# -*- coding: utf-8 -*-

def triangles():
    while True:
        yield L
        L.append(0);
        L = [L[i-1] + L[i] for i in range(len(L))]
n = 0
for t in triangles():
    print(t)
    n = n + 1
    if n == 10:
        break

迭代器

**凡是可作用于for循环的对象都是Iterable类型;**
**凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;**

可以使用isinstance()判断一个对象是否是Iterable对象或者Iterator对象:
>>> from collections import Iterable
>>> isinstance([], Iterable)  #判断是否为可迭代对象
True

>>> isinstance((x for x in range(10)), Iterator)  #判断是否为迭代器对象
True
你可能会问,为什么list、dict、str等数据类型不是Iterator?这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。


小结:
**集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。**
**Python的for循环本质上就是通过不断调用next()函数实现的,例如:**
for x in [1, 2, 3, 4, 5]:
    pass

#实际上完全等价于
# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
    try:
        # 获得下一个值:
        x = next(it)
    except StopIteration:
        # 遇到StopIteration就退出循环
        break

函数式编程

函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!

Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。

高阶函数

函数可以把返回值赋给变量,也可以把它本身赋给变量,这样变量就指向了该函数。
函数名其实就是指向函数的变量。如果把一个函数的函数名指向其他东西,就无法再用这个函数名调用函数了:
>>> abs = 10
>>> abs(-10)
变量可以指向函数,函数的参数又可以接收变量,那么**一个函数就可以接受另一个函数作为参数。这种函数就称为高阶函数。**
#一个简单的高阶函数
def add(x,y,f)
    return f(x) + f(y)

add(-5,6,abs)   #这样调用的结果就是  abs(-5) + abs(6) = 11

map/reduce

Python内建了`map()`和`reduce()`函数。
**`map()`函数**
map()函数接收两个参数,一个是函数,一个是Iterable(可迭代对象),map将传入的函数一次作用到序列的每个元素。并把结果作为新的Iterator返回。例如:
def f(x):
    return x*x
r = map(f,[1,2,3])  # map传入第一个参数是函数对象本身
list(r)  # 由于其结果r是一个Iterator,Iterator是惰性序列,因此通过list函数计算出整个序列并返回一个list。
[1,4,9]

# 一行代码将list的所有数字转化为字符串
list(map(str,[1,2,3,4,5]))
['1','2','3','4','5']
**`reduce()`函数**
reduce()也接收两个参数,一个是函数f(该函数也必须接收两个参数),一个是Iterable(可迭代对象)。reduce()函数会把f的结果和序列的下一个元素做累计计算,例如:
reduce(f,[x1,x2,x3,x4]) = f(f(f(x1,x2),x3),x4)
例如对一个序列求和:当然这里只是举个例子,求和可以直接用Python的内置函数sum()
>>> from functools import reduce
>>> def add(x, y):
...     return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25
练习:
# -*- coding: utf-8 -*-
# 整理名字,首字母大写其余小写
def normalize(name):
    newName = name[0].upper() + name[1:].lower()
    return newName
# 测试:
L1 = ['adam', 'LISA', 'barT']
L2 = list(map(normalize, L1))
print(L2)
# 求一个list中所有项的乘积
from functools import reduce
def prod(L):
    return reduce(lambda x,y:x*y,L)
print('3 * 5 * 7 * 9 =', prod([3, 5, 7, 9]))

filter

filter()函数用于过滤序列。filter()也接收一个函数和一个序列。和map()不同的是,filter把传入的函数一次作用于每一个元素,根据返回的值是True还是False决定保留还是丢弃该元素。
filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要用list()函数获取所有结果并返回list。

sorted

sorted()排序也是一个高阶函数,可以接收一个key函数来实现自定义排序规则,例如按照绝对值大小排序:`sorted([36,-5,12,-46],key=abs)`。key指定的函数来实现自定义的排序,例如安绝对值大小排序。
sorted排序时,对数字默认升序,对字符串会因为大小使得大写字母在前,要忽略大小写排序时,可以用lower,如果想反向排序,还能再加上reverser=True,如下:
`sorted(['bob','about','Zoo','Create'],key=str.lower)`。

返回函数

高阶函数除了可以接收函数作为参数外,还可以把函数作为结果值返回。例如:
def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum
#当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
#调用函数f时,才真正计算求和的结果:
>>> f()
25
#注意,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:
>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1==f2   #f1()和f2()的调用结果互不影响。
False
在上例中,我们在函数lazy_sum中又定义了函数sum,并且内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为**闭包**的程序结构具有极大的威力。

**闭包**
闭包可以理解为一个外函数返回了一个在它内部定义的内函数。这个被返回的内函数可以用外函数的参数和局部变量。
**另外,返回的内函数并没有立即执行,而是直到调用了内函数才执行。因此,返回闭包时牢记一点:返回函数不要饮用任何循环变量,或后续会发生变化的变量。**

匿名函数

当我们传入函数的时候,有些时候并不需要显式的定义函数,直接传入匿名函数更加方便。Python有限的支持匿名函数。例如:`list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))`。可以看出,这里的匿名函数`lambda x: x * x`实际上就是:
def f(x):
    return x*x
关键字`lambda`表示匿名函数,冒号前边的x表示参数,冒号后编写表达式。匿名函数只能有一个表达式且不用写return,返回值就是该表达式的结果。
匿名函数也是一个函数对象,可以把它赋值给变量,再利用变量来调用该函数。也可以把匿名函数作为返回值。

装饰器

函数也是一个对象,函数对象还可以被赋值给变量调用。函数对象有一个`_name_`属性,可以拿到函数的名字。

参考博客Python装饰器由浅入深

装饰器(Decorator)装饰器通常用于在不改变原有函数代码和功能的情况下,为其添加额外的功能。比如在原函数执行前先执行点什么,在执行后执行点什么。

写代码要遵循**开放封闭原则**,虽然在这个原则主要是针对面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码内部不允许被修改,但外部可以被扩展,即:封闭:已实现的功能代码块;开放:对扩展开放。

偏函数

Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。
用例:
int('123456')  #int()函数可以将字符串转为整数
123456
int('123456',base=2)  #int()函数还可以传入一个参数,将二进制字符串转为整数
#若要大量转换二进制字符串,每次都传入base=2很麻烦,所以可以写个函数给个默认参数
def int2(x,base=2)
    return int(x,base)
#functools.partial就是帮助我们创建偏函数,不用自己定义int(),可以直接如下
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
所以,简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
最后,创建偏函数时,实际上可以接收函数对象、*args和**kw这3个参数。

模块

随着代码越来越多,为了方便维护,把他们分组放在不同的文件中。在Python中,一个.py文件就成为一个模块(Module)。
再向上一层,又引入了按目录来组织模块的方式,成为包(Package)。
举个例子,一个abc.py的文件就是一个名字叫abc的模块,一个xyz.py的文件就是一个名字叫xyz的模块。现在,假设我们的abc和xyz这两个模块名字与其他模块冲突了,于是我们可以通过包来组织模块,避免冲突。方法是选择一个顶层包名,比如mycompany,按照如下目录存放:

引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突。现在,abc.py模块的名字就变成了mycompany.abc,类似的,xyz.py的模块名变成了mycompany.xyz。
请注意,每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是mycompany。

使用模块

Python本身内置了许多模块,安装Python之后即可使用。
使用模块之前要导入该模块,使用:`import module_name` 即可导入模块。导入之后,就有了module_name这个变量指向该模块,利用该变量即可访问该模块的所有功能。
当使用命令行运行一个模块的时候,Python解释器会把一个特殊变量`_name_`置为`_main_`。而如果在其他地方导入该模块时不会。所以可以使用`if _name_ == '_main_'`这个判断,来让一个模块在通过命令行运行时执行一些额外代码,最常见的就运行测试。
**作用域**
在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_前缀来实现的。
正常的函数和变量名是公开的(public),可以被直接引用。类似`_xxx_`这样的变量是特殊变量,可以被直接引用,但是有特殊的用途。例如`_author_`,`_name_`,`_doc_`等。
类似`_xxx`和`__xxx`这样的函数或变量就是非公开的(private),不应该被直接引用。
如何使用private函数和变量呢:
def _private_1(name):
    return 'Hello, %s' % name

def _private_2(name):
    return 'Hi, %s' % name

def greeting(name):
    if len(name) > 3:
        return _private_1(name)
    else:
        return _private_2(name)
**我们在模块里公开greeting()函数,而把内部逻辑用private函数隐藏起来了,这样,调用greeting()函数不用关心内部的private函数细节,这也是一种非常有用的代码封装和抽象的方法,即:外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public。**

安装第三方模块

Python中安装第三方模块是使用包管理工具pip完成的。Mac和Linux不用安装pip。Windows在安装Python的时候有勾选项即可。在命令行窗口尝试运行pip可知是否有安装成功。
安装第三方模块:`pip install Pillow`即可安装Pillow模块。
**模块搜索路径**
当我们试图加载一个模块时,Python会在指定路径下搜索对应的.py文件,找不到就会报错。默认情况下Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys模块的path变量中。
如果要添加自己的搜索目录,有两种方式:
1.修改sys.path
2.设置环境变量PYTHONPATH,该环境变量的内容会被自动添加到模块搜索路径中。

面向对象编程

面向对象编程————Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。

面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。

而**面向对象的程序设计把计算机程序视为一组对象的集合**,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。


**举例说明面向过程和面向对象在程序流程上的不同:**
假设处理学生的成绩表,面向过程的程序可以用dict表示,然后使用一个函数打印学生的程序。但是面向对象的设计思想,首先考虑的是学生应该被视为一个对象,对象拥有名字和成绩的属性,还要有能够打印自身的方法。
#  创建Student类
class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score

    def print_score(self):
        print('%s: %s' % (self.name, self.score))
# 根据类创建实例
bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()
面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。Class是一种抽象概念,比如我们定义的Class——Student,是指学生这个概念,而实例(Instance)则是一个个具体的Student,比如,Bart Simpson和Lisa Simpson是两个具体的Student。
所以,面向对象的设计思想是抽象出Class,根据Class创建Instance。

类和实例

面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。

在Python中,通过class关键字定义类。定义时可以把一些我们认为必须绑定的属性强制填写进去。这里的_init_的第一个参数永远是self,表示创建的实例本身。因此在_inti_方法内部,可以把各种属性绑定到self,即实例本身。
class Student(object)  # class后边紧接着是类名,即Student,通常大写开头,紧接着是(object),表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。

    def _init_(self,name,score)  #每个实例都要有name和score属性
        self.name = name
        self.score = score
有了_init_方法,创建实例时就不能穿入空参数了,必须传入与_init_匹配的参数。self不用传,Python解释器会自己传。
定义好Student类之后,就可以根据他来创建Student实例:
>>> bart = Student('Tom',59)
>>> bart  #可以看到,变量bart指向的就是一个Student的实例,后面的0x10a67a590是内存地址
<__main__.Student object at 0x10a67a590>
>>> Student  #而Student本身则是一个类。
<class '__main__.Student'>
类中定义的函数与普通函数的唯一区别在于,他的第一个参数永远是实例变量self,并且调用时不用传入该参数。

**数据封装**
面向对象变成的重要特点就是数据封装。上边的Student类中,每个实例都有name和score数据,可以通过函数来访问这些数据,比如打印成绩。但是既然实例本身有数据,就可以直接在类的内部定义访问数据的函数,这样就把数据给封装起来了。这些封装数据的函数是和类本身关联起来的,我们称为类方法:
class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score

    def print_score(self):  #第一个参数同样必须是self
        print('%s: %s' % (self.name, self.score))

访问限制

在Class内部,可以有属性和方法。而外部代码可以直接调用实例变量的方法来操作数据。这样就隐藏了内部的复杂逻辑。
同时,外部代码还可以自由的修改一个实例的属性。如果要让内部属性不被外部访问,可以在属性名称前加上**两个下划线`__`。**在Pyhon中,实例的变量若以__开头,就变成了私有变量(private),只有内部可以访问,外部不能访问。例如:
class Student(object):

    def __init__(self, name, score):
        self.__name = name   # 这样__name就是私有变量了,只能在实例内部访问。
        self.__score = score

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

#实例外部代码:
实例变量.__name  #由于是私有变量,已经无法再外部这样访问了。
这样就确保了外部代码不能随意修改对象内部的状态,保护对象。如果想让外部代码获取和更改内部变量,可以再给类增加相应getter和setter方法即可。在setter方法中还可以对传入参数进行检查,防止传入错误的参数。
**注意:在Python中,变量名类似`__xxx__`的,也就是以双下划线开头且结尾的,是特殊变量,特殊变量可以直接访问,不是private变量。只有类似`__xxx`的,以双下划线开头的,才是private,而private实际上也只是Python把`__xxx`改成了`_ClassName__xxx`而已,但是不要耍小聪明用这种方式访问private,因为Python解释器可能会改成其他写法。最后,有可能会遇到类似`_xxx`的单下划线开头,意思是虽然能够从外部访问,但是请不要随意访问我,把我视为private。**

继承和多态

在OOP程序设计中,定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class成为基类,父类或超类(Base class/Super class)。子类会继承父类的全部功能
class Animal(object):   #创建一个animal类
    def run(self):
        print('Animal is running...')
class Dog(Animal): #创建一个dog类,继承自animal,直接拥有run方法
    pass
class Cat(Animal):  #创建一个cat类,继承自animal,直接拥有run方法
    def run(self):  # 如果在子类创建一个同名方法,会覆盖父类的方法。
        print('Cat is running...') 
当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict一样,也可以使用`isinstance(a,Animal)`这样的方式来判断。而子类因为继承子父类,所以一个dog,即是Dog类型,又是Animal类型。这就是多态。
多态的好处在于,我们可以编写一些函数,他们接收父类Animal数据类型作为变量,而Animal的子类Cat,Dog类的实例都可以在这些函数中执行,并且会根据Cat和Dog自身的情况来执行。如果以后又增加了Animal的子类Bird类,也不需要回头去修改这些函数,这就是“开闭”原则:
**对扩展开放:允许新增Animal子类;**
**对修改封闭:不需要修改依赖Animal类型的函数。**

继承还可以一级一级地继承下来,就好比从爷爷到爸爸、再到儿子这样的关系。而任何类,最终都可以追溯到根类object,类似DOM树的继承树。


**静态语言 vs 动态语言**
对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了。
这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

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

获取对象信息

当我们拿到一个对象的引用时,如何知道这个对象是什么类型,有哪些方法?
使用`type()`函数判断对象类型。他返回对应的Class类型,int,str等。
使用`isinstance()`函数判断是否是某种类型。对于对象和继承很好用。isinstance还可以判断变量是否是多种类型中的一种。传入个tuple即可。
使用`dir()`函数可以获得一个包含字符串的list,其中包含该对象的所有属性和方法。

实例属性和类属性

由于Python是动态语言,根据类创建的实例可以任意绑定属性。obj.name = Tom 即可。但是我们也可以给类本身绑定属性。可以直接在class中定义属性,这种属性是类属性,归类所有:
class Student(object)
    name = 'Student'
当我们定义了一个类属性的时候,这个属性归类所有,但是该类的所有实例都可以访问该属性。如果实例中定义了或赋予了同名属性,会优先访问实例属性,如果没有就访问类属性。所以实际编程的时候,要避免实例属性和类属性同名。

面向对象高级编程

数据封装,继承和多态只是面向对象程序设计中最基础的3个概念。在Python中,面向对象还有很多高级特性,允许我们写出非常强大的功能。例如多重继承,定制类,元类等概念。

使用slots

定义一个class,创建一个实例之后,还可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。当然,给一个实例绑定的方法,对另一个实例是不起作用的。
想给所有实例都绑定方法,那就直接给class绑定方法。这样所有的实例均可调用该方法。动态绑定还允许我们在程序的运行过程中动态给class加上功能,这在静态语言中很难实现。

如果我们想要限制实例的属性,只允许对实例添加固定的属性,就可以使用`__slots__`。
Python允许在定义class的时候,定义一个特殊的`__slots__`变量,来限制该class实例能添加的属性:
class Student(object):
    __slots__ = ('name','age')  #用tuple定义允许绑定的属性名称。对实例绑定其他属性会报错。
**注意:`__slots__`定义的属性仅对当前类的实例起作用,对继承的子类的实例是不起作用的。除非在子类中也定义slots,这样,子类实例允许定义的属性就是自身slots机上父类的slots。**

使用@property

之前说过,在绑定属性时为了不随意修改属性,会把属性变为private,然后通过getter和setter去读写,在setter中还可以对属性范围进行检查。但这样略显复杂,Python有更简单的方式:
Python内置的`@property`装饰器负责**把一个方法变成属性调用**。
class Student(object):

    @property             # 用@property装饰器装饰birth属性
    def birth(self):      # birth的getter
        return self._birth

    @birth.setter      # 把birth的setter函数变为一个属性
    def birth(self, value):   # birth的setter
        self._birth = value

    @property          #只装饰age,不设置setter属性,就把age变成了一个只读属性
    def age(self):
        return 2015 - self._birth
# 调用的时候
>>> s = Student()  #实例s
>>> s.birth = 1991 # OK,实际转化为s.set_birth(1991)  
>>> s.birth # OK,实际转化为s.get_birth()
**能看出,`@property`装饰器主要在于简化了调用,实际上还是要写一个getter和setter函数的。**

多重继承

之前已经说过了继承,通过继承,子类可以获得父类的功能。多重继承其实就是继承自多个父类,如下:
class Dog(Animal)   # 继承
    pass
class Cat(Animal,Runnable)  # 多重继承
    pass
# 这种多重继承的设计又称为MixIn。为了更好地看出继承关系,可以
class Cat(Animal,RunnableMinIn)  # 用MixIn来表明继承关系
    pass
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
由于Python允许使用多重继承,因此,MixIn就是一种常见的设计。只允许单一继承的语言(如Java)不能使用MixIn的设计。

定制类

之前说过,类似`__xxx__`这种变量或函数名,就表示他们在Python中是有特殊用途的。例如
  • __slots__():限制实例可设置的属性范围。
  • __len__():让class作用于len()函数,求长度。
  • __str__():改变类打印出来的内容。
  • __repr__():返回开发者看到的字符串,有时可以: __repr__ = __str__
  • __iter__():返回一个迭代函数。使得calss可以被用于for循环。
  • __next__():实例在for循环的时候就是不断调用这个方法来拿到下一个值。
  • __getitem__():表现的像list那样按照下标取出元素,还可以传入切片对象slice。
  • __getattr__():调用方法或属性。没找到属性的时候会在该函数中查找。
  • __call__():直接调用实例。

使用枚举类

定义常量的时候,可以使用枚举类。
from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
#这样我们就获得了Month类型的枚举类,可以直接使用Month.Jan来引用一个常量,或者枚举它的所有成员:
for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)
#value属性则是自动赋给成员的int常量,默认从1开始计数。
如果需要更精确地控制枚举类型,可以从Enum派生出自定义类。
@unique装饰器可以帮助我们检查保证没有重复值。

**Enum可以把一组相关常量定义在一个class中,且class不可变,而且成员可以直接比较**

使用元类

**type()**
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。type()函数可以查看一个类型或变量的类型,Hello是一个class,它的类型就是type,而h是一个实例,它的类型就是class Hello。


**metaclass**
metaclass,直译为元类,简单的解释就是:当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。连接起来就是:先定义metaclass,就可以创建类,最后创建实例。

**metaclass是Python中非常具有魔术性的对象,它可以改变类创建时的行为。这种强大的功能使用起来务必小心。**

错误、调试和测试

Python内置了一套异常处理机制,来帮助我们进行错误处理。

编写测试也很重要。有了良好的测试,就可以在程序修改后反复运行,确保程序输出符合我们编写的测试。

错误处理

高级语言通常都内置了一套try...except...finally...的错误处理机制,Python也不例外。
**try**
try:    #当我们认为某些代码可能会出错时,就可以用try来运行这段代码
    print('try...')
    r = 10 / 0
    print('result:', r)
except ZeroDivisionError as e: #如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块
    print('except:', e)
finally:  #执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。
    print('finally...')
print('END')
如果有错误发生: try ⇒  except ⇒ finally
如果没有错误发生: try ⇒ finally
finally也可以不存在,但如果存在就一定会被执行。
可以有多个except捕获不同的错误,此外还可以在except后边加一个else,当没有错误发生时自动执行else语句:
try:
    print('try...')
    r = 10 / int('2')
    print('result:', r)
except ValueError as e:
    print('ValueError:', e)
except ZeroDivisionError as e:
    print('ZeroDivisionError:', e)
else:
    print('no error!')
finally:
    print('finally...')
print('END')
使用try...except捕获错误还有一个巨大的好处,就是可以跨越多层调用,比如函数main()调用foo(),foo()调用bar(),结果bar()出错了,这时,只要main()捕获到了,就可以处理。也就是说,不需要在每个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以了。这样一来,就大大减少了写try...except...finally的麻烦。


**调用堆栈**
如果错误没有被捕获,会一直往上抛,最后被Python解释器捕获,打印一个错误信息然后退出程序。根据错误信息,从上往下可以看到整个错误的调用函数链。跟踪该链,就可以找到错误源头。

**抛出错误**
其实错误也是class,捕获一个错误就是捕获一个实例。错误是有意创建并且抛出的。Python内置函数会抛出很多类型的错误,我们自己编写的函数也可以抛出错误。

调试

程序出错的时候需要调试,可能要看看变量的值之类的,比如print变量,但是调试完还要删掉,韩麻烦。
**断言(assert)**
凡是用print来查看的地方都可以用assert来替代。例如:
def foo(s):
    n = int(s)
    assert n != 0, 'n is zero!'
    return 10 / n

def main():
    foo('0')
assert的意思是断言n != 0应该为True,即n不能为0,否则程序肯定会出错。
若断言失败就会抛出错误,当然程序中处处都有assert也不好,所以Python解释器启动的时候可以用 -0 参数来关闭assert,关闭后assert就直接当pass了。


**logging**
还有一种方式是把print替换为logging。相比assert,logging不会抛出错误,而且可以输出到文件。logging允许你指定记录信息的级别,有debug,info,warning,error等几个级别。logging还可以通过配置,一条语句同时输出到不同的地方。


**pdb**
第四种方式是Python的调试器pdb,让程序单步运行,随时查看状态。
使用`python -m pdb xxx.py` 启动。

**IDE**
最爽的还是使用IDE吧,目前比较好的Python IDE有PyCharm。
另外Eclipse加上pydev插件也可以调试Python程序。

单元测试

单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。比如要测试一个abs()函数,可能要编写正数,负数,0,None等值,然后期待相应的结果。把这些测试用例放在一个测试模块里就是一个完整的单元测试。

单元测试的好处在于,如果我们修改了abs()函数,只要再运行一次单元测试,就能知道我们的修改有没有对原函数造成影响了。

可以在单元测试中编写两个特殊的setUp()和tearDown()方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。setUp()和tearDown()方法有什么用呢?设想你的测试需要启动一个数据库,这时,就可以在setUp()方法中连接数据库,在tearDown()方法中关闭数据库,这样,不必在每个测试方法中重复相同的代码。

文档测试

Python的文档测试模块可以直接提取出注释中的代码并执行测试。

IO编程

IO在计算机中指Input/Output,也就是输入和输出。
IO编程中,Stream(流)是一个很重要的概念,可以把流想象成一个水管,数据就是水管里的水,但是只能单向流动。Input Stream就是数据从外面(磁盘、网络)流进内存,Output Stream就是数据从内存流到外面去。

异步IO的复杂度远远高于同步IO。

文件读写

读写文件时常见的IO操作,Python内置了读写文件的函数,用法是和C兼容的。
在磁盘上读写文件的功能都是有操作系统提供的,现在操作系统不允许普通程序直接操作磁盘,所以读写文见就是请求操作系统打开文件对象,然后通过操作系统提供的接口来读写文件。

**读文件**、
使用Python内置的`open()`函数,传入文件位置和标识符。
例如:`f = open('/User/hehe/test.txt','r')` 。其中标识符r表示读。这样就成功的打开了一个文件,如果文件不存在会报错。接下来使用read()方法来读取文件的全部内容,Python把内容读取到内存,用一个str对象。`f.read()`。最后调用`close()`方法关闭文件。
**文件使用完后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的。**


由于读写文件都可能产生IOError,一旦出错后边的f.close()就不会调用无法关闭文件。所以为了保证无论如何都能正确关闭文件,可以用  try....finally来实现。但是Python还给出了更简单的方式,使用with语句来帮我们调用close:
with open('/path/to/file', 'r') as f:
    print(f.read())
调用read()会一次性读取文件的全部内容,如果文件太大内存就爆了。所以保险起见,可以反复调用read(size)方法,每次读取size个字节的内容。另外调用readline()可以每次读取一行内容,调用readlines()一次读取所有内容并行返回list。可以根据需求决定如何调用。
如果文件很小,read()一次性读取最方便,如果确定文件大小,反复调用read(size)比较保险,如果是配置文件,调用readlines()最方便。
for line in f.readlines():
    print(line.strip()) # 把末尾的'\n'删掉
**file-like Object**
像open()函数返回的这种有read()方法的对象,在Pyton中统称为file-like Object。除了file,还可以是内存的字节流,网络流等等。
StringIO就是在内存中创建的file-like Object,常用作临时缓冲。


**二进制文件**
前边说的默认都是文本,并且是UTF-8编码的文本。要读取二进制文件,如图片视频等,就要用'rb'模式打开文件。
>>> f = open('/Users/michael/test.jpg', 'rb')
>>> f.read()
b'\xff\xd8\xff\xe1\x00\x18Exif\x00\x00...' # 十六进制表示的字节
**字符编码**
要读取非UTF-8编码的文本文件,需要给open()函数传入encoding参数,例如,读取GBK编码的文件:
>>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk')
>>> f.read()
'测试'
遇到有些编码不规范的文件,你可能会遇到UnicodeDecodeError,因为在文本文件中可能夹杂了一些非法编码的字符。遇到这种情况,open()函数还接收一个errors参数,表示如果遇到编码错误后如何处理。最简单的方式是直接忽略:
>>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk', errors='ignore')
**写文件**
写文件和读文件是一样的,唯一的区别是调用open()函数时,传入标识符'w'或者'wb'表示写文本文件或写二进制文件。
>>> f = open('/Users/michael/test.txt', 'w')
>>> f.write('Hello, world!')
>>> f.close()
#或者使用with
with open('/Users/michael/test.txt', 'w') as f:
    f.write('Hello, world!')
要写入特定编码的文本文件,请给open()函数传入encoding参数,将字符串自动转换成指定编码。

StringIO和BytesIO

**StringIO**
很多时候,数据读写不一定是文件,也可以在内存中读写。StringIO顾名思义就是在内存中读写str。要把str写入StringIO,我们需要先创建一个StringIO,然后,像文件一样写入即可:
>>> from io import StringIO
>>> f = StringIO()
>>> f.write('hello')
5
>>> f.write(' ')
1
>>> f.write('world!')
6
>>> print(f.getvalue())   #getvalue()方法用于获得写入后的str。
hello world!
要读取StringIO,可以用一个str初始化StringIO,然后,像读文件一样读取:
>>> from io import StringIO
>>> f = StringIO('Hello!\nHi!\nGoodbye!')
>>> while True:
...     s = f.readline()
...     if s == '':
...         break
...     print(s.strip())
...
Hello!
Hi!
Goodbye!
**BytesIO**
StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO。
BytesIO实现了在内存中读写bytes,我们创建一个BytesIO,然后写入一些bytes:
>>> from io import BytesIO
>>> f = BytesIO()
>>> f.write('中文'.encode('utf-8'))
6
>>> print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87'
请注意,写入的不是str,而是经过UTF-8编码的bytes。
和StringIO类似,可以用一个bytes初始化BytesIO,然后,像读文件一样读取:
>>> from io import BytesIO
>>> f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
>>> f.read()
b'\xe4\xb8\xad\xe6\x96\x87'

操作文件和目录

Python内置的os模块可以直接调用操作系统提供的接口函数。
在Python交互式命令行试试os模块的基本功能:
import os
os.name  #操作系统类型
'posix'  # 说明系统是Linux,Unix或Max OS X
'nt'     # 说明系统是Windows
os.uname()  # 获取系统详细信息,Windows不可用。
 **环境变量**:在操作系统中定义的环境变量,全部保存在os.environ这个变量中,可以直接查看。获取某个环境变量的值可以调用  os.environ.get('key')。
 **操作文件和目录**:操作文件和目录的函数一部分放在os模块中,一部分放在os.path模块中,这一点要注意下。查看,创建和删除目录可以这样调用:
# 查看当前目录的绝对路径
os.path.abspath('.')
# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来:
os.path.join('D:\Python','testdir')
# 然后用上一句返回的路径,创建一个目录
os.mkdir('D:\Python\Creabine')
# 删除一个目录
os.rmdir('D:\Python\Creabine')
把两个路径合成一个时,不要直接拼接字符串,而要通过`os.path.join()`函数,这样可以正确处理不用操作系统的路径分隔符。在Linux/Unix/Mac下,`os.path.join()`返回这样的字符串:`part-1/part-2`,而Windows会返回这样的字符串:`part-1\part-2`。同样的道理,拆分路径也不要拆字符串,使用`os.path.split()`函数,这样可以把路径拆分为两部分,后一部分总是最后级别的文件或目录。如:
os.path.split('D:\Python\Creabine')
('D:\\Python','Creabine')
使用`os.path.splitext()`可以直接让你得到文件扩展名:
os.path.splitext('D:\Python\test.py')
('D:\Python\test','.py')
这些合并拆分路径的函数并不要求目录和文件要真实存在,他们只对字符串进行操作。
文件操作使用下边的函数:
# 对文件重命名,在当前目录下操作
os.rename('test.txt','hehe.py')
# 删掉文件
os.remove('hehe.py')
**复制文件的函数在os中不存在,因为复制文件并非由操作系统提供系统调用。理论上可以通过文件读写完成复制,但要写很多代码。幸运的是`shutil`模块提供了`copyfile()`函数,以及其他很多实用函数,他们看以看做是os模块的补充。**

最后看看如何用Python的特性来过滤文件:
# 遍历当前路径下的文件和文件夹:
os.listdir('.')
# 遍历当前目录下的所有文件夹
[x for x in os.listdir('.') if os.path.isdir(x)]
# 遍历当前目录下的所有 .py  文件
[x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']

序列化

在程序运行的过程中,所有的变量都是存在内存中。当程序运行结束,变量所占用的内存就被操作系统全部回收,如果没有把内容储存到磁盘上,下次重新运行,程序运行过程中改变的变量就会被初始化回去。
我们把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling。序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。
**Python提供了pickle模块来实现序列化。**
尝试讲一个对象序列化并写入文件:
import pickle
d = dict(name='Bob',age=20,score=88)
pickle.dumps(d)
`pickle.dumps()`方法把任意对象序列化成一个bytes,然后就可以把这个bytes写入文件。或者用另一个方法`puckle.dump()`直接把对象序列化后写入一个file-like Object:
f = open('dump.txt','wb')
pickle.dump(d,f)
f.close()
此时写入的dump.txt文件里,是一堆乱七八糟的内容,这是Python保存的对象内部信息。
当我们要把对象从磁盘读到内存时,可以先把内容读到一个bytes,然后用`pickle.loads()`方法反序列化出对象,也可以直接用`pickle.load()`方法从一个file-like Object中直接反序列化出对象。
f = open('dump.txt','rb')
d = pickle.load(f)
f.close()
d
['age':20,'score':88,'name':'Bob']
Pickle的问题在于,他只能用于Python,并且不同版本的Python彼此之间都不兼容,因此只能用来保存那些不重要的数据,不能成功的反序列化也没关系。

**JSON**:若想在不用的语言间传递对象,就要把对象序列化为标准格式,如XML,蛋更好的方式是序列化为JSON,他可以被所有语言读取,也可以方便的存到磁盘或通过网络传输。
JSON和Python内置的数据类型的对应如下:
JSON类型Python类型
{}dict
[]list
‘string’str
123.45int或float
true/falseTrue/False
nullNone

Python内置的JSON模块提供了完善的Python对象到JSON格式的转换:

import json
d = dict(name='Bob'.age=20,score=88)
json.dumps(d)   # 强Python对象变为一个JSON
'{"age":20,"score":88,"name":"Bob"}'
dumps()方法返回一个str,内容就是标准的JSON。类似的,dump()方法可以直接把JSON写入一个file-like Object。
要把JSON反序列化为Python对象,用loads()或者对应的load()方法,前者把JSON的字符串反序列化,后者从file-like Object中读取字符串并反序列化:
>>> json_str = '{"age": 20, "score": 88, "name": "Bob"}'
>>> json.loads(json_str)
{'age': 20, 'score': 88, 'name': 'Bob'}
由于JSON标准规定JSON编码是UTF-8,所以我们总是能正确地在Python的str与JSON的字符串之间转换。

**JSON进阶**:Python的dict对象可以直接序列化为JSON的{},不过,很多时候,我们更喜欢用class表示对象,比如定义Student类,然后序列化:
import json

class Student(object):
    def __init__(self, name, age, score):
        self.name = name
        self.age = age
        self.score = score

s = Student('Bob', 20, 88)
print(json.dumps(s))
运行上边的代码会报错,因为Student对象并不是一个可序列化为JSON的对象。此时我们要给dumps()方法多传入一个参数即可:`print(json.dumps(s,default=lambda obj: obj.__dict__))`
因为通常class的实例都有一个`__dict__`属性,他就是一个dict,用来储存实例变量。也有少出例外,比如定义了`__slots__`的class。

同样的道理,如果我们要把JSON反序列化为一个Student对象实例,loads()方法首先转换出一个dict对象,然后,我们传入的object_hook函数负责把dict转换为Student实例:
def dict2student(d):
    return Student(d['name'], d['age'], d['score'])

运行结果如下:

>>> json_str = '{"age": 20, "score": 88, "name": "Bob"}'
>>> print(json.loads(json_str, object_hook=dict2student))
<__main__.Student object at 0x10cd3c190>

进程和线程

过去的单核CPU执行多任务,是通过轮流执行各个任务交替进行的,由于CPU运算速度快,给人的感觉就是多个任务同时执行。真正的并行执行多任务只能在多核CPU上实现。但是一般任务数量远多于CPU核心的数量,所以系统也会自动把任务轮流调度到每个核心上执行。

对操作系统来说,一个任务就是一个进程(Process),比如打开一个文件就是一个进程,两个文件就是两个进程。有的进程的内部,会同时干多件事,就要同时运行多个子任务,这些子任务称为线程(Thread)。所以,一个进程至少有一个线程。

之前写的Python程序都是执行单任务的进程,也就只有一个线程,实际上的多任务有以下3种模式:
- 多进程模式
- 多线程模式
- 多进程+多线程模式(复杂,很少用)
Python既支持多进程,又支持多线程。

线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。

多进程和多线程的程序涉及到同步、数据共享的问题,编写起来更复杂。

多进程

multiprocessing

如果你打算编写多进程的服务程序,Unix/Linux无疑是正确的选择。由于Windows没有fork调用,难道在Windows上无法用Python编写多进程的程序?

由于Python是跨平台的,自然也应该提供一个跨平台的多进程支持。multiprocessing模块就是跨平台版本的多进程模块。

多线程

ThreadLocal

进程 vs. 线程

分布式进程

正则表达式

常用内建模块

datetime

collections

base64

struct

hashlib

itertools

XML

HTMLParser

urllib

常用第三方模块

PIL

virtualenv

图形界面

网络编程

TCP/IP简介

TCP编程

UDP编程

电子邮件

SMTP发送邮件

POP3收取邮件

访问数据库

使用SQLite

使用MySQL

使用SQLAlchemy

Web开发

最早的软件都是运行在大型机上的,软件使用者通过“哑终端”登陆到大型机上去运行软件。后来随着PC机的兴起,软件开始主要运行在桌面上,而数据库这样的软件运行在服务器端,这种Client/Server模式简称CS架构。

随着互联网的兴起,人们发现,CS架构不适合Web,最大的原因是Web应用程序的修改和升级非常迅速,而CS架构需要每个客户端逐个升级桌面App,因此,Browser/Server模式开始流行,简称BS架构。

Python有上百种Web开发框架,有很多成熟的模板技术,选择Python开发Web应用,不但开发效率高,而且运行速度快。

HTTP协议简介

HTTP是在网络上传输HTML的协议,用于浏览器和服务器的通信。

Web采用的HTTP协议采用了非常简单的请求-响应模式,从而大大简化了开发。当我们编写一个页面时,我们只需要在HTTP请求中把HTML发送出去,不需要考虑如何附带图片、视频等,浏览器如果需要请求图片和视频,它会发送另一个HTTP请求,因此,一个HTTP请求只处理一个资源。

HTTP响应如果包含body,也是通过\r\n\r\n来分隔的。请再次注意,Body的数据类型由Content-Type头来确定,如果是网页,Body就是文本,如果是图片,Body就是图片的二进制数据。

当存在Content-Encoding时,Body数据是被压缩的,最常见的压缩方式是gzip,所以,看到Content-Encoding: gzip时,需要将Body数据先解压缩,才能得到真正的数据。压缩的目的在于减少Body的大小,加快网络传输。

HTML简介

介绍了HTML,CSS,JS,这里就不看了。

WSGI接口

了解了HTTP协议和HTML文档,我们其实就明白了一个Web应用的本质就是:

  1. 浏览器发送一个HTTP请求;
  2. 服务器收到请求,生成一个HTML文档;
  3. 服务器把HTML文档作为HTTP响应的Body发送给浏览器;
  4. 浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示。

所以,最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。Apache、Nginx、Lighttpd等这些常见的静态服务器就是干这件事情的。

要动态生成HTML的话,Python使用WSGI(Web Server Gateway Interface)接口来处理TCP链接,HTTP请求等底层逻辑。

使用Web框架

使用模板

异步IO

协程

asyncio

async/await

aiohttp

实战

更多推荐

Python入门