pynotes

  • python基础

1.字符串和编码

  • 对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符:
>>> ord('A')
65
>>> ord('中')
20013
>>> chr(66)
'B'
>>> chr(25991)
'文'
  • 要计算str包含多少个字符,可以用len()函数:
>>> len('ABC')
3
>>> len('中文')
2
  • 格式化
    在Python中,采用的格式化方式和C语言是一致的,用%实现,举例如下:
>>> 'Hello, %s' % 'world'
'Hello, world'
>>> 'Hi, %s, you have $%d.' % ('Michael', 1000000)
'Hi, Michael, you have $1000000.'
  • format()
    另一种格式化字符串的方法是使用字符串的format()方法,它会用传入的参数依次替换字符串内的占位符{0}、{1}……,不过这种方式写起来比%要麻烦得多:
>>> 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.1%'
  • 练习:
    小明的成绩从去年的72分提升到了今年的85分,请计算小明成绩提升的百分点,并用字符串格式化显示出’xx.x%’,只保留小数点后1位:
s1 = 72
s2 = 85
r = (s2-s1)/s1*100
print('小明的成绩提升了{0:.1f}%'.format(r))

2.使用list 和 tuple

  • list
    Python内置的一种数据类型是列表:list。list是一种有序的集合,可以随时添加和删除其中的元素。
    比如,列出班里所有同学的名字,就可以用一个list表示;
    用len()函数可以获得list元素的个数;
    用索引来访问list中每一个位置的元素,记得索引是从0开始的;
    如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素;
    以此类推,可以获取倒数第2个、倒数第3个;
    测试代码如下:
classmate=['jxh','cxy','hlw','hyx']
print(classmate)
print(classmate[0])
print(classmate[1])
print(classmate[2])
print(classmate[3])
print(classmate[-1])
print(classmate[-2])
print(classmate[-3])
print(classmate[-4])
print(len(classmate))

结果如下:

['jxh', 'cxy', 'hlw', 'hyx']
jxh
cxy
hlw
hyx
hyx
hlw
cxy
jxh
4

list是一个可变的有序表,所以,可以往list中追加元素到末尾;
也可以把元素插入到指定的位置,比如索引号为1的位置;
要删除list末尾的元素,用pop()方法;
要删除指定位置的元素,用pop(i)方法,其中i是索引位置;
要把某个元素替换成别的元素,可以直接赋值给对应的索引位置;
list里面的元素的数据类型也可以不同;
list元素也可以是另一个list的元素;
代码如下:

classmate=['jxh','cxy','hlw','hyx']
print(classmate)
classmate.append('cjl')
print(classmate)
classmate.insert(2,'chj')
print(classmate)
print(classmate.pop())
print(classmate.pop(4))
classmate[0]='change'
print(classmate)
print(len(classmate))

结果如下:

['jxh', 'cxy', 'hlw', 'hyx']
['jxh', 'cxy', 'hlw', 'hyx', 'cjl']
['jxh', 'cxy', 'chj', 'hlw', 'hyx', 'cjl']
cjl
hyx
['change', 'cxy', 'chj', 'hlw']
4
  • tuple
    另一种有序列表叫元组:tuple。tuple和list非常类似,但是tuple一旦初始化就不能修改;
    现在,classmates这个tuple不能变了,它也没有append(),insert()这样的方法。其他获取元素的方法和list是一样的,你可以正常地使用classmates[0],classmates[-1],但不能赋值成另外的元素。
    不可变的tuple有什么意义?因为tuple不可变,所以代码更安全。如果可能,能用tuple代替list就尽量用tuple。
    但是,要定义一个只有1个元素的tuple,如果你这么定义:
>>> t = (1)
>>> t
1

定义的不是tuple,是1这个数!这是因为括号()既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1。所以,只有1个元素的tuple定义时必须加一个逗号,,来消除歧义:

>>> t = (1,)
>>> t
(1,)

看一个“可变的”tuple:
代码如下:

t=('a','b',['A','B'])
t[2][0]='X'
t[2][1]='Y'
print(t)

结果如下:

('a', 'b', ['X', 'Y'])

表面上看,tuple的元素确实变了,但其实变的不是tuple的元素,而是list的元素。tuple一开始指向的list并没有改成别的list,所以,tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。即指向’a’,就不能改成指向’b’,指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的!
3.条件判断

  • if语句
    if语句相比较于c语言来说格式有所变化,其余的并无太大差别。
age = 3
if age >= 18:
    print('adult')
elif age >= 6:
    print('teenager')
else:
    print('kid')
  • 这时候回到之前的input()
birth = input('birth: ')
if birth < 2000:
    print('00前')
else:
    print('00后')

上面的程序会报错,就是因为input()传入进来的是str类型的,不能直接和整数做比较。想要转换为整数类型的,要先通过int()方式进行转换。正确的代码如下:

s = input('birth: ')
birth = int(s)
if birth < 2000:
    print('00前')
else:
    print('00后')

练习:
小明身高1.75,体重80.5kg。请根据BMI公式(体重除以身高的平方)帮小明计算他的BMI指数,并根据BMI指数:
低于18.5:过轻
18.5-25:正常
25-28:过重
28-32:肥胖
高于32:严重肥胖
用if-elif判断并打印结果:

height=1.75
weight=80.5
BMI=weight/(height**2)
if BMI<8.5:
    print('体重过轻')
elif BMI>=18.5 and BMI<25:
    print('正常')
elif BMI>=25 and BMI<28:
    print('过重')
elif BMI>=28 and BMI<32:
    print('肥胖')
else:print('严重肥胖')
过重

4.循环

  • for…in…
    依次把list或tuple中的每个元素迭代出来,for x in …循环就是把每个元素代入变量x,然后执行缩进块的语句。
    Python提供一个range()函数,可以生成一个整数序列,比如range(5)生成的序列是从0开始小于5的整数。
sum = 0
for x in range(101):
    sum = sum + x
print(sum)
  • while循环也是同样的格式的问题
sum = 0
n = 99
while n > 0:
    sum = sum + n
    n = n - 2
print(sum)
  • break,continue
    和c语言没有什么区别,一样使用就ok。
    5.使用dict和set
  • dict
    Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。
name_score={'jxh':99,'cxy':100,'hlw':97}
print(name_score['jxh'])

上面的代码就是利用dict,将学生的名字和他们的分数一一对应,就使得主要找到他们的名字就可以知道他们的成绩了,显得更加的方便。
可在表中增删,并且如果重复给值的话,就会使得后面的值覆盖掉前面的值,其中,删除的方法使用的是pop(),代码如下:

name_score={'jxh':99,'cxy':100}
name_score['hlw']=100
print(name_score)
name_score['hlw']=10
print(name_score)
name_score.pop('jxh')
print(name_score)

结果如下:

{'jxh': 99, 'cxy': 100, 'hlw': 100}
{'jxh': 99, 'cxy': 100, 'hlw': 10}
{'cxy': 100, 'hlw': 10}
  • dict和list的“各有千秋”
    dict有以下几个特点:
    查找和插入的速度极快,不会随着key的增加而变慢;
    需要占用大量的内存,内存浪费多。
    而list相反:
    查找和插入的时间随着元素的增加而增加;
    占用空间小,浪费内存很少。
    所以,dict是用空间来换取时间的一种方法。
  • set
    set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。
    通过add(key)方法可以添加元素到set中。
    通过remove(key)方法可以删除元素。
    set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作。测试代码:
s=set([1,2,3])
print(s)
#避免重复的元素
s1=set([1,1,2,2,3,3])
print(s1)
#添加元素
s.add(4)
print(s)
#移除元素
s.remove(3)
print(s)
s2=set([1,2,3])
s3=set([3,4,5])
#求交集
print(s2&s3)
#求并集
print(s2|s3)

结果显示:

{1, 2, 3}
{1, 2, 3}
{1, 2, 3, 4}
{1, 2, 4}
{3}
{1, 2, 3, 4, 5}
  • 函数

1.调用函数
Python内置了很多有用的函数,我们可以直接调用。可以直接从Python的官方网站查看文档
函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”。
2.定义函数
在Python中,定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回。

def my_abs(x):
    if x >= 0:
        return x
    else:
        return -x
  • 空函数
    如果想定义一个什么事也不做的空函数,可以用pass语句,实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。
def nop():
    pass
  • 数据类型检查
    数据类型检查可以用内置函数isinstance()实现。
def my_abs(x):
#对参数类型做检查,只允许整数和浮点数类型的参数。
    if not isinstance(x, (int, float)):
        raise TypeError('bad operand type')
    if x >= 0:
        return x
    else:
        return -x

3.函数的参数

4.递归函数
使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。(尾递归优化

  • 高级特性

1.切片
取一个list或tuple的部分元素,L[0:3]表示,在L这个list中从索引0开始取,直到索引3为止,但不包括索引3。如果第一个索引是0,还可以省略成为L[:3]。它同样支持倒数切片。那么,tuple也是同理的。同样的,字符串可以视作list,所以他也可以实现切片。

L=list(range(100))
print(L[0:3])
print(L[-2:-1])
print(L[-3:])
#前10个数,每2个取一个
print(L[:10:2])
#所有数,每5个取一个
print(L[::5])
#什么都不写,只写[:]就可以原样复制一个list:
print(L[:])
T=(1,2,3,4,5,6,7,8,8)
print(T[:3])
print('abcdefg'[:3])

结果:

[0, 1, 2]
[98]
[97, 98, 99]
[0, 2, 4, 6, 8]
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
(1, 2, 3)
abc

2.迭代
如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。在Python中,迭代是通过for … in来完成的。
for循环不仅仅可以使用在list,tuple和dict上,还可以是其他的迭代对象。
默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items()。由于字符串也是可迭代对象,因此,也可以作用于for循环。

L=[1,2,3]
for c in L:
    print(c)
T=(1,2,3)
for x in T:
    print(x)
d={'a':1,'b':2,'c':3}
for key in d:
    print(key)
for values in d.values():
    print(values)
#字符串
for ch in 'abcd':
    print(ch)

结果:

1
2
3
1
2
3
a
b
c
1
2
3
a
b
c
d

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

#判断一个对象是不是可迭代的对象
from collections import Iterable
print(isinstance('abs',Iterable))
print(isinstance([1,2,3],Iterable))

结果:

True
True

3.列表生成式
列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用list(range(1, 11))

L1=list(range(1,11))
print(L1)
#筛选仅偶数的平方
L2=[x*x for x in range(1,11) if x%2==0]
print(L2)
#全排列
L3=[m+n for m in 'ABC' for n in 'XYZ']
print(L3)

结果:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[4, 16, 36, 64, 100]
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

for后面的if是一个筛选条件,不能带else,if写在for前面必须加else。
4.生成器(generator)
在Python中,这种一边循环一边计算的机制,称为生成器:列表元素可以按照某种算法推算出来,不必创建完整的list,从而节省大量的空间。
可以通过next()函数获得generator的下一个返回值。

g=(x*x for x in range(10))
print(g)
print(next(g))
print(next(g))
print(next(g))
#但是使用next获取下一个元素实在是非常的繁琐,一般采用的是for
for n in g:
    print(n)
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'
print(fib(4))

如果一个函数定义中包含yield关键字(理解的关键在于:下次迭代时,代码从yield的下一跳语句开始执行。),那么这个函数就不再是一个普通函数,而是一个generator。
这就是杨辉三角是个重点):

def triangles():
    N=[1]    #初始化为[1],杨辉三角的每一行为一个list
    while True:
        yield N   #yield 实现记录功能,没有下一个next将跳出循环,
        S=N[:]   #将list N赋给S,通过S计算每一行
        S.append(0) #将list添加0,作为最后一个元素,长度增加1
        N=[S[i-1]+S[i] for i in range(len(S))]    #通过S来计算得出N
n = 0
results = []
for t in triangles():
    print(t)
    results.append(t)
    n = n + 1
    if n == 10:
        break
if results == [
    [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]
]:
    print('测试通过!')
else:
    print('测试失败!')

5.迭代器
主要的就是区分可迭代对象(Iterable)迭代器(Iterator)

  • 可以直接作用于for循环的对象统称为可迭代对象:Iterable。
  • 可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
    把list、dict、str等Iterable变成Iterator可以使用iter()函数:
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
  • 函数式编程

1.高阶函数

  • map/reduce
    map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
def f(x):
    return x*x
r=map(f,[1,2,3,4,5,6])
#map()传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator,
# Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。
print(list(r))
#把这个list所有数字转为字符串
print(list(map(str,[1,2,3,4,5,6])))
[1, 4, 9, 16, 25, 36]
['1', '2', '3', '4', '5', '6']

reduce把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4).

>>> from functools import reduce
>>> def fn(x, y):
...     return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579
  • filter
    Python内建的filter()函数用于过滤序列。filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
#求奇数
def is_odd(n):
    return n%2==1
print(list(filter(is_odd,[1,2,4,5,6,9,10,15])))
#用filter求素数
# 计算素数的一个方法是埃氏筛法,它的算法理解起来非常简单。
# 首先,列出从2开始的所有自然数,构造一个序列:
# 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...
# 取序列的第一个数2,它一定是素数,然后用2把序列的2的倍数筛掉:
# 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...
# 取新序列的第一个数3,它一定是素数,然后用3把序列的3的倍数筛掉:
# 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...
# 取新序列的第一个数5,然后用5把序列的5的倍数筛掉:
# 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...
# 不断筛下去,就可以得到所有的素数。
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)
for n in primes():
    if n < 100:
        print(n)
    else:
        break

结果:

[1, 5, 9, 15]
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
59
61
67
71
73
79
83
89
97
  • sorted
    Python内置的sorted()函数就可以对list进行排序,它还可以接收一个key函数来实现自定义的排序,它还可以接收一个key函数来实现自定义的排序,可以实现反向排序。
#sorted()函数就可以对list进行排序:
print(sorted([36,5,-12,9,-21]))
print(sorted([36,5,-12,9,-21],key=abs))
print(sorted(['bob', 'about', 'Zoo', 'Credit']))
print(sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower))
print(sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True))

结果:

[-21, -12, 5, 9, 36]
[5, 9, -12, -21, 36]
['Credit', 'Zoo', 'about', 'bob']
['about', 'bob', 'Credit', 'Zoo']
['Zoo', 'Credit', 'bob', 'about']

假设我们用一组tuple表示学生名字和成绩:
L = [(‘Bob’, 75), (‘Adam’, 92), (‘Bart’, 66), (‘Lisa’, 88)]
请用sorted()对上述列表分别按名字还有成绩排序:

from operator import itemgetter
students = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
#itemgetter函数是按照索引项对应的字段(暂且这么说)进行排列的
print(sorted(students, key=itemgetter(0)))
print(sorted(students, key=lambda t: t[1]))
print(sorted(students, key=itemgetter(1), reverse=True))

结果:

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

这其中的lambda有不理解的参考这里
2.返回函数

3.匿名函数(很少的情况下会使用到)

list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]

关键字lambda表示匿名函数。
4.装饰器
假设我们要增强函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
5.偏函数
Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。

print(int('12345'))
#可以指定这是几进制的
print(int('12345',base=8))
# 假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:
def int2(x,base=2):
    return int(x,base)
print(int2('1000000'))
# functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:
import functools
int22=functools.partial(int,base=2)
print(int22('1000000'))
12345
5349
64
64
  • 模块

1.使用模块
Python本身就内置了很多非常有用的模块,只要安装完毕,这些模块就可以立刻使用。
2.安装第三方模块
在Python中,安装第三方模块,是通过包管理工具pip完成的。
一般来说,第三方库都会在Python官方的pypi.python.org网站注册,要安装一个第三方库,必须先知道该库的名称,可以在官网或者pypi上搜索,比如Pillow的名称叫Pillow,因此,安装Pillow的命令就是:pip install Pillow

  • 面向对象编程

1.类和实例

  • 类和实例
    面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”。在Python中,定义类是通过class关键字,创建实例是通过类名+()实现的,通过定义一个特殊的__init__方法,在创建实例的时候,就把name,score等属性绑上去。注意到__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。
class student(object):
    def __init__(self,name,score):
        self.name=name
        self.score=score
bart=student('Bart Simpson',59)
print(bart.name,bart.score)
Bart Simpson 59
  • 数据封装
    Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和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)
bart.print_score()

2.访问限制
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。也就是这样的方式使得我们不能直接通过上面例子的student.name进行访问了,而是需要自己书写方法(也就是定义在类内部的函数)进行访问。
3.继承和多态
当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。子类会继承父类的全部功能,但是我们同时也可以在子类中重新改进父类的功能,那么子类的功能就会覆盖父类的,这就是另一个好处:多态

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 ....')
def run_twice(Animal):
    Animal.run()
print(run_twice(Animal()))
print(run_twice(Dog()))
print(run_twice(Cat()))
Animal is running...
None
Dog is running ...
None
Cat is running ....
None

4.获取对象信息

  • 使用type()
  • 使用isinstance()(其中,能够使用type来判断类型的,都可以使用isinstance来判断)
  • 使用dir()
    如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list。
    仅仅把属性和方法列出来是不够的,配合getattr()、setattr()以及hasattr(),我们可以直接操作一个对象的状态。
    5.实例属性和类属性
    实例属性是要比类属性优先的,当他们的属性都设置值的时候,是选择显示实例属性的值的。
  • 面向对象高级编程

1.使用_slots_
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性,使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。

class Student(object):
    __slots__ = ('name','age')
s=Student()
s.name='蒋辛亥'
s.age=22
print(s.age,s.name)
class GraduateStudent(Student):
    pass
g=GraduateStudent()
g.age=22
g.name='蒋辛亥'
g.score=100
print(g.score,g.name,g.age)
22 蒋辛亥
100 蒋辛亥 22

2.使用@property
Python内置的@property装饰器就是负责把一个方法变成属性调用的。

class Student(object):
    @property
    def score(self):
        return self._score
    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value
# @property的实现比较复杂,我们先考察如何使用。把一个getter方法变成
# 属性,只需要加上@property就可以了,此时,@property本身又创建了另
# 一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,
# 我们就拥有一个可控的属性操作:
s = Student()
s.score = 60
print('s.score =', s.score)
# ValueError: score must between 0 ~ 100!
s.score = 9999
s.score = 60
Traceback (most recent call last):
  File "C:/Users/狂客/PycharmProjects/untitled/hello.py", line 19, in <module>
    s.score = 9999
  File "C:/Users/狂客/PycharmProjects/untitled/hello.py", line 12, in score
    raise ValueError('score must between 0 ~ 100!')
ValueError: score must between 0 ~ 100!

3.多重继承
通过多重继承,一个子类就可以同时获得多个父类的所有功能。
对于需要Runnable功能的动物,就多继承一个Runnable,例如Dog:

class Dog(Mammal, Runnable):
    pass

他在本身哺乳类的父类上,又增加了一个Runnable的父类。
4.定制类

  • str
  • iter
  • getitem
  • getattr
  • call
    5.使用枚举类
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)
# 如果需要更精确地控制枚举类型,可以从Enum派生出自定义类:
# @unique装饰器可以帮助我们检查保证没有重复值。
from enum import Enum, unique
@unique
class Weekday(Enum):
    Sun = 0 # Sun的value被设定为0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6
for name, member in Weekday.__members__.items():
    print(name, '=>', member)
Jan => Month.Jan , 1
Feb => Month.Feb , 2
Mar => Month.Mar , 3
Apr => Month.Apr , 4
May => Month.May , 5
Jun => Month.Jun , 6
Jul => Month.Jul , 7
Aug => Month.Aug , 8
Sep => Month.Sep , 9
Oct => Month.Oct , 10
Nov => Month.Nov , 11
Dec => Month.Dec , 12
Sun => Weekday.Sun
Mon => Weekday.Mon
Tue => Weekday.Tue
Wed => Weekday.Wed
Thu => Weekday.Thu
Fri => Weekday.Fri
Sat => Weekday.Sat

6.使用元类

# metaclass是创建类,所以必须从type类型派生:
class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)
# 指示使用ListMetaclass来定制类
class MyList(list, metaclass=ListMetaclass):
    pass
L = MyList()
L.add(1)
L.add(2)
L.add(3)
L.add('END')
print(L)
' Simple ORM using metaclass '
class Field(object):
    def __init__(self, name, column_type):
        self.name = name
        self.column_type = column_type
    def __str__(self):
        return '<%s:%s>' % (self.__class__.__name__, self.name)
class StringField(Field):
    def __init__(self, name):
        super(StringField, self).__init__(name, 'varchar(100)')
class IntegerField(Field):
    def __init__(self, name):
        super(IntegerField, self).__init__(name, 'bigint')
class ModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
        if name=='Model':
            return type.__new__(cls, name, bases, attrs)
        print('Found model: %s' % name)
        mappings = dict()
        for k, v in attrs.items():
            if isinstance(v, Field):
                print('Found mapping: %s ==> %s' % (k, v))
                mappings[k] = v
        for k in mappings.keys():
            attrs.pop(k)
        attrs['__mappings__'] = mappings # 保存属性和列的映射关系
        attrs['__table__'] = name # 假设表名和类名一致
        return type.__new__(cls, name, bases, attrs)
class Model(dict, metaclass=ModelMetaclass):
    def __init__(self, **kw):
        super(Model, self).__init__(**kw)
    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)
    def __setattr__(self, key, value):
        self[key] = value
    def save(self):
        fields = []
        params = []
        args = []
        for k, v in self.__mappings__.items():
            fields.append(v.name)
            params.append('?')
            args.append(getattr(self, k, None))
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))
# testing code:
class User(Model):
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
u.save()
[1, 2, 3, 'END']
Found model: User
Found mapping: id ==> <IntegerField:id>
Found mapping: name ==> <StringField:username>
Found mapping: email ==> <StringField:email>
Found mapping: password ==> <StringField:password>
SQL: insert into User (id,username,email,password) values (?,?,?,?)
ARGS: [12345, 'Michael', 'test@orm.org', 'my-pwd']
  • 错误、调试和测试

1.错误处理

  • 错误处理
    内置了一套try…except…finally…的错误处理机制。
  • try
    当我们认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。
    由于没有错误发生,所以except语句块不会被执行,但是finally如果有,则一定会被执行(可以没有finally语句)。
try:
    print('try...')
    r = 10 / 0
    print('result:', r)
except ZeroDivisionError as e:
    print('except:', e)
finally:
    print('finally...')
print('END')
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: division by zero
finally...
END
try...
result: 5.0
no error!
finally...
END

Python所有的错误都是从BaseException类派生的,常见的错误类型和继承关系看这里:https://docs.python.org/3/library/exceptions.html#exception-hierarchy

  • 调用栈
    如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。
def foo(s):
    return 10 / int(s)
def bar(s):
    return foo(s) * 2
def main():
    bar('0')
main()
Traceback (most recent call last):
  File "C:/Users/狂客/PycharmProjects/untitled/hello.py", line 7, in <module>
    main()
  File "C:/Users/狂客/PycharmProjects/untitled/hello.py", line 6, in main
    bar('0')
  File "C:/Users/狂客/PycharmProjects/untitled/hello.py", line 4, in bar
    return foo(s) * 2
  File "C:/Users/狂客/PycharmProjects/untitled/hello.py", line 2, in foo
    return 10 / int(s)
ZeroDivisionError: division by zero
  • 记录错误
    正常的Python解释器打印出错误信息后,程序也就结束了,但是我们希望的是把错误堆栈打印出来之后,仍然能让程序运行下去,这个时候我们可以使用Python内置的logging模块去实现。
import logging
def foo(s):
    return 10 / int(s)
def bar(s):
    return foo(s) * 2
def main():
    try:
        bar('0')
    except Exception as e:
        logging.exception(e)
main()
print('END')
ERROR:root:division by zero
Traceback (most recent call last):
  File "C:/Users/狂客/PycharmProjects/untitled/hello.py", line 11, in main
    bar('0')
  File "C:/Users/狂客/PycharmProjects/untitled/hello.py", line 7, in bar
    return foo(s) * 2
  File "C:/Users/狂客/PycharmProjects/untitled/hello.py", line 4, in foo
    return 10 / int(s)
ZeroDivisionError: division by zero
END
  • 抛出错误
    因为错误是class,捕获一个错误就是捕获到该class的一个实例。根据需要,可以定义一个错误的class,选择好继承关系,然后,用raise语句抛出一个错误的实例。
class FooError(ValueError):
    pass
def foo(s):
    n = int(s)
    if n==0:
        raise FooError('invalid value: %s' % s)
    return 10 / n
foo('0')
Traceback (most recent call last):
  File "C:/Users/狂客/PycharmProjects/untitled/hello.py", line 8, in <module>
    foo('0')
  File "C:/Users/狂客/PycharmProjects/untitled/hello.py", line 6, in foo
    raise FooError('invalid value: %s' % s)
__main__.FooError: invalid value: 0

2.调试
第一种方法就是用print()语句将变量打印出来查看
第二种方法:使用断言assert,第一种方法中的print位置都可以改成assert,区别就是启动Python解释器时可以用-O参数来关闭assert。关闭后,你可以把所有的assert语句当成pass来看。

def foo(s):
    n = int(s)
    assert n != 0, 'n is zero!'
    return 10 / n
def main():
    foo('0')
$ python err.py
Traceback (most recent call last):
  ...
AssertionError: n is zero!

第三种方法就是用logging()来代替print(),和assert比,logging不会抛出错误,而且可以输出到文件。

import logging
logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
INFO:root:n = 0
Traceback (most recent call last):
  File "C:/Users/狂客/PycharmProjects/untitled/hello.py", line 6, in <module>
    print(10 / n)
ZeroDivisionError: division by zero

这就是logging的好处,它允许你指定记录信息的级别,有debug,info,warning,error等几个级别,当我们指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debug和info就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
logging的另一个好处是通过简单的配置,一条语句可以同时输出到不同的地方,比如console和文件。
第四个方法就是启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态。
第五个是pdb.set_trace。
第六个是IDE,也就是平时的pycharm,vscode等解释器。其中,pycharm的设置断点和单步执行的方式点击查看
3.单元测试
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
比如对函数abs(),我们可以编写出以下几个测试用例:
输入正数,比如1、1.2、0.99,期待返回值与输入相同;
输入负数,比如-1、-1.2、-0.99,期待返回值与输入相反;
输入0,期待返回0;
输入非数值类型,比如None、[]、{},期待抛出TypeError。
把上面的测试用例放到一个测试模块里,就是一个完整的单元测试。
unittest模块,unittest.TestCase,setUp与tearDown。
4.文档测试

  • IO编程

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

  • 读文件
    要以读文件的模式打开一个文件对象,使用Python内置的open()函数,传入文件名和标示符。如果文件打开成功,接下来,调用read()方法可以一次读取文件的全部内容,Python把内容读到内存,用一个str对象表示。最后一步是调用close()方法关闭文件。文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的。由于文件读写时都有可能产生IOError,一旦出错,后面的f.close()就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,我们可以使用try … finally来实现,但是每次都这么写实在太繁琐,所以,Python引入了with语句来自动帮我们调用close()方法。
with open('D:/python.txt','r',encoding='gbk',errors='ignore') as f:
    print(f.read())
  • 二进制文件
    想要读取二进制文件,我们需要修改的就是:‘r’->‘rb’即可。
  • 字符编码
    f = open(’/Users/michael/gbk.txt’, ‘r’, encoding=‘gbk’),也就是需要给open()函数传入encoding参数。
  • 写文件
    写文件和读文件是一样的,唯一区别是调用open()函数时,传入标识符’w’或者’wb’表示写文本文件或写二进制文件。
    2.StringIO和BytesIO
  • StringIO
    要把str写入StringIO,我们需要先创建一个StringIO,然后,像文件一样写入即可,getvalue()方法用于获得写入后的str。
from io import StringIO
f=StringIO()
#这样子输出来的是字符串的长度
print(f.write('hello'))
print(f.write(' '))
print(f.write('world!'))
print(f.getvalue())
5
1
6
hello world!

要读取StringIO,可以用一个str初始化StringIO,然后,像读文件一样读取:

from io import  StringIO
f=StringIO('hello\nhi\nworld!')
while True:
    s=f.readline()
    if s=='':
        break
    print(s.strip())
hello
hi
world!
  • BytesIO
    StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO。
# BytesIO实现了在内存中读写bytes,我们创建一个BytesIO,然后写入一些bytes:
from io import BytesIO
f=BytesIO()
f.write('中文'.encode('utf-8'))
print(f.getvalue())
# 和StringIO类似,可以用一个bytes初始化BytesIO,然后,像读文件一样读取:
from io import BytesIO
f=BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
print(f.read())
b'\xe4\xb8\xad\xe6\x96\x87'
b'\xe4\xb8\xad\xe6\x96\x87'

3.操作文件和目录
如果我们要操作文件、目录,可以在命令行下面输入操作系统提供的各种命令来完成。比如dir、cp等命令。

import os
# 如果是posix,说明系统是Linux、Unix或Mac OS X,如果是nt,就是Windows系统。
print(os.name)
  • 操作文件和目录
import os
#查看当前目录的绝对路径
print(os.path.abspath('.'))
C:\Users\狂客\PycharmProjects\untitled

把两个路径合成一个时,不要直接拼字符串,而要通过os.path.join()函数,这样可以正确处理不同操作系统的路径分隔符。要拆分路径时,也不要直接去拆字符串,而要通过os.path.split()函数,这样可以把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名。os.path.splitext()可以直接让你得到文件扩展名,很多时候非常方便。
4.序列化

  • 进程和线程

1.多进程
Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊。普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。但是Windows系统没有fork调用。
Python的os模块封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松创建子进程:

  • multiprocessing
    由于Windows没有fork调用,multiprocessing模块就是跨平台版本的多进程模块。
from multiprocessing import Process
import os
# 子进程要执行的代码
def run_proc(name):
    print('Run child process %s(%s)...'%(name,os.getpid()))
if __name__=='__main__':
    print('Parent process %s.'%os.getpid())
    p=Process(target=run_proc,args=('test',))
    print('Child process will start.')
    p.start()
    p.join()
    print('Child process end.')
Parent process 9908.
Child process will start.
Run child process test(21760)...
Child process end.

创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动,这样创建进程比fork()还要简单。join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。

  • Pool
    如果要启动大量的子进程,可以用进程池的方式批量创建子进程:
from multiprocessing import Pool
import os, time, random
def long_time_task(name):
    print('Run task %s (%s)...' % (name, os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print('Task %s runs %0.2f seconds.' % (name, (end - start)))
if __name__=='__main__':
    print('Parent process %s.' % os.getpid())
    p = Pool(4)
    for i in range(5):
        p.apply_async(long_time_task, args=(i,))
    print('Waiting for all subprocesses done...')
    p.close()
    p.join()
    print('All subprocesses done.')
Parent process 2008.
Waiting for all subprocesses done...
Run task 0 (19928)...
Run task 1 (12064)...
Run task 2 (14756)...
Run task 3 (21952)...
Task 1 runs 1.77 seconds.
Run task 4 (12064)...
Task 4 runs 0.61 seconds.
Task 0 runs 2.39 seconds.
Task 2 runs 2.89 seconds.
Task 3 runs 2.98 seconds.
All subprocesses done.

对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。
请注意输出的结果,task 0,1,2,3是立刻执行的,而task 4要等待前面某个task完成后才执行,这是因为Pool的默认大小在我的电脑上是4,因此,最多同时执行4个进程。这是Pool有意设计的限制,并不是操作系统的限制。由于Pool的默认大小是CPU的核数,如果你不幸拥有8核CPU,你要提交至少9个子进程才能看到上面的等待效果。

  • 子进程
    subprocess模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出。
    如果子进程还需要输入,则可以通过communicate()方法输入。
  • 进程间通信
    Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据。
from multiprocessing import Process, Queue
import os, time, random
# 写数据进程执行的代码:
def write(q):
    print('Process to write: %s' % os.getpid())
    for value in ['A', 'B', 'C']:
        print('Put %s to queue...' % value)
        q.put(value)
        time.sleep(random.random())
# 读数据进程执行的代码:
def read(q):
    print('Process to read: %s' % os.getpid())
    while True:
        value = q.get(True)
        print('Get %s from queue.' % value)
if __name__=='__main__':
    # 父进程创建Queue,并传给各个子进程:
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    # 启动子进程pw,写入:
    pw.start()
    # 启动子进程pr,读取:
    pr.start()
    # 等待pw结束:
    pw.join()
    # pr进程里是死循环,无法等待其结束,只能强行终止:
    pr.terminate()
Process to write: 17524
Put A to queue...
Process to read: 11332
Get A from queue.
Put B to queue...
Get B from queue.
Put C to queue...
Get C from queue.

2.多线程
Python的标准库提供了两个模块:_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。

import time, threading
# 新线程执行的代码:
def loop():
    print('thread %s is running...' % threading.current_thread().name)
    n = 0
    while n < 5:
        n = n + 1
        print('thread %s >>> %s' % (threading.current_thread().name, n))
        time.sleep(1)
    print('thread %s ended.' % threading.current_thread().name)
print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)
thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended.

由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,Python的threading模块有个current_thread()函数,它永远返回当前线程的实例。主线程实例的名字叫MainThread,子线程的名字在创建时指定,我们用LoopThread命名子线程。名字仅仅在打印时用来显示,完全没有其他意义,如果不起名字Python就自动给线程命名为Thread-1,Thread-2……

  • Lock
    多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。
import time, threading
# 假定这是你的银行存款:
balance = 0
def change_it(n):
    # 先存后取,结果应该为0:
    global balance
    balance = balance + n
    balance = balance - n
def run_thread(n):
    for i in range(2000000):
        change_it(n)
t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)

像上述的代码,他执行的结果并不是0,而且每次执行的结果可能都会有所改变。究其原因,是因为修改balance需要多条语句,而执行这几条语句时,线程可能中断,从而导致多个线程把同一个对象的内容改乱了。
如果我们要确保balance计算正确,就要给change_it()上一把锁,当某个线程开始执行change_it()时,我们说,该线程因为获得了锁,因此其他线程不能同时执行change_it(),只能等待,直到锁被释放后,获得该锁以后才能改。由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。创建一个锁就是通过threading.Lock()来实现:

import time, threading
# 假定这是你的银行存款:
balance = 0
lock = threading.Lock()
def change_it(n):
    # 先存后取,结果应该为0:
    global balance
    balance = balance + n
    balance = balance - n
def run_thread(n):
    for i in range(100000):
        # 先要获取锁:
        lock.acquire()
        try:
            # 放心地改吧:
            change_it(n)
        finally:
            # 改完了一定要释放锁:
            lock.release()
t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)

这样子执行之后的结果就永远会是0了。

  • 多核CPU
    这里要说的就是Python在进入死循环的时候也会是使用满多核CPU中的一个核,不像是C、C++、Java可以直接将所有的核都用完。这是因为Python解释器中的GIL锁(GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。)。
    3.ThreadLocal
    一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题。
import threading
# 创建全局ThreadLocal对象:
local_school = threading.local()
def process_student():
    # 获取当前线程关联的student:
    std = local_school.student
    print('Hello, %s (in %s)' % (std, threading.current_thread().name))
def process_thread(name):
    # 绑定ThreadLocal的student:
    local_school.student = name
    process_student()
t1 = threading.Thread(target=process_thread, args=('Alice',), name='Thread-A')
t2 = threading.Thread(target=process_thread, args=('Bob',), name='Thread-B')
t1.start()
t2.start()
t1.join()
t2.join()
Hello, Alice (in Thread-A)
Hello, Bob (in Thread-B)

4.进程VS.线程
首先,要实现多任务,通常我们会设计Master-Worker模式,Master负责分配任务,Worker负责执行任务,因此,多任务环境下,通常是一个Master,多个Worker
如果用多进程实现Master-Worker,主进程就是Master,其他进程就是Worker。
如果用多线程实现Master-Worker,主线程就是Master,其他线程就是Worker。
进程模式最大的优点就是稳定性高,因为一个子进程崩溃了,不会影响主进程和其他子进程。(当然主进程挂了所有进程就全挂了,但是Master进程只负责分配任务,挂掉的概率低)著名的Apache最早就是采用多进程模式。
多进程模式的缺点是创建进程的代价大,在Unix/Linux系统下,用fork调用还行,在Windows下创建进程开销巨大。另外,操作系统能同时运行的进程数也是有限的,在内存和CPU的限制下,如果有几千个进程同时运行,操作系统连调度都会成问题。
多线程模式通常比多进程快一点,但是也快不到哪去,而且,多线程模式致命的缺点就是任何一个线程挂掉都可能直接造成整个进程崩溃,因为所有线程共享进程的内存。在Windows上,如果一个线程执行的代码出了问题,你经常可以看到这样的提示:“该程序执行了非法操作,即将关闭”,其实往往是某个线程出了问题,但是操作系统会强制结束整个进程
在Windows下,多线程的效率比多进程要高,所以微软的IIS服务器默认采用多线程模式。由于多线程存在稳定性的问题,IIS的稳定性就不如Apache。为了缓解这个问题,IIS和Apache现在又有多进程+多线程的混合模式。

  • 线程切换
    多任务一旦多到一个限度,就会消耗掉系统所有的资源,结果效率急剧下降,所有任务都做不好:
    我们打个比方,假设你不幸正在准备中考,每天晚上需要做语文、数学、英语、物理、化学这5科的作业,每项作业耗时1小时。
    如果你先花1小时做语文作业,做完了,再花1小时做数学作业,这样,依次全部做完,一共花5小时,这种方式称为单任务模型,或者批处理任务模型。
    假设你打算切换到多任务模型,可以先做1分钟语文,再切换到数学作业,做1分钟,再切换到英语,以此类推,只要切换速度足够快,这种方式就和单核CPU执行多任务是一样的了,以幼儿园小朋友的眼光来看,你就正在同时写5科作业。
    但是,切换作业是有代价的,比如从语文切到数学,要先收拾桌子上的语文书本、钢笔(这叫保存现场),然后,打开数学课本、找出圆规直尺(这叫准备新环境),才能开始做数学作业。操作系统在切换进程或者线程时也是一样的,它需要先保存当前执行的现场环境(CPU寄存器状态、内存页等),然后,把新任务的执行环境准备好(恢复上次的寄存器状态,切换内存页等),才能开始执行。这个切换过程虽然很快,但是也需要耗费时间。如果有几千个任务同时进行,操作系统可能就主要忙着切换任务,根本没有多少时间去执行任务了,这种情况最常见的就是硬盘狂响,点窗口无反应,系统处于假死状态。
  • 计算密集型 vs. IO密集型
    我们可以把任务分为计算密集型和IO密集型。
    计算密集型任务的特点是要进行大量的计算,消耗CPU资源。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。
    计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。
    第二种任务的类型是IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。
    IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。
  • 异步IO
    考虑到CPU和IO之间巨大的速度差异,一个任务在执行的过程中大部分时间都在等待IO操作,单进程单线程模型会导致别的任务无法并行执行,因此,我们才需要多进程模型或者多线程模型来支持多任务并发执行。
    现代操作系统对IO操作已经做了巨大的改进,最大的特点就是支持异步IO。如果充分利用操作系统提供的异步IO支持,就可以用单进程单线程模型来执行多任务,这种全新的模型称为事件驱动模型,Nginx就是支持异步IO的Web服务器,它在单核CPU上采用单进程模型就可以高效地支持多任务。在多核CPU上,可以运行多个进程(数量与CPU核心数相同),充分利用多核CPU。由于系统总的进程数量十分有限,因此操作系统调度非常高效。用异步IO编程模型来实现多任务是一个主要的趋势。
    对应到Python语言,单线程的异步编程模型称为协程,有了协程的支持,就可以基于事件驱动编写高效的多任务程序。我们会在后面讨论如何编写协程。
    5.分布式进程
    点击看详细情况
    6.正则表达式
  • re模块
  • 切分字符串
  • 分组
  • 贪婪匹配
  • 编译
  • 常用内建模块

1.datetime
datetime是Python处理日期和时间的标准库。

  • 获取当前日期和时间
    注意到datetime是模块,datetime模块还包含一个datetime类,通过from datetime import datetime导入的才是datetime这个类。
  • 获取指定日期和时间
  • datetime转换为timestamp
    我们把1970年1月1日 00:00:00 UTC+00:00时区的时刻称为epoch time,记为0(1970年以前的时间timestamp为负数),当前时间就是相对于epoch time的秒数,称为timestamp。
    把一个datetime类型转换为timestamp只需要简单调用timestamp()方法(注意Python的timestamp是一个浮点数,整数位表示秒)。
  • str转换为datetime
    转换方法是通过datetime.strptime()实现,字符串’%Y-%m-%d %H:%M:%S’规定了日期和时间部分的格式。
  • datetime转换为str
  • datetime加减
    加减可以直接用+和-运算符,不过需要导入timedelta这个类。
  • 本地时间转换为UTC时间
    一个datetime类型有一个时区属性tzinfo,但是默认为None,所以无法区分这个datetime到底是哪个时区,除非强行给datetime设置一个时区。
  • 时区转换
    先通过utcnow()拿到当前的UTC时间,再转换为任意时区的时间。
    利用带时区的datetime,通过astimezone()方法,可以转换到任意时区。注:不是必须从UTC+0:00时区转换到其他时区,任何带时区datetime都可以正确转换。
from datetime import datetime,timedelta,timezone
# 获取当前日期和时间
now=datetime.now()
print(now)
# 获取指定日期和时间
dt=datetime(2015,4,19,12,20)#用指定日期时间创建datetime
print(dt)
# datetime转换为timestamp
print(dt.timestamp())
# str转换为datetime
# 很多时候,用户输入的日期和时间是字符串,要处理日期和时间,首先必须把str转换为datetime。转换方法是通过datetime.strptime()实现,需要一个日期和时间的格式化字符串:
cday=datetime.strptime('2015-6-1 18:19:59','%Y-%m-%d %H:%M:%S')
print(cday)
# datetime转换为str
print(now.strftime('%a,%b %d %H:%M'))
# datetime加减
print(now,now+timedelta(hours=10),now-timedelta(days=1),now+timedelta(days=2,hours=12))
# 本地时间转换为UTC时间
tz_utc_8 = timezone(timedelta(hours=8)) # 创建时区UTC+8:00
now1 = datetime.now()
print(now1)
dt2 = now.replace(tzinfo=tz_utc_8) # 强制设置为UTC+8:00
print(dt2)
# 时区转换
# 拿到UTC时间,并强制设置时区为UTC+0:00:
utc_dt = datetime.utcnow().replace(tzinfo=timezone.utc)
print(utc_dt)
# astimezone()将转换时区为北京时间:
bj_dt = utc_dt.astimezone(timezone(timedelta(hours=8)))
print(bj_dt)
# astimezone()将转换时区为东京时间:
tokyo_dt = utc_dt.astimezone(timezone(timedelta(hours=9)))
print(tokyo_dt)
# astimezone()将bj_dt转换时区为东京时间:
tokyo_dt2 = bj_dt.astimezone(timezone(timedelta(hours=9)))
print(tokyo_dt2)
2020-08-05 15:13:10.963397
2015-04-19 12:20:00
1429417200.0
2015-06-01 18:19:59
Wed,Aug 05 15:13
2020-08-05 15:13:10.963397 2020-08-06 01:13:10.963397 2020-08-04 15:13:10.963397 2020-08-08 03:13:10.963397
2020-08-05 15:13:10.988323
2020-08-05 15:13:10.963397+08:00
2020-08-05 07:13:10.988323+00:00
2020-08-05 15:13:10.988323+08:00
2020-08-05 16:13:10.988323+09:00
2020-08-05 16:13:10.988323+09:00

datetime表示的时间需要时区信息才能确定一个特定的时间,否则只能视为本地时间。
如果要存储datetime,最佳方法是将其转换为timestamp再存储,因为timestamp的值与时区完全无关。
2.collections
collections是Python内建的一个集合模块,提供了许多有用的集合类。

  • namedtuple
    tuple之前说了可以表示不变集合,像是一个坐标,但是(1,2)有时候就很那看出来这是表示坐标的,自己定义class又麻烦,于是namedtuple就派上用场了。
from collections import namedtuple
point=namedtuple('Point',['x','y'])
p=point(1,2)
print(p.x,p.y)
# 这样一来,我们用namedtuple可以很方便地定义一种数据类型,它具备tuple的不变性,又可以根据属性来引用,使用十分方便。
# 类似的,如果要用坐标和半径表示一个圆,也可以用namedtuple定义:
# namedtuple('名称', [属性list]):
Circle = namedtuple('Circle', ['x', 'y', 'r'])
c=Circle(1,2,3)
print(c.x,c.y,c.r)
1 2
1 2 3
  • deque
    使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低。
    deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈。
    deque除了实现list的append()和pop()外,还支持appendleft()和popleft(),这样就可以非常高效地往头部添加或删除元素。
from collections import deque
q=deque(['a','b','c'])
print(q)
q.append('x')
print(q)
q.appendleft('y')
print(q)
deque(['a', 'b', 'c'])
deque(['a', 'b', 'c', 'x'])
deque(['y', 'a', 'b', 'c', 'x'])
  • defaultdict
    使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict:
from collections import defaultdict
dd=defaultdict(lambda :'N/A')
dd['key1']='abc'
print(dd['key1'])
print(dd['fey2'])
abc
N/A
  • OrderedDict
    OrderedDict呢,是在对dict做迭代时,使得我们可以确定Key的顺序,但是因为python3.6之后改写了dict的内部算法,所以dict都变得有序了,OrderedDict好像意义不是很大了。
from collections import OrderedDict
d=dict([('a',1),('d',2),('c',3)])
print(d)# dict的Key是无序的(python3.6版本之前),但是因为python3.6之后改写了dict的内部算法,所以dict都变得有序了
od=OrderedDict([('a',1),('d',2),('c',3)])
print(od)# OrderedDict的Key是有序的
od1=OrderedDict()
od1['z']=1
od1['y']=2
od1['x']=3
print(list(od1.keys()))# 按照插入的Key的顺序返回
# OrderedDict可以实现一个FIFO(先进先出)的dict,当容量超出限制时,先删除最早添加的Key:
from collections import OrderedDict
class LastUpdatedOrderedDict(OrderedDict):
    def __init__(self, capacity):
        super(LastUpdatedOrderedDict, self).__init__()
        self._capacity = capacity
    def __setitem__(self, key, value):
        containsKey = 1 if key in self else 0
        if len(self) - containsKey >= self._capacity:
            last = self.popitem(last=False)
            print('remove:', last)
        if containsKey:
            del self[key]
            print('set:', (key, value))
        else:
            print('add:', (key, value))
        OrderedDict.__setitem__(self, key, value)
{'a': 1, 'd': 2, 'c': 3}
OrderedDict([('a', 1), ('d', 2), ('c', 3)])
['z', 'y', 'x']
  • ChainMap
    ChainMap可以把一组dict串起来并组成一个逻辑上的dict。ChainMap本身也是一个dict,但是查找的时候,会按照顺序在内部的dict依次查找。
    应用程序往往都需要传入参数,参数可以通过命令行传入,可以通过环境变量传入,还可以有默认参数。我们可以用ChainMap实现参数的优先级查找,即先查命令行参数,如果没有传入,再查环境变量,如果没有,就使用默认参数。
from collections import ChainMap
import os, argparse
# 构造缺省参数:
defaults = {
    'color': 'red',
    'user': 'guest'
}
# 构造命令行参数:
parser = argparse.ArgumentParser()
parser.add_argument('-u', '--user')
parser.add_argument('-c', '--color')
namespace = parser.parse_args()
command_line_args = { k: v for k, v in vars(namespace).items() if v }
# 组合成ChainMap:
combined = ChainMap(command_line_args, os.environ, defaults)
# 打印参数:
print('color=%s' % combined['color'])
print('user=%s' % combined['user'])

输入参数“-u bob”的结果是:

color=red
user=bob

上面的代码中没有任何参数时,打印出默认参数,当传入命令行参数时,优先使用命令行参数,同时传入命令行参数和环境变量,命令行参数的优先级较高

  • Counter
    Counter是一个简单的计数器。
from collections import Counter
c = Counter()
for ch in 'programming':
    c[ch] = c[ch] + 1
print(c)
c.update('hello') # 也可以一次性update
print(c)
Counter({'r': 2, 'g': 2, 'm': 2, 'p': 1, 'o': 1, 'a': 1, 'i': 1, 'n': 1})
Counter({'r': 2, 'o': 2, 'g': 2, 'm': 2, 'l': 2, 'p': 1, 'a': 1, 'i': 1, 'n': 1, 'h': 1, 'e': 1})

Counter实际上也是dict的一个子类,上面的结果可以看出每个字符出现的次数。
3.base64
Base64是一种用64个字符来表示任意二进制数据的方法。
Base64的原理很简单,首先,准备一个包含64个字符的数组:
[‘A’, ‘B’, ‘C’, … ‘a’, ‘b’, ‘c’, … ‘0’, ‘1’, … ‘+’, ‘/’]
然后,对二进制数据进行处理,每3个字节一组,一共是3x8=24bit,划为4组,每组正好6个bit:
这样我们得到4个数字作为索引,然后查表,获得相应的4个字符,就是编码后的字符串。
https://static.liaoxuefeng.com/files/attachments/949444125467040
所以,Base64编码会把3字节的二进制数据编码为4字节的文本数据,长度增加33%,好处是编码后的文本数据可以在邮件正文、网页等直接显示。
如果要编码的二进制数据不是3的倍数,最后会剩下1个或2个字节怎么办?Base64用\x00字节在末尾补足后,再在编码的末尾加上1个或2个=号,表示补了多少字节,解码的时候,会自动去掉。

import base64
print(base64.b64encode(b'binary\x00string'))
print(base64.b64decode(b'YmluYXJ5AHN0cmluZw=='))
# 由于标准的Base64编码后可能出现字符+和/,在URL中就不能直接作为参数,所以又有一种"url safe"的base64编码,其实就是把字符+和/分别变成-和_:
print(base64.b64encode(b'i\xb7\x1d\xfb\xef\xff'))
print(base64.urlsafe_b64encode(b'i\xb7\x1d\xfb\xef\xff'))
print(base64.urlsafe_b64decode('abcd--__'))
b'YmluYXJ5AHN0cmluZw=='
b'binary\x00string'
b'abcd++//'
b'abcd--__'
b'i\xb7\x1d\xfb\xef\xff'

4.struct
Python提供了一个struct模块来解决bytes和其他二进制数据类型的转换。

# struct的pack函数把任意数据类型变成bytes:
import struct
print(struct.pack('>I', 10240099))
# pack的第一个参数是处理指令,'>I'的意思是:
# >表示字节顺序是big-endian,也就是网络序,I表示4字节无符号整数。
# 后面的参数个数要和处理指令一致。
# unpack把bytes变成相应的数据类型:
print(struct.unpack('>IH', b'\xf0\xf0\xf0\xf0\x80\x80'))
# 根据>IH的说明,后面的bytes依次变为I:4字节无符号整数和H:2字节无符号整数。
b'\x00\x9c@c'
(4042322160, 32896)

5.hashlib

  • MD5
    Python的hashlib提供了常见的摘要算法,如MD5,SHA1等等。什么是摘要算法呢?摘要算法又称哈希算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。
    摘要算法就是通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest,目的是为了发现原始数据是否被人篡改过。
    摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数,计算f(data)很容易,但通过digest反推data却非常困难。而且,对原始数据做一个bit的修改,都会导致计算出的摘要完全不同。
import hashlib
md5 = hashlib.md5()
md5.update('how to use md5 in python hashlib?'.encode('utf-8'))
print(md5.hexdigest())
# 如果数据量很大,可以分块多次调用update(),最后计算的结果是一样的:
import hashlib
md5 = hashlib.md5()
md5.update('how to use md5 in '.encode('utf-8'))
md5.update('python hashlib?'.encode('utf-8'))
print(md5.hexdigest())
print(len('d26a53750bc40b38b65a520292f69306'))
d26a53750bc40b38b65a520292f69306
d26a53750bc40b38b65a520292f69306
32

MD5是最常见的摘要算法,速度很快,生成结果是固定的128 bit字节,通常用一个32位的16进制字符串表示。

  • SHA1
import hashlib
sha1 = hashlib.sha1()
sha1.update('how to use sha1 in '.encode('utf-8'))
sha1.update('python hashlib?'.encode('utf-8'))
print(sha1.hexdigest())
print(len('2c76b57293ce30acef38d98f6046927161b46a44'))
2c76b57293ce30acef38d98f6046927161b46a44
40

SHA1的结果是160 bit字节,通常用一个40位的16进制字符串表示。
比SHA1更安全的算法是SHA256和SHA512,不过越安全的算法不仅越慢,而且摘要长度更长。
摘要算法能应用到什么地方?举个常用例子:
任何允许用户登录的网站都会存储用户登录的用户名和口令。如何存储用户名和口令呢?方法是存到数据库表中:

namepassword
michael123456
bobabc999
alicealice2008

如果以明文保存用户口令,如果数据库泄露,所有用户的口令就落入黑客的手里。此外,网站运维人员是可以访问数据库的,也就是能获取到所有用户的口令。
正确的保存口令的方式是不存储用户的明文口令,而是存储用户口令的摘要,比如MD5:

usernamepassword
michaele10adc3949ba59abbe56e057f20f883e
bobe10adc3949ba59abbe56e057f20f883e
alicee10adc3949ba59abbe56e057f20f883e

当用户登录时,首先计算用户输入的明文口令的MD5,然后和数据库存储的MD5对比,如果一致,说明口令输入正确,如果不一致,口令肯定错误。
6.hmac
通过哈希算法,我们可以验证一段数据是否有效,方法就是对比该数据的哈希值,例如,判断用户口令是否正确,我们用保存在数据库中的password_md5对比计算md5(password)的结果,如果一致,用户输入的口令就是正确的。
为了防止黑客通过彩虹表根据哈希值反推原始口令,在计算哈希的时候,不能仅针对原始输入计算,需要增加一个salt来使得相同的输入也能得到不同的哈希,这样,大大增加了黑客破解的难度。
如果salt是我们自己随机生成的,通常我们计算MD5时采用md5(message + salt)。但实际上,把salt看做一个“口令”,加salt的哈希就是:计算一段message的哈希时,根据不通口令计算出不同的哈希。要验证哈希值,必须同时提供正确的口令。
这实际上就是Hmac算法:Keyed-Hashing for Message Authentication。它通过一个标准算法,在计算哈希的过程中,把key混入计算过程中。
和我们自定义的加salt算法不同,Hmac算法针对所有哈希算法都通用,无论是MD5还是SHA-1。采用Hmac替代我们自己的salt算法,可以使程序算法更标准化,也更安全。

import hmac
message = b'Hello, world!'
key = b'secret'
h = hmac.new(key, message, digestmod='MD5')
# 如果消息很长,可以多次调用h.update(msg)
print(h.hexdigest())
fa4ee7d173f2d97ee79022d1a7355bcf

7.itertools

  • itertools提供的几个“无限”迭代器:count()、cycle()、repeat()
import itertools
natuals=itertools.count(1)
for n in natuals:
    print(n)
#上述代码会打印出自然数序列,根本停不下来,只能按Ctrl+C退出。
cs=itertools.cycle('ABC')
for c in cs:
    print(c)
#'A''B''C'不断的循环输出
ns=itertools.repeat('A',6)
for x in ns:
    print(x)
#repeat()负责把一个元素无限重复下去,不过如果提供第二个参数就可以限定重复次数:

无限序列只有在for迭代时才会无限地迭代下去,如果只是创建了一个迭代对象,它不会事先把无限个元素生成出来,事实上也不可能在内存中创建无限多个元素。
无限序列虽然可以无限迭代下去,但是通常我们会通过takewhile()等函数根据条件判断来截取出一个有限的序列:

import itertools
natuals = itertools.count(1)
ns = itertools.takewhile(lambda x: x <= 10, natuals)
print(list(ns))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  • itertools提供的几个迭代器操作函数更加有用:
    • chain()
    • groupby()
import itertools
# chain()可以把一组迭代对象串联起来,形成一个更大的迭代器:
for c in itertools.chain('ABC','XYZ'):
    print(c)
# groupby()把迭代器中相邻的重复元素挑出来放在一起:
for key,group in itertools.groupby('AAABBCCAAA'):
    print(key,list(group))
# 实际上挑选规则是通过函数完成的,只要作用于函数的两个元素返回的值相等,这两个元素就被认为是在一组的,而函数返回值作为组的key。如果我们要忽略大小写分组,就可以让元素'A'和'a'都返回相同的key:
for key, group in itertools.groupby('AaaBBbcCAAa',lambda c: c.upper()):
    print(key, list(group))
A
B
C
X
Y
Z
A ['A', 'A', 'A']
B ['B', 'B']
C ['C', 'C']
A ['A', 'A', 'A']
A ['A', 'a', 'a']
B ['B', 'B', 'b']
C ['c', 'C']
A ['A', 'A', 'a']

8.contextlib
在Python中,读写文件这样的资源要特别注意,必须在使用完毕后正确关闭它们。正确关闭文件资源的一个方法是使用try…finally:

try:
    f = open('/path/to/file', 'r')
    f.read()
finally:
    if f:
        f.close()

写try…finally非常繁琐。Python的with语句允许我们非常方便地使用资源,而不必担心资源没有关闭,所以上面的代码可以简化为:

with open('/path/to/file', 'r') as f:
    f.read()

并不是只有open()函数返回的fp对象才能使用with语句。实际上,任何对象,只要正确实现了上下文管理,就可以用于with语句。
实现上下文管理是通过__enter__和__exit__这两个方法实现的。例如,下面的class实现了这两个方法:

class Query(object):
    def __init__(self, name):
        self.name = name
    def __enter__(self):
        print('Begin')
        return self
    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type:
            print('Error')
        else:
            print('End') 
    def query(self):
        print('Query info about %s...' % self.name)

这样我们就可以把自己写的资源对象用于with语句:

with Query('Bob') as q:
    q.query()
  • @contextmanager
    编写__enter__和__exit__仍然很繁琐,因此Python的标准库contextlib提供了更简单的写法,上面的代码可以改写如下:
from contextlib import contextmanager
class Query(object):
    def __init__(self, name):
        self.name = name
    def query(self):
        print('Query info about %s...' % self.name)
@contextmanager
def create_query(name):
    print('Begin')
    q = Query(name)
    yield q
    print('End')

@contextmanager这个decorator接受一个generator,用yield语句把with … as var把变量输出出去,然后,with语句就可以正常地工作了:

with create_query('Bob') as q:
    q.query()

很多时候,我们希望在某段代码执行前后自动执行特定代码,也可以用@contextmanager实现。例如:

@contextmanager
def tag(name):
    print("<%s>" % name)
    yield
    print("</%s>" % name)
with tag("h1"):
    print("hello")
    print("world")

上述代码执行结果为:

<h1>
hello
world
</h1>

代码的执行顺序是:
with语句首先执行yield之前的语句,因此打印出<h1>;
yield调用会执行with语句内部的所有语句,因此打印出hello和world;
最后执行yield之后的语句,打印出</h1>。
因此,@contextmanager让我们通过编写generator来简化上下文管理。

  • @closing
    如果一个对象没有实现上下文,我们就不能把它用于with语句。这个时候,可以用closing()来把该对象变为上下文对象。例如,用with语句使用urlopen():
from contextlib import closing
from urllib.request import urlopen
with closing(urlopen('https://www.python.org')) as page:
    for line in page:
        print(line)

closing也是一个经过@contextmanager装饰的generator,这个generator编写起来其实非常简单:

@contextmanager
def closing(thing):
    try:
        yield thing
    finally:
        thing.close()

它的作用就是把任意对象变为上下文对象,并支持with语句。
9.urllib

  • Get
    urllib的request模块可以非常方便地抓取URL内容,也就是发送一个GET请求到指定的页面,然后返回HTTP的响应:
from urllib import request
req = request.Request('http://www.douban.com/')
req.add_header('User-Agent', 'Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25')
with request.urlopen(req) as f:
    print('Status:', f.status, f.reason)
    for k, v in f.getheaders():
        print('%s: %s' % (k, v))
    print('Data:', f.read().decode('utf-8'))
Status: 200 OK
Date: Wed, 05 Aug 2020 15:47:05 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: close
Vary: Accept-Encoding
Vary: Accept-Encoding
X-Xss-Protection: 1; mode=block
X-Douban-Mobileapp: 0
Expires: Sun, 1 Jan 2006 01:00:00 GMT
Pragma: no-cache
Cache-Control: must-revalidate, no-cache, private
Set-Cookie: bid=DEDTa_ngASE; Expires=Thu, 05-Aug-21 15:47:05 GMT; Domain=.douban.com; Path=/
X-DOUBAN-NEWBID: DEDTa_ngASE
X-DAE-App: talion
X-DAE-Instance: default
Server: dae
Strict-Transport-Security: max-age=15552000
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Data: 
<!DOCTYPE html>
<html itemscope itemtype="http://schema.org/WebPage" class="ua-safari ua-mobile ">
  <head>
      <meta charset="UTF-8">
      <title>豆瓣(手机版)</title>
      <meta name="google-site-verification" content="ok0wCgT20tBBgo9_zat2iAcimtN4Ftf5ccsh092Xeyw" />
      <meta name="viewport" content="width=device-width, height=device-height, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0">
      <meta name="format-detection" content="telephone=no">
      <meta name="description" content="读书、看电影、涨知识、学穿搭...,加入兴趣小组,获得达人们的高质量生活经验,找到有相同爱好的小伙伴。">
      <meta name="keywords" content="豆瓣,手机豆瓣,豆瓣手机版,豆瓣电影,豆瓣读书,豆瓣同城">
      <link rel="canonical" href="
https://m.douban.com/">
      <link href="https://img3.doubanio.com/f/talion/20f294507038a0d03718cd15b4defe16ea78d05a/css/card/base.css" rel="stylesheet">
<script>
  var saveKey = '_t_splash'
  var day = 3
  if (Date.now() - window.localStorage.getItem(saveKey) < 1000 * 60 * 60 * 24 * day) {
    window.location.replace('/home_guide')
  } else {
    window.addEventListener('click', function(e) {
      if (e.target.id === 'home_guide') {
        window.localStorage.setItem(saveKey, Date.now())
      }
    })
  }
</script>
      <link rel="stylesheet" href="https://img3.doubanio.com/misc/mixed_static/4f3062503e5a7c84.css">
      <link rel="icon" type="image/png" sizes="16x16" href="https://img3.doubanio.com/f/talion/c970bb0d720963a7392f7dd6c77068bb9925caaf/pics/icon/dou16.png">
      <link rel="icon" type="image/png" sizes="32x32" href="https://img3.doubanio.com/f/talion/2f3c0bc0f35b031d4535fd993ae3936f4e40e6c8/pics/icon/dou32.png">
      <link rel="icon" type="image/png" sizes="48x48" href="https://img3.doubanio.com/f/talion/10a4a913a5715f628e4b598f7f9f2c18621bdcb3/pics/icon/dou48.png">
      <!-- iOS touch icon -->
      <link rel="apple-touch-icon-precomposed" href="https://img3.doubanio.com/f/talion/997f2018d82979da970030a5eb84c77f0123ae5f/pics/icon/m_logo_76.png">
      <link rel="apple-touch-icon-precomposed" sizes="76x76" href="https://img3.doubanio.com/f/talion/997f2018d82979da970030a5eb84c77f0123ae5f/pics/icon/m_logo_76.png">
      <link rel="apple-touch-icon-precomposed" sizes="120x120" href="https://img3.doubanio.com/f/talion/18932a3e71a60ed7150ca2ca7ebf21ddadd7092e/pics/icon/m_logo_120.png">
      <link rel="apple-touch-icon-precomposed" sizes="152x152" href="https://img3.doubanio.com/f/talion/b99497ff8538c54b9ba6f40867da932396ab2562/pics/icon/m_logo_152.png">
      <link rel="apple-touch-icon-precomposed" sizes="167x167" href="https://img3.doubanio.com/f/talion/0c233ada957a95e632f81607e30230d16e8293e8/pics/icon/m_logo_167.png">
      <link rel="apple-touch-icon-precomposed" sizes="180x180" href="https://img3.doubanio.com/f/talion/8e7b9cbd097c02972c4191aa03fdb084524505c4/pics/icon/m_logo_180.png">
      <link rel="apple-touch-icon-precomposed" sizes="200x200" href="https://img3.doubanio.com/f/talion/7c6364aadf368dc0210173c940cfd0f64ceddc66/pics/icon/m_logo_200.png">
      <!-- For Android -->
      <link rel="icon" sizes="128x128" href="https://img3.doubanio.com/f/talion/b99497ff8538c54b9ba6f40867da932396ab2562/pics/icon/m_logo_152.png">
      <link rel="icon" sizes="192x192" href="https://img3.doubanio.com/f/talion/7c6364aadf368dc0210173c940cfd0f64ceddc66/pics/icon/m_logo_200.png">
      <!-- For Web App Manifest -->
      <link type="application/opensearchdescription+xml" rel="search" href="/opensearch"/>
          <!-- hm baidu -->
          <script type="text/javascript">
          var _hmt = _hmt || [];
          (function() {
            var hm = document.createElement("script");
            hm.src = "https://hm.baidu.com/hm.js?6d4a8cfea88fa457c3127e14fb5fabc2";
            var s = document.getElementsByTagName("script")[0];
            s.parentNode.insertBefore(hm, s);
          })();
          _hmt.logTruncate = function(type) {
              _hmt.push(['_trackEvent', 'article', 'truncate', type, 1]);
          }
          </script>
  </head>
  <body ontouchstart="">
    <div class="page">
  <div class="splash">
    <div class="splash-content">
      <div class="splash-pic">
        <img src="https://img3.doubanio.com/f/talion/706ae8511b66f390ae0427daf85df7ddd93ab1b9/pics/card/splash/splash_bg.jpg" />
      </div>
    </div>
    <div class="splash-bottom">
      <div class="splash-text">
        <p class="splash-text-main">来豆瓣,记录你的书影音生活</p>
        <!-- <p>更多书影音讨论在豆瓣App</p> -->
      </div>
      <a class="splash-btn" href="https://m.douban.com/to_app?copy_open=1&url=/recommend_feed&source=splash">下载App</a>
      <a class="splash-link" id="home_guide" href="/home_guide">进入网页版 &gt;</a>
    </div>
  </div>
    </div>
    <script src="https://img3.doubanio.com/f/talion/ee8e0c54293aefb5709ececbdf082f8091ad5e49/js/card/zepto.min.js"></script>
  </body>
</html>
  • Post
    如果要以POST发送一个请求,只需要把参数data以bytes形式传入。
from urllib import request, parse
print('Login to weibo.cn...')
email = input('Email: ')
passwd = input('Password: ')
login_data = parse.urlencode([
    ('username', email),
    ('password', passwd),
    ('entry', 'mweibo'),
    ('client_id', ''),
    ('savestate', '1'),
    ('ec', ''),
    ('pagerefer', 'https://passport.weibo.cn/signin/welcome?entry=mweibo&r=http%3A%2F%2Fm.weibo.cn%2F')
])
req = request.Request('https://passport.weibo.cn/sso/login')
req.add_header('Origin', 'https://passport.weibo.cn')
req.add_header('User-Agent', 'Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25')
req.add_header('Referer', 'https://passport.weibo.cn/signin/login?entry=mweibo&res=wel&wm=3349&r=http%3A%2F%2Fm.weibo.cn%2F')
with request.urlopen(req, data=login_data.encode('utf-8')) as f:
    print('Status:', f.status, f.reason)
    for k, v in f.getheaders():
        print('%s: %s' % (k, v))
    print('Data:', f.read().decode('utf-8'))
Login to weibo.cn...
Email: 1144196409@qq.com
Password: 060216060216
Status: 200 OK
Server: nginx/1.6.1
Date: Wed, 05 Aug 2020 16:00:17 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: close
Vary: Accept-Encoding
Cache-Control: no-cache, must-revalidate
Expires: Sat, 26 Jul 1997 05:00:00 GMT
Pragma: no-cache
Access-Control-Allow-Origin: https://passport.weibo.cn
Access-Control-Allow-Credentials: true
DPOOL_HEADER: 85-144-35-aliyun-core.jpool.sinaimg.cn
Data: {"retcode":50011015,"msg":"\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef","data":{"username":"1144196409@qq.com","errline":704}}
  • Handler
    如果还需要更复杂的控制,比如通过一个Proxy去访问网站,我们需要利用ProxyHandler来处理,示例代码如下:
proxy_handler = urllib.request.ProxyHandler({'http': 'http://www.example.com:3128/'})
proxy_auth_handler = urllib.request.ProxyBasicAuthHandler()
proxy_auth_handler.add_password('realm', 'host', 'username', 'password')
opener = urllib.request.build_opener(proxy_handler, proxy_auth_handler)
with opener.open('http://www.example.com/login.html') as f:
    pass

10.XML
操作XML有两种方法:DOM和SAX。DOM会把整个XML读入内存,解析为树,因此占用内存大,解析慢,优点是可以任意遍历树的节点。SAX是流模式,边读边解析,占用内存小,解析快,缺点是我们需要自己处理事件。
正常情况下,优先考虑SAX,因为DOM实在太占内存。
在Python中使用SAX解析XML非常简洁,通常我们关心的事件是start_element,end_element和char_data,准备好这3个函数,然后就可以解析xml了。
当SAX解析器读到一个节点时:

<a href="/">python</a>

会产生3个事件:
1.start_element事件,在读取<a href="/">时;
2.char_data事件,在读取python时;
3.end_element事件,在读取</a>时。

from xml.parsers.expat import ParserCreate
class DefaultSaxHandler(object):
    def start_element(self, name, attrs):
        print('sax:start_element: %s, attrs: %s' % (name, str(attrs)))
    def end_element(self, name):
        print('sax:end_element: %s' % name)
    def char_data(self, text):
        print('sax:char_data: %s' % text)
xml = r'''<?xml version="1.0"?>
<ol>
    <li><a href="/python">Python</a></li>
    <li><a href="/ruby">Ruby</a></li>
</ol>
'''
handler = DefaultSaxHandler()
parser = ParserCreate()
parser.StartElementHandler = handler.start_element
parser.EndElementHandler = handler.end_element
parser.CharacterDataHandler = handler.char_data
parser.Parse(xml)
sax:start_element: ol, attrs: {}
sax:char_data: 
sax:char_data:     
sax:start_element: li, attrs: {}
sax:start_element: a, attrs: {'href': '/python'}
sax:char_data: Python
sax:end_element: a
sax:end_element: li
sax:char_data: 
sax:char_data:     
sax:start_element: li, attrs: {}
sax:start_element: a, attrs: {'href': '/ruby'}
sax:char_data: Ruby
sax:end_element: a
sax:end_element: li
sax:char_data: 
sax:end_element: ol

11.HTMLParser
HTML本质上是XML的子集,但是HTML的语法没有XML那么严格,所以不能用标准的DOM或SAX来解析HTML。好在Python提供了HTMLParser来非常方便地解析HTML,只需简单几行代码:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from html.parser import HTMLParser
from html.entities import name2codepoint
class MyHTMLParser(HTMLParser):
    def handle_starttag(self, tag, attrs):
        print('<%s>' % tag)
    def handle_endtag(self, tag):
        print('</%s>' % tag)
    def handle_startendtag(self, tag, attrs):
        print('<%s/>' % tag)
    def handle_data(self, data):
        print(data)
    def handle_comment(self, data):
        print('<!--', data, '-->')
    def handle_entityref(self, name):
        print('&%s;' % name)
    def handle_charref(self, name):
        print('&#%s;' % name)
parser = MyHTMLParser()
parser.feed('''<html>
<head></head>
<body>
<!-- test html parser -->
    <p>Some <a href=\"#\">html</a> HTML&nbsp;tutorial...<br>END</p>
</body></html>''')
<html>
<head>
</head>
<body>
<!--  test html parser  -->  
<p>
Some 
<a>
html
</a>
 HTML tutorial...
<br>
END
</p>
</body>
</html>
  • 常用第三方模块

1.Pillow

2.requests

3.chardet

4.psutill

5.virtualenv

  • 图形界面

Python支持多种图形界面的第三方库,包括:Tk、wxWidgets、Qt、GTK等等。

  • Tkinter
    我们编写的Python代码会调用内置的Tkinter,Tkinter封装了访问Tk的接口;Tk是一个图形库,支持多个操作系统,使用Tcl语言开发;Tk会调用操作系统提供的本地GUI接口,完成最终的GUI。
  • 第一个GUI程序
# 第一步是导入Tkinter包的所有内容:
from tkinter import *
# 第二步是从Frame派生一个Application类,这是所有Widget的父容器:
class Application(Frame):
    def __init__(self,master=None):
        Frame.__init__(self,master)
        self.pack()
        self.createWidgets()
    def createWidgets(self):
        self.helloLabel=Label(self,text='Hello World!')
        self.helloLabel.pack()
        self.quitButton=Button(self,text='Quit',command=self.quit)
        self.quitButton.pack()
# 第三步,实例化Application,并启动消息循环
app=Application()
# 设置窗口标题:
app.master.title('Hello World')
# 主消息循环:
app.mainloop()

在GUI中,每个Button、Label、输入框等,都是一个Widget。Frame则是可以容纳其他Widget的Widget,所有的Widget组合起来就是一棵树。
pack()方法把Widget加入到父容器中,并实现布局。pack()是最简单的布局,grid()可以实现更复杂的布局。
在createWidgets()方法中,我们创建一个Label和一个Button,当Button被点击时,触发self.quit()使程序退出。

  • 输入文本
    加入一个文本框,让用户可以输入文本,然后点按钮后,弹出消息对话框。
# 第一步是导入Tkinter包的所有内容:
from tkinter import *
import tkinter.messagebox as messagebox
class Application(Frame):
    def __init__(self, master=None):
        Frame.__init__(self, master)
        self.pack()
        self.createWidgets()
    def createWidgets(self):
        self.nameInput = Entry(self)
        self.nameInput.pack()
        self.alertButton = Button(self, text='Hello', command=self.hello)
        self.alertButton.pack()
    def hello(self):
        name = self.nameInput.get() or 'world'
        messagebox.showinfo('Message', 'Hello, %s' % name)
app = Application()
# 设置窗口标题:
app.master.title('Hello World')
# 主消息循环:
app.mainloop()

当用户点击按钮时,触发hello(),通过self.nameInput.get()获得用户输入的文本后,使用tkMessageBox.showinfo()可以弹出消息对话框。
1.海龟绘图

# 导入turtle包的所有内容:
from turtle import *
# 设置笔刷宽度:
width(4)
# 前进:
forward(200)
# 右转90度:
right(90)
# 笔刷颜色:
pencolor('red')
forward(100)
right(90)
pencolor('green')
forward(200)
right(90)
pencolor('blue')
forward(100)
right(90)
# 调用done()使得窗口等待被关闭,否则将立刻关闭窗口:
done()

调用width()函数可以设置笔刷宽度,调用pencolor()函数可以设置颜色。绘图完成后,记得调用done()函数,让窗口进入消息循环,等待被关闭。否则,由于Python进程会立刻结束,将导致窗口被立刻关闭。

from turtle import *
def drawStar(x, y):
    pu()
    goto(x, y)
    pd()
    # set heading: 0
    seth(0)
    for i in range(5):
        fd(40)
        rt(144)
for x in range(0, 250, 50):
    drawStar(x, 0)
done()
from turtle import *
# 设置色彩模式是RGB:
colormode(255)
lt(90)
lv = 14
l = 120
s = 45
width(lv)
# 初始化RGB颜色:
r = 0
g = 0
b = 0
pencolor(r, g, b)
penup()
bk(l)
pendown()
fd(l)
def draw_tree(l, level):
    global r, g, b
    # save the current pen width
    w = width()
    # narrow the pen width
    width(w * 3.0 / 4.0)
    # set color:
    r = r + 1
    g = g + 2
    b = b + 3
    pencolor(r % 200, g % 200, b % 200)
    l = 3.0 / 4.0 * l
    lt(s)
    fd(l)
    if level < lv:
        draw_tree(l, level + 1)
    bk(l)
    rt(2 * s)
    fd(l)
    if level < lv:
        draw_tree(l, level + 1)
    bk(l)
    lt(s)
    # restore the previous pen width
    width(w)
speed("fastest")
draw_tree(l, 4)
done()

2.turtle库

  • 网络编程

1.TCP/IP简介

2.TCP编程

3.UDP编程

  • 电子邮件

1.SMTP发送邮件

2.POP3收取邮件

  • 访问数据库

1.使用SQLite
Python就内置了SQLite3,所以,在Python中使用SQLite,不需要安装任何东西,直接使用。
要操作关系数据库,首先需要连接到数据库,一个数据库连接称为Connection;
连接到数据库后,需要打开游标,称之为Cursor,通过Cursor执行SQL语句,然后,获得执行结果。
Python定义了一套操作数据库的API接口,任何数据库要连接到Python,只需要提供符合Python标准的数据库驱动即可。
由于SQLite的驱动内置在Python标准库中,所以我们可以直接来操作SQLite数据库。

# 导入SQLite驱动:
import sqlite3
# 连接到SQLite数据库
# 数据库文件是test.db
# 如果文件不存在,会自动在当前目录创建:
conn = sqlite3.connect('test.db')
# 创建一个Cursor:
cursor = conn.cursor()
# 执行一条SQL语句,创建user表:
cursor.execute('create table user (id varchar(20) primary key, name varchar(20))')
# 继续执行一条SQL语句,插入一条记录:
cursor.execute('insert into user (id, name) values (\'1\', \'Michael\')')
# 通过rowcount获得插入的行数:
print(cursor.rowcount)
cursor.execute('select * from user where id=?', ('1',))
# 获得查询结果集:
values = cursor.fetchall()
print(values)
# 关闭Cursor:
cursor.close()
# 提交事务:
conn.commit()
# 关闭Connection:
conn.close()
1
[('1', 'Michael')]

使用Python的DB-API时,只要搞清楚Connection和Cursor对象,打开后一定记得关闭,就可以放心地使用。
使用Cursor对象执行insert,update,delete语句时,执行结果由rowcount返回影响的行数,就可以拿到执行结果。
使用Cursor对象执行select语句时,通过featchall()可以拿到结果集。结果集是一个list,每个元素都是一个tuple,对应一行记录。
如果SQL语句带有参数,那么需要把参数按照位置传递给execute()方法,有几个?占位符就必须对应几个参数,例如:

cursor.execute('select * from user where name=? and pwd=?', ('abc', 'password'))

2.使用MySQL
MySQL的SQL占位符是%s,上述的SQLite的占位符是?,这是一个区别。

# 导入MySQL驱动:
import mysql.connector
# 注意把password设为你的root口令:
conn = mysql.connector.connect(user='root', password='password', database='test')
cursor = conn.cursor()
# 创建user表:
cursor.execute('create table user (id varchar(20) primary key, name varchar(20))')
# 插入一行记录,注意MySQL的占位符是%s:
cursor.execute('insert into user (id, name) values (%s, %s)', ['1', 'Michael'])
print(cursor.rowcount)
# 提交事务:
conn.commit()
cursor.close()
# 运行查询:
cursor = conn.cursor()
cursor.execute('select * from user where id = %s', ('1',))
values = cursor.fetchall()
print(values)
# 关闭Cursor和Connection:
cursor.close()
conn.close()

3.使用SQLAlchemy
用tuple表示一行很难看出表的结构。如果把一个tuple用class实例来表示,就可以更容易地看出表的结构来:

class User(object):
    def __init__(self, id, name):
        self.id = id
        self.name = name
[
    User('1', 'Michael'),
    User('2', 'Bob'),
    User('3', 'Adam')
]

这就是传说中的ORM技术:Object-Relational Mapping,把关系数据库的表结构映射到对象上。
在Python中,最有名的ORM框架是SQLAlchemy。我们来看看SQLAlchemy的用法。首先通过pip安装SQLAlchemy:

$ pip install sqlalchemy

然后,利用上次我们在MySQL的test数据库中创建的user表,用SQLAlchemy来试试:
第一步,导入SQLAlchemy,并初始化DBSession:

# 导入:
from sqlalchemy import Column, String, create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
# 创建对象的基类:
Base = declarative_base()
# 定义User对象:
class User(Base):
    # 表的名字:
    __tablename__ = 'user'
    # 表的结构:
    id = Column(String(20), primary_key=True)
    name = Column(String(20))
# 初始化数据库连接:
engine = create_engine('mysql+mysqlconnector://root:password@localhost:3306/test')
# 创建DBSession类型:
DBSession = sessionmaker(bind=engine)

以上代码完成SQLAlchemy的初始化和具体每个表的class定义。如果有多个表,就继续定义其他class,例如School:

class School(Base):
    __tablename__ = 'school'
    id = ...
    name = ...

create_engine()用来初始化数据库连接。SQLAlchemy用一个字符串表示连接信息:
‘数据库类型+数据库驱动名称://用户名:口令@机器地址:端口号/数据库名’
你只需要根据需要替换掉用户名、口令等信息即可。
下面,我们看看如何向数据库表中添加一行记录。
由于有了ORM,我们向数据库表中添加一行记录,可以视为添加一个User对象:

# 创建session对象:
session = DBSession()
# 创建新User对象:
new_user = User(id='5', name='Bob')
# 添加到session:
session.add(new_user)
# 提交即保存到数据库:
session.commit()
# 关闭session:
session.close()

可见,关键是获取session,然后把对象添加到session,最后提交并关闭。DBSession对象可视为当前数据库连接。
如何从数据库表中查询数据呢?有了ORM,查询出来的可以不再是tuple,而是User对象。SQLAlchemy提供的查询接口如下:

# 创建Session:
session = DBSession()
# 创建Query查询,filter是where条件,最后调用one()返回唯一行,如果调用all()则返回所有行:
user = session.query(User).filter(User.id=='5').one()
# 打印类型和对象的name属性:
print('type:', type(user))
print('name:', user.name)
# 关闭Session:
session.close()

运行结果如下:

type: <class '__main__.User'>
name: Bob

可见,ORM就是把数据库表的行与相应的对象建立关联,互相转换。
由于关系数据库的多个表还可以用外键实现一对多、多对多等关联,相应地,ORM框架也可以提供两个对象之间的一对多、多对多等功能。
例如,如果一个User拥有多个Book,就可以定义一对多关系如下:

class User(Base):
    __tablename__ = 'user'
    id = Column(String(20), primary_key=True)
    name = Column(String(20))
    # 一对多:
    books = relationship('Book')
class Book(Base):
    __tablename__ = 'book'
    id = Column(String(20), primary_key=True)
    name = Column(String(20))
    # “多”的一方的book表是通过外键关联到user表的:
    user_id = Column(String(20), ForeignKey('user.id'))

do_sqlalchemy.py

  • Web开发

1.HTTp协议简介

2.HTML简介

3.WSGI接口

4.使用Web框架

5.使用模板

  • 异步IO

1.协程

2.asyncio

3.async/await

4.aiohttp

  • 使用MicroPython

1.搭建开发环境

2.控制小车

3.遥控小车

4.遥控转向

  • 实战

1.Day1-搭建开发环境
2.Day2-编写Web App骨架
3.Day3-编写ORM
4.Day4-编写Model
5.Day5-编写Web框架
6.Day6-编写配置文件
7.Day7-编写MVC
8.Day8-构建前端
9.Day9-编写API
10.Day10-用户注册和登录
11.Day11-编写日志创建页
12.Day12-编写日志列表页
13.Day13-提升开发效率
14.Day14-完成Web App
15.Day15-部署Web App
16.Day16-编写移动App
FAQ
期末总结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值