人生苦短,我用python

人生苦短,我用Python

廖雪峰的个人网站

缺点

  1. 运行速度慢
  2. 代码不能加密(解释型语言)

入门

变量

py变量本身类型不固定,是一门动态语言,一个变量可以被赋值成不同类型。

比如之前为int,后来为string

判断常量类型可以使用isinstance(x, (int, float))

命名规则

  • 常量
    • 全大写

tips

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
# output
# py中使用r''表示括号中的东西不转义
print "%s %d" % ("",1)

# input
user = raw_input("Enter your name:")

# getHelp
help(funcName)

# Maths
# / 普通除法,结果会是浮点数
# // 地板除法,结果不一定
# ** 双星号

# 空值 None
name = None

# and or not
5 > 3 and 3 > 1

#string
string[index]
string[start:end]


#if
if expression:
if_suite
elif expression:
elif_suite
else:
else_suite

#for iterable
for XX in dict|str|list|set|tuple|generator:
for k,v in aDict.items()

#while
while expression:

变量不需要预先声明类型,在赋值的那一刻被初始化。

字符编码

  • UNICODE
    • 把所有语言都统一到一套编码里
    • 通常2个字节
    • 计算机内存中统一使用UNICODE编码,按需进行转换
  • ASCII
    • 1个字节,通常用来保存英文
  • UTF-8
    • 英文字符1个字节,中文字符3个字节
    • 有一个额外的好处,ASCII编码可以看成是UTF8编码的一部分

list

方括号

有序的集合,可以随时添加和删除其中的元素。 元素可以是任意元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
aList = [1,2,3,4]
#取最后一个元素可以用-1
aList[-1]
#insert
aList.append()
aList.insert(1,'hello')
#pop
aList.pop()
aList.pop(int)

#列表生成
#使用list函数
>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 循环生成
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# 全排列
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

扩展:generator

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
#列表生成的[]改为()
g = (x * x for x in range(1, 11))
#记录生成算法,需要的时候再进行计算
for n in g:
print(g)
next(g)

#如果一个函数中包含yield关键字,这个函数就是一个generator
#generator的函数在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
#next可能跑出StopIteration错误,需要捕获
while True:
try:
XXX
except StopIteration as e:
print("generator return value",e.value)
break
else:
finally:

Tuple

圆括号,不可更改

1
2
3
4
# 元组不可以被更改(内容可以)
aTuple = ('robot',2,3,4)
# 定义一个元素的tuple需要加逗号,否则t就是1这个数了
t = (1,)

字典dict(Map)

大括号

注意key是不可变的(字符串、整数)

1
2
3
4
5
6
7
8
aDict = {'host':'earth'}
aDict['port']=80
aDict.keys()
aDict['port']
# 判断是否in
'port' in aDict
# 删除
aDict.pop('')

set

是一组key的集合,且key不能重复

需要提供一个list作为输入集合,会自动过滤重复项

1
2
3
4
s = set([1,2,3])
s.add()
s.remove()
#可以做数学意义上的& |

Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ClassName(base_class[es]):
"optional documentation string"
static_member_declarations
method_declarations

#example
class FooClass(object):
"test"
version = 0.1
#invoke after construction,self = this
def __init__(self,nm='Hg Dendi'):
func_suite

#etc

dendi = Student();
slots

可以动态给对象或类绑定属性和方法

slots表示限制对象的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

# 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

s = Student()
s.score = 100

Enum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1 
from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

# 2 子类继承Enum
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

day1 = Weekday.Mon
metaclass

控制类的创建行为

1
2
3
4
5
6
7
8
9
10
11
12
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)

class MyList(list, metaclass=ListMetaclass):
pass

>>> L = MyList()
>>> L.add(1)
>> L
[1]

标识符

1
2
_xxx_	系统定义、特殊变量,可以被直接饮用
_xxx private

build-in func

Func
operator.eq() Compare
str(obj) get description
type(obj) getType
repr(obj) or. ‘obj’ 字符串表示
ord(char) 字符的证书表示
chr(int) 整数对应的char
isinstance(x, str) 判断前者是不是后者的类型
map(func,list) 将list中的元素通过func进行转换
reduce(func,list) 将list中的元素通过func进行减少
filter(func,list) 根据func指示的布尔值进行过滤,返回Iterator,一般需要list()
sorted(list,key=func,reverse = bool) 排序,根据key的大小排序
dir() 一个对象所有的方法

buildInFunc

函数

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
# annotation & func
def function_name([arguments]):
"optional documentation string."
function_suite

#etc
def my_abs(x):
if x>=0:
return x
else:
return -x

#可以使用默认参数
def power(a,b=2):
do sth
#默认参数必须指向不变对象!否则多次执行会出现问题
def add_end(L=[]):
L.append('END')
return L
>>> add_end()
['END']
>>> add_end()
['END', 'END']

#可变参数,传入0个或任意个参数,在调用时自动组装为tuple
def cal(*numbers):
sum = 0
for n in numbers:
sum = sum + n*n
return sum

cal(1,2)
cal(0)
aList = [1,2,3]
cal(*aList)

#关键字参数,传入任意个含参数名的参数,在函数内部自动组装为一个dict
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)

>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}

# 命名关键字参数
# 限制关键字参数的名字,比如只接受city和job作为关键字参数
# 命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名参数关键字
# 可变参数后不需要再额外加*,默认变为关键字参数
def person(name,age,*,city=“Beijing”,job)
print(name,age,city,job)

需要注意,默认参数必须指向不变对象,否则会出现默认参数更改后,之后再次调用函数的默认参数为之前更改过的对象。

懒加载函数

比如不需要立即求和,而是在后面的代码中根据需要再进行计算

注意返回的函数不要引用循环变量,或者后续会发生变化的变量

1
2
3
4
5
6
7
8
9
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
f = lazy_sum(1, 3, 5, 7, 9)
f()
匿名函数
1
2
3
4
lambda x:x*x
#就是
def f(x):
return x*x
wrapper

函数也有对象,可以拿到属性.

若想在代码运行期间增加功能,称为装饰器。

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
def log(func):
# 使wrapper和func的__name__相同
@functools.wraps(func)
def wrapper(*args,**kw):
print('call %s():' % func.__name__)
return func(*args,**kw)
return wrapper

#相当于执行了now = log(now)
@log
def now()
print("2017-01-01")
#通过属性拿到函数名'now'
now.__name__

#三层嵌套,可以log加参数
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator

#相当于now = log('execute')(now)
@log('execute')
def now():
print('2015-3-25')
偏函数

把一个函数的某个参数固定住

1
2
3
import functools
int2 = functools.partial(int , base=2)
int2('1000000')

编码

  1. ASCII 英语和数字,一个字节表示一个字符
  2. Unicode 通常两个字节表示一个字符,特殊符号可能需要四个(Python3)
  3. UTF-8 把字符编码成1-6个字节,汉字通常3个字节,英文字母1个字节
1
2
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

文件读取

1
2
3
4
5
6
7
8
f = open('//','r',encoding='gbk')
#更推荐用with来写
with open('','r') as f:
do sth
# 无参即一次读完,可以调用read(size),readline()
f.read()
f.write('error')
f.close()

进程

Linux操作系统提供了一个fork()系统调用,调用一次,返回两次,因为操作系统把当前进程赋值了一份,即创建了一个子进程,故而分别在父进程和子进程内返回了。

其中子进程返回0,而父进程返回子进程的id。因为父进程可以fork出很多子进程,所以父进程要记下每个子进程的id,而子进程只需要调用getppid()就可以拿到父进程的ID

1
2
3
4
5
6
7
8
9
import os

print('Process (%s) start...' % os.getpid())
# Only works on Unix/Linux/Mac:
pid = os.fork()
if pid == 0:
print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
else:
print('I (%s) just created a child process (%s).' % (os.getpid(), pid))
multiprocessing

因为windows没有fork,故而需要一个跨平台的多进程支持库

创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动。

join() 方法用于进程间的同步,表示等待子进程结束后再继续往下运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.')
pool

大量启动子进程,可以用进程池的方式批量创建子进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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())
#pool默认大小为CPU的核数
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.')

线程

任何进程默认就会启动一个线程,我们把它称为主线程。

使用threading开启新线程,threading.Thread(target=loop,name=’’)

Lock

多进程中,同一个变量,各自有一份拷贝存在每个进程中,互不影响。而多线程中,所有变量都由线程共享,故而任何一个变量都可以被任何一个线程修改。

1
2
3
4
5
6
lock = threading.Lock()
lock.acquire()
try:
do something
finally:
lock.release()

Python线程虽然是真正意义上的线程,但是解释器执行代码时,都会有一个GIL锁,

任何Python线程执行前,必须先获得GIL锁,然后执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个全局锁实际上把所有线程的执行代码都给上了锁,所以,即使跑在100核的CPU上,也只能用到1个核

GIL Global Interpreter Lock

Threadlocal

同java中的threadlocal

1
2
local_school = threading.local()
local_school.student

进程和线程比较

多进程稳定性高,一个子进程崩溃了不会影响其他进程。但是创建进程的代价大

多线程速度快一些,但是任何一个线程挂掉都可能直接造成整个代码崩溃,因为所有线程共享进程的内存。比如Windows上的“该程序执行了非法操作,即将关闭”,往往是某个线程出了问题,但是操作系统会强制结束整个进程。

切换是有代价的,多任务一旦多到一个限度,就会消耗掉系统所有的资源,结果效率急剧下降。

  • 保存现场(CPU寄存器状态,内存页等)
  • 准备新环境(恢复上次的寄存器状态,内存页等)

正则表达式

表达 含义
\d num
\w 数字
. 任意一个字符
* 任意个字符(包括0个)
+ 至少一个字符
0或1个字符
{n} n个字符
{n,m} n-m个字符
\s 空格
[] 表示范围,比如[0-9a-zA-Z_]
\ 特殊符号需要在前面加\进行转义
A\ B A或B
^ 行的开头,^\d表示以数字开头
$ 结尾,\d$表示以数字结尾
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
s = 'ABC\\-001'
# 可以使用r前缀,这样就不用考虑转义了
s = r'ABC\-001'

#python使用re库进行正则匹配
import re
re.match(pattern,str)
#成功返回一个Match对象,否则返回None
if re.match(XX,str):
print('OK')
else:
print('Fail')

# 提取子串,用()表示的就是要提取的分组
>>> 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'
# 在Match对象上用group()方法提取出自串
>>> m.group(1)
'010'
>>> m.group(2)
'12345'

python中的正则匹配采用的是贪婪算法,若需要采用非贪婪算法,需要在后面加一个?

比如\d+?

网络编程

TCP

客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import socket

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('www.sina.com.cn',80))
s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')
buffer = []
while True:
d = s.recv(1024)
if d:
buffer.append(d)
else:
break
data = b''.join(buffer)
s.close()

header,html = data.split(b'\r\n\r\n',1)
print(header.decode('utf-8'))
with open('sina.html','wb') as f:
f.write(html)
服务器端
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
def tcplink(sock, addr):
print('Accept new connection from %s:%s...' % addr)
sock.send(b'Welcome!')
while True:
data = sock.recv(1024)
time.sleep(1)
if not data or data.decode('utf-8') == 'exit':
break
sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8'))
sock.close()
print('Connection from %s:%s closed.' % addr)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1',9999))
s.listen(5)
print('Waiting for connection...')
while True:
# 接受一个新连接:
sock, addr = s.accept()
# 创建新线程来处理TCP连接:
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()

# 配套的客户端
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接:
s.connect(('127.0.0.1', 9999))
# 接收欢迎消息:
print(s.recv(1024).decode('utf-8'))
for data in [b'Michael', b'Tracy', b'Sarah']:
# 发送数据:
s.send(data)
print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()

UDP

无连接协议,只需要知道对方的IP和端口号,就可以发送数据包,但是无法确认是否到达。

速度快,但传输数据不可靠。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Server
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定端口:
s.bind(('127.0.0.1', 9999))
print('Bind UDP on 9999...')
while True:
# 接收数据:
data, addr = s.recvfrom(1024)
print('Received from %s:%s.' % addr)
s.sendto(b'Hello, %s!' % data, addr)

# Client
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in [b'Michael', b'Tracy', b'Sarah']:
# 发送数据:
s.sendto(data, ('127.0.0.1', 9999))
# 接收数据:
print(s.recv(1024).decode('utf-8'))
s.close()

数据库

SQLite

轻量级,可嵌入,但是不能承受高并发访问

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
# 导入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))')
<sqlite3.Cursor object at 0x10f8aa260>
# 继续执行一条SQL语句,插入一条记录:
>>> cursor.execute('insert into user (id, name) values (\'1\', \'Michael\')')
<sqlite3.Cursor object at 0x10f8aa260>
# 通过rowcount获得插入的行数:
>>> cursor.rowcount
1
# 关闭Cursor:
>>> cursor.close()
# 提交事务:
>>> conn.commit()
# 关闭Connection:
>>> conn.close()

# 查询
>>> conn = sqlite3.connect('test.db')
>>> cursor = conn.cursor()
# 执行查询语句:
>>> cursor.execute('select * from user where id=?', ('1',))
<sqlite3.Cursor object at 0x10f8aa340>
# 获得查询结果集:
>>> values = cursor.fetchall()
>>> values
[('1', 'Michael')]
>>> cursor.close()
>>> conn.close()

MySQL

为服务器端设计的数据库,能承受高并发访问,占用内存页高

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

异步IO

当代码需要执行一个耗时的IO操作时,并不等待IO结果,然后就去执行其他代码了。一段时间后,当IO返回结果时,再通知CPU进行处理

一般采用一个消息循环,主线程不断的重复“读取消息-处理消息”这一过程

1
2
3
4
loop = get_event_loop()
while True:
event = loop.get_event()
process_event(event)

Coroutine 协程

Coroutine看上去也是函数,但在执行过程中,可以中断,然后执行别的函数,在适当的时候再回来接着执行。

看起来像多线程,但实际上在同一个线程中运行,运行效率极高,且不需要多线程的锁机制。

Python对coroutine的支持是通过generator实现的。Python的yield不但可以返回一个值,还可以接收调用者发出的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def consumer():
r = ''
while True:
# 通过yield拿到消息,处理,并返回
n = yield r
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
r = '200 OK'

def produce(c):
# 启动生成器
c.send(None)
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n)
print('[PRODUCER] Consumer return: %s' % r)
c.close()

c = consumer()
produce(c)

asyncio(async await)

asyncio就是一个消息循环,从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。

同一个线程并发执行两个coroutine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import threading
import asyncio

# 简化为async def hello()
@asyncio.coroutine
def hello():
print('Hello world! (%s)' % threading.currentThread())
# 简化为 await asyncio.sleep(1)
yield from asyncio.sleep(1)
print('Hello again! (%s)' % threading.currentThread())

loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
0%