Commit 9ead04db authored by whzecomjm's avatar whzecomjm
Browse files

modi py3notes

parent 8886d714
Loading
Loading
Loading
Loading
+33 −45
Original line number Diff line number Diff line
%% Cell type:markdown id: tags:

# Python 教程 (1) 基础部分

这个教程根据廖雪峰的[Python3教程](https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000)所写.主要是一些基础教程, 截止于模块的学习之前.


## Jupyter 使用

Jupyter Notebook是一个Web应用程序,允许您创建和共享包含实时代码,方程,可视化和说明文本的文档。 用途包括:数据清理和转换,数值模拟,统计建模,机器学习等等。

一些常用的 Jupyter 的魔术命令.
一些常用的 Jupyter 的魔术命令. 比如运行一个py程序. 我们可以用 `%load` 来加载一个存在的 py 文件. 更多的一些魔术命令如下:

| 魔术命令 | 作用 |
| :--- | :---|
|%quickref|显示 IPython 快速参考|
|%debug	|从最新的异常跟踪的底部进入交互式调试器|
|%run script.py	|执行 script.py|
|%load script.py| 加载 script.py|
|%cd direcrory	|切换工作目录|

更多内容参见 [Jupyter 安装与使用](https://blog.csdn.net/gubenpeiyuan/article/details/79252402). 比如运行一个py程序. 我们可以用 `%load` 来加载一个存在的 py文件.
更多内容参见 [Jupyter 安装与使用](https://blog.csdn.net/gubenpeiyuan/article/details/79252402).

```python
%load baidu.py
```

## Python 基础

### 一些注意事项
### Python3 的一些注意事项
1. python 不用分号
2. \# 表示注释, 多行注释用两行 ''' 包着.
3. print 里可以用逗号分开不同的内容, 也可以用加号连接相同类型的内容, 比如都是 str.
4. py3默认支持中文, 可以不加 `#-*-coding: UTF-8 -*-`

下面是一个简单的求绝对值的例子:

%% Cell type:code id: tags:

``` python
x = input("This is an absolute function, please enter an value:") # 输入一个数字
a = float(x)
a = float(x) #转为浮点数
if a > 0:
    print("|", a, "|=" ,a)
else:
    print("|",a, "|=",-a)
```

%% Output

    This is an absolute function, please enter an value:1
    | 1.0 |= 1.0
    This is an absolute function, please enter an value:3141
    | 3141.0 |= 3141.0

%% Cell type:markdown id: tags:

python 是动态语言, 可以**覆盖取值和更换变量类型**.

%% Cell type:markdown id: tags:

python 里的 "/" 就是表示除法, 地板除法用 "//", 相应的 "%" 表示余数.

### 字符编码
#### ASCII、Unicode和UTF-8的关系

1. ASCII 是最早的编码, 使用一个字节能表示英文字母和一些符号, 但是不能表示中文等其他文字;
2. GB2312 是中国制定的中文编码, 使用2个字节.
3. Unicode 是统一所有语言的一套编码, 不会出现乱码问题.
4. UTF-8 编码是一种可变长编码, 相当于是 Unicode 的压缩, 能节省空间, 也能兼容所有的编码.
5. 在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。
6. 浏览网页的时候,服务器会把动态生成的Unicode内容转换为UTF-8再传输到浏览器.
7. Python 3 的字符串是以Unicode编码的.


%% Cell type:markdown id: tags:

对于单个字符的编码,Python提供了ord()函数获取**单个**字符的整数表示,chr()函数把编码转换为对应的字符:

%% Cell type:code id: tags:

``` python
a= ord('A')
b= ord('')
c=chr(100)
d=chr(25991)
print(a,b,c,d)
```

%% Output

    65 36229 d 文

%% Cell type:markdown id: tags:

同样我们可以直接使用十六进制来表示一个字符: 比如文的整数表示 25991, 超是 36229, 转化为十六进制为 6587 和 8d85.于是我们可以在前面添加 `\u`

%% Cell type:code id: tags:

``` python
'\u6587\u8d85'
```

%% Output

    '文超'

%% Cell type:markdown id: tags:

#### 格式化

最后一个常见的问题是如何输出格式化的字符串。我们经常会输出类似'亲爱的xxx你好!你xx月的话费是xx,余额是xx'之类的字符串,而xxx的内容都是根据变量变化的,所以,需要一种简便的格式化字符串的方式。在Python中,采用的格式化方式和C语言是一致的,用`%`实现. 常见的占位符有有:

|占位符|	替换内容|
|:--|:--:|
|%d|	整数|
|%f|	浮点数|
|%s|	字符串|
|%x|	十六进制整数|

注意 % 前面没有逗号. 另外, 当使用`%`符号时, 用两个来转义.
注意 % 前面没有逗号. 另外, 当使用`%`符号时, 用两个连续的`%%`来表示转义`%`.

%% Cell type:code id: tags:

``` python
print('Hello, %s' % 'world')
print('Hello, %s' %'Wenchao')
print('Hi, %s, you are the top 1%% in the whole %d.' %('Wenchao', 1000000))
```

%% Output

    Hello, world

%% Cell type:code id: tags:

``` python
print('Hi, %s, you have %% $%d.' % ('Michael', 1000000))
```

%% Output

    Hi, Michael, you have % $1000000.
    Hello, Wenchao
    Hi, Wenchao, you are the top 1% in the whole 1000000.

%% Cell type:code id: tags:

``` python
s1=72
s2=85
r=(s2-s1)/s1*100
print('小明成绩提升了百分之 %.2f%%.' %r) # 使用 `%.2f` 表示保留浮点后两位.
print('小明成绩提升了百分之 %.2f %%.' %r) # 使用 `%.2f` 表示保留浮点后两位.
```

%% Output

    小明成绩提升了百分之 18.06%.
    小明成绩提升了百分之 18.06 %.

%% Cell type:markdown id: tags:

### 列表和元组
python里的列表 list 用方括号括起来, 可以用`len()`函数查看元素个数, 序列是从零开始, 使用负数表示倒数第几个:

%% Cell type:code id: tags:

``` python
classmates = ['Michael', 'Noam', 'Wenchao']
print(len(classmates),
classmates[0],
classmates[-1])
```

%% Output

    3 Michael Wenchao

%% Cell type:markdown id: tags:

下面我们介绍一下list的操作. 首先可以直接对某个值进行更改赋值, 比如 `s[1]=2` 这样. 我们还能增添和删除列表, 使用 append insert pop等.

%% Cell type:code id: tags:

``` python
classmates.append('Tamar') # 增加元素, 每次运行都会增加一个重复
print(classmates)
```

%% Output

    ['Michael', 'Noam', 'Wenchao', 'Tamar']

%% Cell type:code id: tags:

``` python
classmates.insert(1, 'Jack') # 插入到序列1的位置
print(classmates)
```

%% Output

    ['Michael', 'Jack', 'Noam', 'Wenchao', 'Tamar']

%% Cell type:code id: tags:

``` python
classmates.pop() # 删除末尾的一个元素, (i) 表示删除序列号为i的元素
print(classmates)
```

%% Output

    ['Michael', 'Jack', 'Noam', 'Wenchao']

%% Cell type:code id: tags:

``` python
L = [
    ['Apple', 'Google', 'Microsoft'],
    ['Java', 'Python', 'Ruby', 'PHP'],
    ['Adam', 'Bart', 'Lisa']
]
print(L[1][-1]) # 打印第二行最后一列的元素.
```

%% Output

    PHP

%% Cell type:markdown id: tags:

进一步地, 我们可以使用tuple来表示**不能改变的列表**, 用小括号表示. 但要注意的是, 只有一个元素的时候, 要在后面加逗号, 比如 `t=(1,)` 否则回合数学括号混淆. tuple里面可以嵌套列表, 这样可以让它更灵活, 列表内的元素是可变的. tuple 是安全的列表.
进一步地, 我们可以使用tuple来表示**不能改变的列表**, 用小括号表示. 但要注意的是, 只有一个元素的时候, 要在后面加逗号, 比如 `t=(1,)` 否则会和数学括号混淆. tuple里面可以嵌套列表, 这样可以让它更灵活, 列表内的元素是可变的. tuple 是安全的列表.

%% Cell type:markdown id: tags:

### 条件判断
if, elif, else 使用需要后面接冒号作为缩进的标志. 缩进用空格, jupyter 里可以用 tab代替.
如果我们需要用到 input, 注意`input()`返回的数据类型是`str`, 我们可能需要用 int, float 等转换类型.

%% Cell type:code id: tags:

``` python
w=input("请输入你的体重(kg):")
h=input("请输入你的身高(cm):")
W=float(w)
H=float(h)/100
bmi=W/H**2
if bmi<18.5:
    print("你太瘦了, 要多补一补!")
elif 18.5<=bmi<25:
    print("恭喜!你的BMI指数正常.")
elif 25<=bmi<28:
    print("有点重了哦,注意啦!")
elif 28<=bmi<32:
    print("你已经是个小胖子啦~")
else:
    print("再不减肥你和猪没啥区别啦 XD")
print("你的BMI指数是:%.2f."% bmi)
```

%% Output

    请输入你的体重(kg):50
    请输入你的身高(cm):173
    你太瘦了, 要多补一补!
    你的BMI指数是:16.71

%% Cell type:markdown id: tags:

### 循环语句
为了让计算机能计算成千上万次的重复运算,我们就需要循环语句。

Python的循环有两种,一种是`for...in`循环; 第二种循环是while循环,只要条件满足,就不断循环,条件不满足时退出循环。

注意**使用循环的变量要先进行赋值**.

%% Cell type:code id: tags:

``` python
sum=0
for x in range(101): # range 函数的序列也要注意.
    sum=sum+x
print(sum) # print 要删除缩进.
```

%% Output

    5050

%% Cell type:code id: tags:

``` python
sum=0
n=1
while n<101:
    sum=sum+n
    n=n+1
print(sum)
```

%% Output

    5050

%% Cell type:code id: tags:

``` python
L = ['Bart', 'Lisa', 'Adam']
n=0

for x in L:
    print("Hellp, %s!" %L[n])
    n=n+1
```

%% Output

    Hellp, Bart!
    Hellp, Lisa!
    Hellp, Adam!

%% Cell type:markdown id: tags:

### 使用dict和set

Dict 是 key-value存储方式, 且一个key只能对应一个value.

和list比较,dict有以下几个特点:

1. 查找和插入的速度极快,不会随着key的增加而变慢;
2. 需要占用大量的内存,内存浪费多。

dict的key必须是不可变对象。但是value 是可变的, 本身也能用pop删除一个key:value元素组.

下面是 dict 用法:

%% Cell type:code id: tags:

``` python
d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
d['Michael']
d.get('Tracy')
print(d['Michael'], d.get('Tracy'))
```

%% Output

    85
    95 85

%% Cell type:markdown id: tags:

set和dict类似,也是一组key的集合,但不存储value。不可以放入可变对象. 由于key不能重复,所以,在set中,没有重复的key。(所以集合相同的元素会被合并) 一般的tuple放入set不会出错,而特殊的tuple(带有list)的出错,因为带有可变对象.

%% Cell type:code id: tags:

``` python
s = set([1,1,2, 2, 3])
print(s)
s.add(4) # 增加key
print(s)
s.remove(1) # 删除key
print(s)
```

%% Output

    {1, 2, 3}
    {1, 2, 3, 4}
    {2, 3, 4}

%% Cell type:markdown id: tags:

集合可以做运算,使用 `&` 表示交集, `|` 表示并集.

对于其他不可变量, 比如 str 是不能直接操作的, 但是可以改变后存到重新存到新的变量:

%% Cell type:code id: tags:

``` python
a = 'abc'
b = a.replace('a','A')
print(b)
```

%% Output

    Abc

%% Cell type:markdown id: tags:

## 函数

在Python中,定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号`:`,然后,在缩进块中编写函数体,函数的返回值用return语句返回。

%% Cell type:code id: tags:

``` python
# 这是个坐标变换公式
import math
from math import cos, sin, pi
def move(x,y,t,a):
    nx=x+t*math.cos(a)
    ny=y-t*math.sin(a)
    nx=x+t*cos(a)
    ny=y-t*sin(a)
    return float("%.2f" % nx),float("%.2f"% ny)  # 或者可以使用 round 函数来保留小数点后两位, 比如 round(nx,2).
move(2,3,1,math.pi/3) # 返回值是一个tuple.

move(2,3,1,pi/3) # 返回值是一个tuple.
```

%% Output

    (2.5, 2.13)

%% Cell type:code id: tags:

``` python
# 这是一个二次方程求根公式
import math
from math import sqrt
def quad(a,b,c):
    eq = str(a)+'x^2+'+str(b)+'x+' + str(c)+'=0'
    delta=b**2-4*a*c
    if a==0:
        return "This is not a quadratic equation"
    elif b**2-4*a*c>0:
        x1=(-b+math.sqrt(delta))/(2*a)
        x2=(-b-math.sqrt(delta))/(2*a)
        return "The solutions of the equation are %.2f and %.2f" %(x1,x2)
    elif b**2-4*a*c==0:
        return "This is not a quadratic equation."
    elif delta>0:
        x1=(-b+sqrt(delta))/(2*a)
        x2=(-b-sqrt(delta))/(2*a)
        return "The solutions of the "+eq+ " are %.2f and %.2f." %(x1,x2)
    elif delta==0:
        x=(-b)/(2*a)
        return "This equation have two equal solutions %.2f" %(x)
        return "This equation "+eq+" have two equal solutions %.2f" %(x)
    else:
        return "There is no real solutions for this quadratic euqation."
        return "There is no real solution for this quadratic euqation "+eq+"."

quad(2,6,2)
quad(1,-1,1)
```

%% Output

    'The solutions of the equation are -0.38 and -2.62'
    'There is no real solution for this quadratic euqation 1x^2+-1x+1=0.'

%% Cell type:markdown id: tags:

### 参数的使用

%% Cell type:code id: tags:

``` python
# 这是一个n次幂的公式, 默认情况下使用一个自变量表示平方
def power(x,n=2):
    s=1
    while n>0:
        s=s*x
        n=n-1
    return s

print(power(5),power(2,3))
```

%% Output

    25 8

%% Cell type:markdown id: tags:

从上面的例子可以看出,默认参数可以简化函数的调用。设置默认参数时,有几点要注意:

1. 必选参数在前,默认参数在后,否则Python的解释器会报错.
2. 有时候默认参数设置为`None`可以防止多次调用结果不一样.
3. `*nums` 可以把list元素 `nums` 变成可变参数传到函数内, 这种写法相当有用, 而且很常见.

%% Cell type:code id: tags:

``` python
# 关于3的例子
def pwsum(*args):
    sum=0
    for n in args:
        sum =sum+n**2
    return sum

nums=[1,5,7,8,10]
pwsum(*nums)
```

%% Output

    239

%% Cell type:markdown id: tags:

关键字参数 `**kw` , kw 接受的是一个 dict.

- 可变参数既可以直接传入:`func(1, 2, 3)`,又可以先组装list或tuple,再通过`*args`传入:`func(*(1, 2, 3))`

- 关键字参数既可以直接传入:`func(a=1, b=2)`,又可以先组装dict,再通过`**kw`传入:`func(**{'a': 1, 'b': 2})`

使用`*args``**kw`是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。

%% Cell type:markdown id: tags:

### 递归函数
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。

递归函数关键就是把步骤分为:之前的n-1步和最后一步

%% Cell type:code id: tags:

``` python
# 阶乘函数
def frac(n):
    if n==1:
        return 1
    else:
        return n* frac(n-1)

frac(10)
```

%% Output

    3628800

%% Cell type:code id: tags:

``` python
# 汉诺塔
def move(n, a, b, c):
    if n == 1:
        print(a, '-->', c)
    else:
        move((n-1), a, c, b) # 最上面的 n-1个 从 a 通过 c 移到 b
        print(a, '-->', c) # 最下面一个直接移到c
        move((n-1), b, a, c) #上面的 n-1 个从b通过 a 移动到c

move(3,"A","B","C")
```

%% Output

    A --> C
    A --> B
    C --> B
    A --> C
    B --> A
    B --> C
    A --> C

%% Cell type:markdown id: tags:

## 高级特性
在Python中,代码不是越多越好,而是越少越好。代码不是越复杂越好,而是越简单越好。

### 切片

取一个list或tuple的部分元素是非常常见的操作.我们可以用循环:

%% Cell type:code id: tags:

``` python
L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
r = []
n = 3
for i in range(n): # range n: 0,1,2,3,...,n-1
    r.append(L[i])
r
```

%% Output

    ['Michael', 'Sarah', 'Tracy']

%% Cell type:markdown id: tags:

在Python中提供了Slice操作符, 比如前三个元素可以使用下列代码:

```python
L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
L[0:3] # 从索引0开始取,直到索引3为止,但不包括索引3。
L[:3] # 第一个索引是0可以省略
L[-2:] # 从倒数第二个开始
L[-2:-1] # 倒数第二个到倒数第一个之前, 所以只有一个元素
```

可以直接使用range创建list.

%% Cell type:code id: tags:

``` python
L=list(range(100))
L[10:22:2] # 最后的2是等差数列的差值
```

%% Output

    [10, 12, 14, 16, 18, 20]

%% Cell type:code id: tags:

``` python
# 删除字符串头尾的空格
def trim(s):
    if s=="":
        return
    while s[0]==" ":
        s = s[1:]
        if s == "":
            return s
    while s[-1]==" ":
        s= s[:-1]
    return s

trim(" heko ")
```

%% Output

    'heko'

%% Cell type:markdown id: tags:

### 迭代

如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。在Python中,迭代是通过for ... in来完成的.

如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断:

```python
>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False
```

%% Cell type:code id: tags:

``` python
def findMinAndMax(L):
    if L==[]:
        return (None, None)
    max = min = L[0]
    for x in L:
        if x > max:
            max =x
        if x < min:
            min =x
    return (min, max)

findMinAndMax([2,3,7,1,24,67])

```

%% Output

    (1, 67)

%% Cell type:markdown id: tags:

### 列表生成式
使用 for 循环生成列表比较麻烦, 可以用简化的列表生成式:

```python
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
```

代替
```python
>>> L = []
>>> for x in range(1, 11):
...    L.append(x * x)
...
>>> L
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
```

运用列表生成式,可以写出非常简洁的代码。例如,列出当前目录下的所有文件和目录名,可以通过一行代码实现:

%% Cell type:code id: tags:

``` python
import os
[d for d in os.listdir('.')]
```

%% Output

    ['.ipynb_checkpoints',
     'google-picture',
     'Python3.ipynb',
     'replacetxt.ipynb',
     'ssr-address.ipynb',
     'tieba']

%% Cell type:code id: tags:

``` python
L1=['Hello','World',18,'Apple',None]
L2=[l.lower() for l in L1 if isinstance(l,str)]
print(L2)
```

%% Output

    ['hello', 'world', 'apple']

%% Cell type:markdown id: tags:

### 生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的`[]`改成`()`,就创建了一个generator. 如果要一个一个打印出来,可以通过`next()`函数获得generator的下一个返回值. 因为generator也是可迭代对象, 所以我们可以使用for循环调用.

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

%% Cell type:code id: tags:

``` python
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'
fib(5)
```

%% Output

    1
    1
    2
    3
    5

    'done'

%% Cell type:markdown id: tags:

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

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

%% Cell type:code id: tags:

``` python
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'
g=fib(5)
print(g)
for n in g:
    print(n)
```

%% Output

    <generator object fib at 0x05659FB0>
    1
    1
    2
    3
    5

%% Cell type:markdown id: tags:

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

下面是一个杨辉三角的生成器:

%% Cell type:code id: tags:

``` python
def YHT(max):
    n=0
    L=[1]
    while n<max:
        yield L
        L = [1]+[L[n]+L[n+1] for n in range(len(L)-1)]+[1]
        n=n+1
    return None

for n in YHT(10):
    print(n)
```

%% Output

    [1]
    [1, 1]
    [1, 2, 1]
    [1, 3, 3, 1]
    [1, 4, 6, 4, 1]
    [1, 5, 10, 10, 5, 1]
    [1, 6, 15, 20, 15, 6, 1]
    [1, 7, 21, 35, 35, 21, 7, 1]
    [1, 8, 28, 56, 70, 56, 28, 8, 1]
    [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]

%% Cell type:markdown id: tags:

### 迭代器
我们已经知道,可以直接作用于for循环的数据类型有以下几种:

- 一类是集合数据类型,如list、tuple、dict、set、str等;

- 一类是generator,包括生成器和带yield的generator function。

这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。可以使用isinstance()判断一个对象是否是Iterable对象:

%% Cell type:code id: tags:

``` python
from collections import Iterable
isinstance('abc', Iterable)
isinstance((x for x in range(10)), Iterable)
```

%% Output

    True

%% Cell type:markdown id: tags:

而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。可以使用isinstance()判断一个对象是否是Iterator对象:

%% Cell type:code id: tags:

``` python
from collections import Iterator
isinstance((x for x in range(10)), Iterator)
```

%% Output

    True

%% Cell type:markdown id: tags:

生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。

把list、dict、str等Iterable变成Iterator可以使用iter()函数:

```py
>>> isinstance(iter('abc'), Iterator)
True
```

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

凡是可作用于for循环的对象都是Iterable类型;

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

## 函数式编程
### 高级函数
把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。

#### map 和 reduce 函数
`map()`函数接收两个参数,一个是函数,一个是`Iterable`,map将传入的函数依次作用到序列的每个元素,并把结果作为新的`Iterator`返回。

%% Cell type:code id: tags:

``` python
def f(x):
    return x**2

r=map(f,[x for x in range(10)])
next(r)
list(r) # Iterator是惰性序列, 用list 函数列出
```

%% Output

    [1, 4, 9, 16, 25, 36, 49, 64, 81]

%% Cell type:markdown id: tags:

reduce把一个函数作用在一个序列`[x1, x2, x3, ...]`上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
```py
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
```
比方说对一个序列求和,就可以用reduce实现:(注意 reduce 函数需要从模块functools中调用)

%% Cell type:code id: tags:

``` python
from functools import reduce
# 把数字拼在一起
def combine(x,y):
    return 10*x+y
reduce(combine, [1,2,5,7,9])

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
# char2digit
def str2int(s):
    def fn(x, y):
        return x * 10 + y
    def char2num(s):
        return DIGITS[s]
    return reduce(fn, map(char2num, s))
str2int('273243')
```

%% Output

    273243

%% Cell type:code id: tags:

``` python
# 输入的不规范的英文名字,变为首字母大写,其他小写的规范名字
from functools import reduce
def normalize(name):
    return name[0].upper() + name[1:].lower()

# prod of list
def prod(list):
    def pd(x,y):
        return x*y
    return reduce(pd, list)

prod([x for x in range(2,5)])


# str2float函数,把字符串'123.456'转换成浮点数123.456
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def str2float(s):
    def char2num(s):
        return DIGITS[s]
    def fn(x, y):
        return x * 10 + y
    def fn2(x,y):
        return x * 0.1 + y
    s=s.split('.')       # 将 string 拆成一个两半的列表
    return reduce(fn, map(char2num, s[0]))+0.1*reduce(fn2, map(char2num, s[1][::-1])) #[::-1] 逆序
str2float('23.13')
```

%% Output

    23.13

%% Cell type:markdown id: tags:

#### filter 函数
Python内建的filter()函数用于过滤序列。filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

%% Cell type:code id: tags:

``` python
def not_empty(s):
    return s and s.strip()

list(filter(not_empty, ['A', '', 'B', None, 'C', '  ']))
```

%% Output

    ['A', 'B', 'C']

%% Cell type:code id: tags:

``` python
# generate prime
def _odd_iter():
    n = 1
    while True:
        n = n + 2
        yield n
def not_divisible(n):
    return lambda x: x % n > 0
def primes():
    yield 2
    it = _odd_iter() # 初始序列
    while True:
        n = next(it) # 返回序列的第一个数
        yield n
        it = filter(not_divisible(n), it) # 构造新序列

# 打印100以内的素数:
for n in primes():
    if n < 5:
        print(n)
    else:
        break
```

%% Cell type:code id: tags:

``` python
# 回文数
def is_palindrome(n):
    return str(n) == str(n)[::-1]
output=filter(is_palindrome, range(10, 100))
print('10~100:', list(output))
```

%% Output

    10~100: [11, 22, 33, 44, 55, 66, 77, 88, 99]

%% Cell type:markdown id: tags:


注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list。

#### sorted 函数

排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但如果是字符串或者两个dict呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。

Python内置的sorted()函数就可以对list进行排序

```py
>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
```

此外,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:

```py
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
```
要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True:

```py
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']
```

%% Cell type:code id: tags:

``` python
# 成绩或者姓名排序
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
def by_name(t):
    return t[0]
def by_credit(t):
    return t[1]
L1 = sorted(L, key=by_name)
L2 = sorted(L, key=by_credit, reverse=True)
print(L1,'\n',L2)
```

%% Output

    [('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]
     [('Adam', 92), ('Lisa', 88), ('Bob', 75), ('Bart', 66)]

%% Cell type:markdown id: tags:

### 返回函数

*以后再深耕*: [返回函数](https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431835236741e42daf5af6514f1a8917b8aaadff31bf000).

### 匿名函数

关键字lambda表示匿名函数,冒号前面的x表示函数参数。匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

```py
>>> f = lambda x: x * x
>>> f
<function <lambda> at 0x101c6ef28>
>>> f(5)
25
```

%% Cell type:code id: tags:

``` python
L=list(filter(lambda n : n%2 == 1, range(1,20)))
print(L)
```

%% Output

    [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

%% Cell type:markdown id: tags:

### 装饰器
比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“[装饰器”(Decorator)](https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014318435599930270c0381a3b44db991cd6d858064ac0000)

### 偏函数
functools.partial就是帮助我们创建一个偏函数, 把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

```py
int2 = functools.partial(int, base=2)
```

当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。

%% Cell type:markdown id: tags:

# Python 教程 (2) 高级部分

这个教程根据廖雪峰的[Python3教程](https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000)所写. 从模块开始的高级部分.

## 模块

在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。

为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在Python中,一个.py文件就称之为一个模块(Module)。

使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中,因此,我们自己在编写模块时,不必考虑名字会与其他模块冲突。但是也要注意,尽量不要与内置函数名字冲突。点[这里](http://docs.python.org/3/library/functions.html)查看Python的所有内置函数。

为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)。引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突。

请注意,每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是顶层包名。

- [常用内建模块](https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014319347182373b696e637cc04430b8ee2d548ca1b36d000)
- [常用第三方模块](https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001432002680493d1babda364904ca0a6e28374498d59a7000)

### 使用模块

Python本身就内置了很多非常有用的模块,只要安装完毕,这些模块就可以立刻使用。

我们以内建的sys模块为例,编写一个hello的模块:

%% Cell type:code id: tags:

``` python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

' a test module '

__author__ = 'whzecomjm'

import sys

def test():
    args = sys.argv
    if len(args)==1:
        print('Hello, world!')
    elif len(args)==2:
        print('Hello, %s!' % args[1])
    else:
        print('Too many arguments!')

if __name__=='__main__':
    test()
```

%% Output

    Too many arguments!

%% Cell type:markdown id: tags:

- 第一行注释为了兼容 Unix 上运行, 第二行是编码.
- 第四行是模块文档注释, 任何模块代码的第一个字符串都被视为模块的文档注释;
- 第6行使用__author__变量把作者写进去.

当我们在命令行运行hello模块文件时,Python解释器把一个特殊变量`__name__`置为`__main__`,而如果在其他地方导入该hello模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。

在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过`_`前缀来实现的.

类似`_xxx``__xxx`这样的函数或变量就是非公开的(private),不应该被直接引用;之所以我们说,private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。

### 安装第三方模块

在Python中,安装第三方模块,是通过包管理工具pip完成的。注意:Mac或Linux上有可能并存Python 3.x和Python 2.x,因此对应的pip命令是pip3。(但是windows如果只安装py3只需要输入命令 pip)

在使用Python时,我们经常需要用到很多第三方库,例如,上面提到的Pillow,以及MySQL驱动程序,Web框架Flask,科学计算Numpy等。用pip一个一个安装费时费力,还需要考虑兼容性。我们推荐直接使用 [Anaconda](https://www.anaconda.com/),这是一个基于Python的数据处理和科学计算平台,它已经内置了许多非常有用的第三方库,我们装上Anaconda,就相当于把数十个第三方模块自动安装好了,非常简单易用。

下载后直接安装,Anaconda会把系统Path中的python指向自己自带的Python,并且,Anaconda安装的第三方模块会安装在Anaconda自己的路径下,不影响系统已安装的Python目录。

%% Cell type:markdown id: tags:

## 面向对象编程

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

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

在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。

如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是Student这种数据类型应该被视为一个对象,这个对象拥有name和score这两个属性(Property)。如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个print_score消息,让对象自己把自己的数据打印出来。

%% Cell type:code id: tags:

``` python
class Student(object): # 创建类对象

    def __init__(self, name, score):
        # 初始化对象, 注意每次构造器都得写个self
        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()
```

%% Output

    Bart Simpson: 59
    Lisa Simpson: 87

%% Cell type:markdown id: tags:

*总结:面向过程以步骤分类,面向对象以功能(属性)分类。面向对象功能上的统一保证了其可扩展性。*

### 类和实例

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

由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的`__init__`方法,在创建实例的时候,就把name,score等属性绑上去.

注意到`__init__`方法的第一个参数永远是`self`,表示创建的实例本身,因此,在`__init__`方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量`self`,并且,调用时,不用传递该参数。

#### 数据封装
但是,既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。

```py
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))
```

要定义一个方法,除了第一个参数是self外,其他和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入.

#### 访问限制
在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线`__`,在Python中,实例的变量名如果以`__`开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问.

%% Cell type:code id: tags:

``` python
class Student(object):
    def __init__(self, name, gender):
        self.name = name
        self.__gender = gender
    def get_gender(self):
        return self.__gender
    def set_gender(self, gender):
        if gender == 'male' or 'female':
            self.__gender = gender
        else:
            raise ValueError('bad gender!')

bart = Student('Bart', 'male')
tom = Student('Tom','')
bart.get_gender()
tom.set_gender('female')
tom.get_gender()
```

%% Output

    'female'

%% Cell type:markdown id: tags:

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

```py
class Animal(object):
    def run(self):
        print('Animal is running...')
class Dog(Animal):
    def run(self):
        print('Dog is running...')

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

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

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

#### 获取对象信息

- 我们来判断对象类型,使用`type()`函数: 基本类型都可以用`type()`判断.

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

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

### 面向对象高级编程

更多面向对象高级编程参见[这里](https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143186738532805c392f2cc09446caf3236c34e3f980f000).

## 文件读写

读写文件是最常见的IO操作。Python内置了读写文件的函数,用法和C是兼容的。

读文件
要以读文件的模式打开一个文件对象,使用Python内置的open()函数,传入文件名和标示符:

```py
>>> f= open('/Users/michael/test.txt', 'r')
```

标示符'r'表示读,这样,我们就成功地打开了一个文件。

如果文件打开成功,接下来,调用read()方法可以一次读取文件的全部内容,Python把内容读到内存,用一个str对象表示:

```py
>>> f.read()
'Hello, world!'
```

最后一步是调用close()方法关闭文件。文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的:

```py
>>> f.close()
```

如果文件很小,read()一次性读取最方便;如果不能确定文件大小,反复调用read(size)比较保险;如果是配置文件,调用readlines()最方便:

```py
for line in f.readlines():
    print(line.strip()) # 把末尾的'\n'删掉
```

### 二进制文件

前面讲的默认都是读取文本文件,并且是UTF-8编码的文本文件。要读取二进制文件,比如图片、视频等等,用'rb'模式打开文件即可.

要读取非UTF-8编码的文本文件,需要给open()函数传入encoding参数,例如,读取GBK编码的文件:

```py
>>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk')
>>> f.read()
'测试'
```

### 写文件
写文件和读文件是一样的,唯一区别是调用open()函数时,传入标识符'w'或者'wb'表示写文本文件或写二进制文件:

```py
>>> f = open('/Users/michael/test.txt', 'w')
>>> f.write('Hello, world!')
>>> f.close()
```

用with语句来得保险:

```py
with open('/Users/michael/test.txt', 'w') as f:
    f.write('Hello, world!')
```

细心的童鞋会发现,以'w'模式写入文件时,如果文件已存在,会直接覆盖(相当于删掉后新写入一个文件)。如果我们希望追加到文件末尾怎么办?可以传入'a'以追加(append)模式写入。

%% Cell type:code id: tags:

``` python
todo = r'C:\Users\whzec\Desktop\to-do.md'
# 'r'是防止字符转义的,因为有\t.
with open (todo, 'r') as f:
    s = f.read()
    print(s)
```

%% Output

    # To-D0 List
    
    - [v] 犹太课程 2门可用其他系的课代替
    - 给下学期课老师写邮件
    - Javascripts python
    - 焦点效应与透明度错觉  习得性无助
    - gsm73 p9.
    - 整理所有 quantization 资料
    - 整理代数讨论班SUSTC
    - PI for ring theory
    - 邓肯图
    - 基本的代数 各个定义,我们需要重新整理一下wiki的书签
    - 写group algebra
    
    ### 课程表
    - (v)88891 Colloquium Algebra
    
    #### 第一学期
    - 888100 Symbolic dynamics 周日 没有考试
    - 888580 Topics in Combinatorics 10--1300 周日(v)
    
    #### 第二学期
    
    - 88778 Networks Science (周三 11-14)
    - (v)887810-01,02 Introduction to Artificial Intelligence (周四 14-16 16-17)
    - 88826	Differential geometry 2 (要考试 周三晚上5-8)
    - (v)88802 Walks on ordinals  周日10-13
    - 88854 lie group and algebra 周二11-14
    
    

%% Cell type:markdown id: tags:

### 操作文件和目录
Python内置的os模块也可以直接调用操作系统提供的接口函数。

打开Python交互式命令行,我们来看看如何使用os模块的基本功能:
```py
>>> import os
>>> os.name # 操作系统类型
'posix'
```
如果是posix,说明系统是Linux、Unix或Mac OS X,如果是nt,就是Windows系统。

%% Cell type:code id: tags:

``` python
import os
os.name
```

%% Output

    'nt'

%% Cell type:markdown id: tags:

### 序列化
我们把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,都是一个意思。

序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。

反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。

#### JSON

如果我们要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式,比如XML,但更好的方法是序列化为JSON,因为JSON表示出来就是一个字符串,可以被所有语言读取,也可以方便地存储到磁盘或者通过网络传输。JSON不仅是标准格式,并且比XML更快,而且可以直接在Web页面中读取,非常方便。

Python内置的json模块提供了非常完善的Python对象到JSON格式的转换。我们先看看如何把Python对象变成一个JSON:

```py
>>> import json
>>> d = dict(name='Bob', age=20, score=88)
>>> json.dumps(d)
'{"age": 20, "score": 88, "name": "Bob"}'
```

要把JSON反序列化为Python对象,用loads()或者对应的load()方法,前者把JSON的字符串反序列化,后者从file-like Object中读取字符串并反序列化:

```py
>>> json_str = '{"age": 20, "score": 88, "name": "Bob"}'
>>> json.loads(json_str)
{'age': 20, 'score': 88, 'name': 'Bob'}
```

%% Cell type:markdown id: tags:

## 多进程和多线程

python 在 MacOS 和 linux 支持 fork(), 所以可以更好的学习多进程和多线程. 具体内容参见[这里](https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014319272686365ec7ceaeca33428c914edf8f70cca383000).

## 正则表达式

在正则表达式中,如果直接给出字符,就是精确匹配。用`\d`可以匹配一个数字(digits),`\w`可以匹配一个字母或数字(words), `.`匹配任意一个字符, `*`匹配任意个字符(包括0个), `+` 表示至少一个, `?`表示0或者1个, `{n}` 表示n 个字符, `{n,m}` 表示 n到 m 个字符, 来看一个例子:

```py
\d{3}\s+\d{3,8}
```
我们来从左到右解读一下:

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

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

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

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

### 进阶

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

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

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

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

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

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

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

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

- `[^0-9]` 匹配非数字, `^`在括号内表示*非*.

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

## re模块
Python提供re模块,包含所有正则表达式的功能。由于Python的字符串本身也用\转义,所以要特别注意. 因此我们强烈建议使用Python的r前缀,就不用考虑转义的问题了:

```py
s = r'ABC\-001' # Python的字符串
# 对应的正则表达式字符串不变:
# 'ABC\-001'
```

先看看如何判断正则表达式是否匹配:

```py
>>> import re
>>> re.match(r'^\d{3}\-\d{3,8}$', '010-12345')
<_sre.SRE_Match object; span=(0, 9), match='010-12345'>
>>> re.match(r'^\d{3}\-\d{3,8}$', '010 12345')
>>>
```

%% Cell type:markdown id: tags:

match()方法判断是否匹配,如果匹配成功,返回一个Match对象,否则返回None。常见的判断方法就是:

```py
test = '用户输入的字符串'
if re.match(r'正则表达式', test):
    print('ok')
else:
    print('failed')
```

### 切分字符串

用正则表达式切分字符串比用固定的字符更灵活,请看正常的切分代码:
```py
>>> 'a b   c'.split(' ')
['a', 'b', '', '', 'c']
```

嗯,无法识别连续的空格,用正则表达式试试:
```py
>>> re.split(r'\s+', 'a b   c')
['a', 'b', 'c']
```

无论多少个空格都可以正常分割。加入,试试:

```py
>>> re.split(r'[\s\,]+', 'a,b, c  d')
['a', 'b', 'c', 'd']
```
再加入;试试:
```py
>>> re.split(r'[\s\,\;]+', 'a,b;; c  d')
['a', 'b', 'c', 'd']
```
如果用户输入了一组标签,下次记得用正则表达式来把不规范的输入转化成正确的数组。

### 分组
除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()表示的就是要提取的分组(Group)。比如:

`^(\d{3})-(\d{3,8})$`分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码:

```py
>>> m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
>>> m
<_sre.SRE_Match object; span=(0, 9), match='010-12345'>
>>> m.group(0)
'010-12345'
>>> m.group(1)
'010'
>>> m.group(2)
'12345'
```

如果正则表达式中定义了组,就可以在Match对象上用group()方法提取出子串来。

注意到group(0)永远是原始字符串,group(1)、group(2)……表示第1、2、……个子串。

%% Cell type:markdown id: tags:

### 贪婪匹配
最后需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的0:
```py
>>> re.match(r'^(\d+)(0*)$', '102300').groups()
('102300', '')
```

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

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

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

### 编译
如果一个正则表达式要重复使用几千次,出于效率的考虑,我们可以预编译该正则表达式,接下来重复使用时就不需要编译这个步骤了,直接匹配:

%% Cell type:code id: tags:

``` python
import re
re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
# 使用:
re_telephone.match('010-12345').groups()
```

%% Output

    ('010', '12345')

%% Cell type:markdown id: tags:

## 网络编程

### TCP/IP简介
IP协议负责把数据从一台计算机通过网络发送到另一台计算机。数据被分割成一小块一小块,然后通过IP包发送出去。由于互联网链路复杂,两台计算机之间经常有多条线路,因此,路由器就负责决定如何把一个IP包转发出去。IP包的特点是按块发送,途径多个路由,但不保证能到达,也不保证顺序到达。

IP地址实际上是一个32位整数(称为IPv4),以字符串表示的IP地址如192.168.0.1实际上是把32位整数按8位分组后的数字表示,目的是便于阅读。

IPv6地址实际上是一个128位整数,它是目前使用的IPv4的升级版,以字符串表示类似于2001:0db8:85a3:0042:1000:8a2e:0370:7334。

TCP协议则是建立在IP协议之上的。TCP协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP协议会通过握手建立连接,然后,对每个IP包编号,确保对方按顺序收到,如果包丢掉了,就自动重发。

一个TCP报文除了包含要传输的数据外,还包含源IP地址和目标IP地址,源端口和目标端口。许多常用的更高级的协议都是建立在TCP协议基础上的,比如用于浏览器的HTTP协议、发送邮件的SMTP协议等。TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据。相对TCP,UDP则是面向无连接的协议。

使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。

虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。


Socket是网络编程的一个抽象概念。通常我们用一个Socket表示“打开了一个网络链接”,而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可。

### UDP 协议


TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据。相对TCP,UDP则是面向无连接的协议。

使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。

虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。

### 电子邮件

Email的历史比Web还要久远,直到现在,Email也是互联网上应用非常广泛的服务。

一封电子邮件的旅程就是:

```
发件人 -> MUA -> MTA -> MTA -> 若干个MTA -> MDA <- MUA <- 收件人
```

发邮件时,MUA和MTA使用的协议就是SMTP:Simple Mail Transfer Protocol,后面的MTA到另一个MTA也是用SMTP协议。

收邮件时,MUA和MDA使用的协议有两种:POP:Post Office Protocol,目前版本是3,俗称POP3;IMAP:Internet Message Access Protocol,目前版本是4,优点是不但能取邮件,还可以直接操作MDA上存储的邮件,比如从收件箱移到垃圾箱,等等。

#### SMTP发送邮件

SMTP是发送邮件的协议,Python内置对SMTP的支持,可以发送纯文本邮件、HTML邮件以及带附件的邮件。

Python对SMTP支持有smtplib和email两个模块,email负责构造邮件,smtplib负责发送邮件。

首先,我们来构造一个最简单的纯文本邮件, 然后,通过SMTP发出去:

%% Cell type:code id: tags:

``` python
from email.mime.text import MIMEText
msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')

# 输入Email地址和口令:
from_addr = input('From: ')
password = input('Password: ')
# 输入收件人地址:
to_addr = input('To: ')
# 输入SMTP服务器地址:
smtp_server = input('SMTP server: ')

import smtplib
server = smtplib.SMTP(smtp_server, 25) # SMTP协议默认端口是25
server.set_debuglevel(1)
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()
```

%% Cell type:markdown id: tags:

使用Python的smtplib发送邮件十分简单,只要掌握了各种邮件类型的构造方法,正确设置好邮件头,就可以顺利发出。

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

%% Cell type:markdown id: tags:

## 访问数据库

不能做快速查询,只有把数据全部读到内存中才能自己遍历,但有时候数据的大小远远超过了内存(比如蓝光电影,40GB的数据),根本无法全部读入内存。

为了便于程序保存和读取数据,而且,能直接通过条件快速查询到指定的数据,就出现了数据库(Database)这种专门用于集中存储和查询的软件。

数据库软件诞生的历史非常久远,早在1950年数据库就诞生了。经历了网状数据库,层次数据库,我们现在广泛使用的关系数据库是20世纪70年代基于关系模型的基础上诞生的。

### SQLite

SQLite是一种嵌入式数据库,它的数据库就是一个文件。由于SQLite本身是C写的,而且体积很小,所以,经常被集成到各种应用程序中,甚至在iOS和Android的App中都可以集成。

Python就内置了SQLite3,所以,在Python中使用SQLite,不需要安装任何东西,直接使用。

### MySQL
MySQL是Web世界中使用最广泛的数据库服务器。SQLite的特点是轻量级、可嵌入,但不能承受高并发访问,适合桌面和移动应用。而MySQL是为服务器端设计的数据库,能承受高并发访问,同时占用的内存也远远大于SQLite。

此外,MySQL内部有多种数据库引擎,最常用的引擎是支持数据库事务的InnoDB。

## Web开发

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

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

在BS架构下,客户端只需要浏览器,应用程序的逻辑和数据都存储在服务器端。浏览器只需要请求服务器,获取Web页面,并把Web页面展示给用户即可。

当然,Web页面也具有极强的交互性。由于Web页面是用HTML编写的,而HTML具备超强的表现力,并且,服务器端升级后,客户端无需任何部署就可以使用到新的版本,因此,BS架构迅速流行起来。

今天,除了重量级的软件如Office,Photoshop等,大部分软件都以Web形式提供。比如,新浪提供的新闻、博客、微博等服务,均是Web应用。

Web应用开发可以说是目前软件开发中最重要的部分。Web开发也经历了好几个阶段:

1. 静态Web页面:由文本编辑器直接编辑并生成静态的HTML页面,如果要修改Web页面的内容,就需要再次编辑HTML源文件,早期的互联网Web页面就是静态的;

2. CGI:由于静态Web页面无法与用户交互,比如用户填写了一个注册表单,静态Web页面就无法处理。要处理用户发送的动态数据,出现了Common Gateway Interface,简称CGI,用C/C++编写。

3. ASP/JSP/PHP:由于Web应用特点是修改频繁,用C/C++这样的低级语言非常不适合Web开发,而脚本语言由于开发效率高,与HTML结合紧密,因此,迅速取代了CGI模式。ASP是微软推出的用VBScript脚本编程的Web开发技术,而JSP用Java来编写脚本,PHP本身则是开源的脚本语言。

4. MVC:为了解决直接用脚本语言嵌入HTML导致的可维护性差的问题,Web应用也引入了Model-View-Controller的模式,来简化Web开发。ASP发展为ASP.Net,JSP和PHP也有一大堆MVC框架。

目前,Web开发技术仍在快速发展中,异步开发、新的MVVM前端技术层出不穷。

Python的诞生历史比Web还要早,由于Python是一种解释型的脚本语言,开发效率高,所以非常适合用来做Web开发。Python有上百种Web开发框架,有很多成熟的模板技术,选择Python开发Web应用,不但开发效率高,而且运行速度快。

## 异步IO
由于我们要解决的问题是CPU高速执行能力和IO设备的龟速严重不匹配,多线程和多进程只是解决这一问题的一种方法。

另一种解决IO问题的方法是异步IO。当代码需要执行一个耗时的IO操作时,它只发出IO指令,并不等待IO结果,然后就去执行其他代码了。一段时间后,当IO返回结果时,再通知CPU进行处理。

具体python的操作参见[这里](https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143208573480558080fa77514407cb23834c78c6c7309000).