python概要笔记2
python概要笔记2
chenxin 2017/06 update
IO编程-同步IO,异步IO,回调模式,轮询模式
同步IO/异步IO
你说“来个汉堡”,服务员说等下,然后你站那里等着.之后服务员转身拿给你个汉堡,这就是同步IO.
入股服务员告诉你,汉堡需要等5分钟,等做好了,我们再通知你,这样你可以立刻去干别的事情,这是异步IO。
如果是服务员跑过来找到你,这是回调模式.
如果服务员发短信通知你,你就得不停地检查手机,这是轮询模式。
本章的IO编程都是同步模式,异步IO由于复杂度太高,后续涉及到服务器端程序开发时我们再讨论。
文件读写 with 语句 以及 文件打开方式
Python引入了with语句来自动帮我们调用close()方法:
with open('/path/to/file', 'r') as f:
print(f.read())
这和前面的try ... finally是一样的,但是代码更佳简洁,并且不必调用f.close()方法。
调用read()会一次性读取文件的全部内容
要保险起见,可以反复调用read(size)方法,每次最多读取size个字节的内容。
调用readline()可以每次读取一行内容
调用readlines()一次读取所有内容并按行返回list。
如果文件很小,read()一次性读取最方便;如果不能确定文件大小,反复调用read(size)比较保险;如果是配置文件,调用readlines()最方便:
for line in f.readlines():
print(line.strip()) # 把末尾的'\n'删掉
网友给出的总结:
r 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
rb 以二进制格式...
r+ 打开文件用于读写。文件指针将会放在文件的开头。
rb+ 以二进制格式...
w 打开文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
wb 以二进制格式...
w+ 打开文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。(这个测试好像有问题)
wb+ 以二进制格式...
a 打开文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件进行写入。
ab 以二进制格式...
a+ 打开文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
ab+ 以二进制格式...
教程提示:
读写文件最好做到:1.要么读,要么写,代码简单,性能高.2.顺序读/写,不要seek,性能高.
结论: 所以一般只需要用r, rb, w, wb就够了
StringIO和BytesIO(操作内存数据)
顾名思义就是在内存中读写str,就是StringIO。要把str写入StringIO,我们需要先创建一个StringIO,然后,像文件一样写入即可:
from io import StringIO
f = StringIO()
f.write('hello')
print(f.getvalue()) #hello
再比如:
f = StringIO('Hello!\nHi!\nGoodbye!')
while True:
s = f.readline()
if s == '':
break
print(s.strip())
Hello!
Hi!
Goodbye!
要操作二进制数据,就需要使用BytesIO。
BytesIO实现了在内存中读写bytes,我们创建一个BytesIO,然后写入一些bytes:
from io import BytesIO
f = BytesIO()
f.write('中文'.encode('utf-8')) #6
print(f.getvalue()) #b'\xe4\xb8\xad\xe6\x96\x87'
f.seek(0) # 会把读取或写入的指针放到文件的开头处去(但不建议用,因为使用场景一般都是写了后再读.但StringIO要么用来读,要么用来写,不能同时用,否则容易乱.
比如以下方式就不建议:
f = StringIO();
f.write('Hello World'); #写
s = f.readline(); #读
print s; #这种方法无论如何都读不出f的内容,使用readlines和循环也不行.除非将seek(0)指到头部去才行.其stream position为0(可以通过d.tell()获得),而后执行d.readline()
操作文件和目录
环境变量
os.name
os.uname()
os.environ
os.environ.get('key')
操作文件和目录,os和os.path模块
os.path.abspath('.')查看当前目录的绝对路径
os.path.join('/path/path2','testdir')打印想要创建目录的路径
os.mkdir('/path/path2/testdir')创建目录
os.rmdir('/path/path2/testdir')删除目录
os.path.split('/path/path2/test.txt')拆分目录或文件,后一部分总是最后级别的目录或文件名
os.path.splitext('/path/path2/test.txt')可以得到文件扩展名
os.rename('test.txt','test.py')重命名
os.remove('test.py')删除文件
[x for x in os.walk(os.path.realpath('.'))] 遍历当前目录下所有目录与文件
[x for x in os.listdir('.') if os.path.isdir(x)]列出当前目录下的所有目录
[x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']列出所有的.py文件
shutil模块是os模块的补充,copyfile()函数提供了复制文件的功能
序列化(编码)
pickle(python特有的),更常用的是JSON
我们把变量从内存中变成可存储或传输的过程称之为序列化.
d = dict(name='Bob', age=20, score=88) #使用dict生成一个dict
pickle.dumps(d) #序列化一个对象.把任意对象序列化成一个bytes,然后,就可以把这个bytes写入文件。或者用另一个方法pickle.dump()直接把对象序列化后写入一个file-like Object.
pickle.dump()如下:
f = open('0518.txt', 'wb')
pickle.dump(d, f) #将二进制写入文件
f.close()
pickle.load()方法从一个file-like Object中直接反序列化出对象.
f = open('0518.txt', 'rb')
e = pickle.load(f)
f.close()
e #{'age': 20, 'score': 88, 'name': 'Bob'}
序列化(编码,解码)
JSON (JavaScript Object Notation)
把对象序列化为标准格式,比如XML,但更好的方法是序列化为JSON.
因为JSON表示出来就是一个字符串(数据两边各有一个单引号),可以被所有语言读取,也可以方便地存储到磁盘或者通过网络传输。JSON不仅是标准格式,并且比XML更快,而且可以直接在Web页面中读取,非常方便。
编码:Python对象编码-->Json格式字符串 json.dumps()
解码:Json格式字符串解码-->Python对象 json.loads()
JSON类型 Python类型
{} dict
[] list/tupple
"string" str
1234.56 int或float
true/false True/False
null None
json序列化(编码)
import json
f = open('0518.txt', 'w')
json.dumps(d) #'{"name": "Bill", "score": 88, "age": 20}'
json.dump(d, f) #直接编码成json格式的东西写到文件里去
f.close()
json反序列化(解码)
要把JSON反序列化为Python对象,用loads()把JSON的字符串反序列化,或者对应的load()方法从file-like Object中读取字符串并反序列化.
json_str = '{"age": 20, "score": 88, "name": "Bob"}'
json.loads(json_str) #{'age': 20, 'score': 88, 'name': 'Bob'}
f = open('0518.txt', 'r')
e = json.load(f)
e #{'name': 'Bill', 'score': 88, 'age': 20}
f.close()
json进阶
print(json.dumps(s, default=lambda obj: obj.dict)) #s是一个类的实例...略
举例:
class Student(object):
def init(self, name, age, score):
self.name = name
self.age = age
self.score = score
s = Student('Zhangsan', '20', '98')
dic = json.dumps(s, default=lambda obj: obj.dict) #default是哪种方法来格式化.一般的类实例都有个__dict__默认的.但有__slots__的类例外.
print('以下是json的数据格式')
print(dic) #{"age": "20", "name": "Zhangsan", "score": "98"}
py = json.loads(dic)
print('以下是python的数据格式')
print(py) #{'age': '20', 'name': 'Zhangsan', 'score': '98'}
小结
Python语言特定的序列化模块是pickle,但如果要把序列化搞得更通用、更符合Web标准,就可以使用json模块。
json模块的dumps()和loads()函数是定义非常好的接口。当我们使用时,只需要传入一个必须的参数。但是,当默认的序列化或反序列机制不满足我们的要求时,我们又可以传入更多的参数来定制序列化或反序列化的规则,既做到了接口简单易用,又做到了充分的扩展性和灵活性。
进程和线程
对于操作系统来说,一个任务就是一个进程(Process).
在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread).
多任务的实现有3种方式:多进程模式;多线程模式;多进程+多线程模式。
多进程和多线程的程序涉及到同步、数据共享的问题,编写起来更复杂.
多进程
fork()
fork()系统调用只能用在类unix系统上,win不支持.
fork调用一次,返回两次.父进程返回子进程的pid,而子进程返回0.
pid = os.fork()
subprocess引入外部子进程
import subprocess
r = subprocess.call(['nslookup', 'www.python.org'])
如果子进程还需要输入,则可以通过communicate()方法输入:
p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = p.communicate(b'set q=mx\npython.org\nexit\n')
print(output.decode('utf-8'))
print('Exit code:', p.returncode)
multiprocessing模块的Process类
os.fork()只能用在类unix系统.为了解决垮平台问题,引入了multiprocessing跨平台的多进程模块.
multiprocessing模块引入了一个Process类来代表一个进程对象.
from multiprocessing import Process
p = Process(target=run_proc, args=('test',))
p.start()
p.join()
join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
multiprocessing模块封装了fork()调用,使我们不需要关注fork()的细节.
multiprocessing模块的Pool
如果要启动大量的子进程,可以用进程池的方式批量创建子进程.
from multiprocessing import Pool
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()
对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。
multiprocessing模块的进程间通信Queue
multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据。
我们以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据
from multiprocessing import Process, Queue
def write(q):
print('Process to write: %s' % os.getpid())
for value in ['A', 'B', 'C']:
q.put(value)
def read(q):
while True:
value = q.get(True)
print('Get %s from queue.' % value)
if name=='main':
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
pw.start()
pr.start()
pw.join()
pr.terminate()
多线程
Python的线程是真正的Posix Thread,而不是模拟出来的线程。
threading
一个进程至少有一个线程.线程是操作系统直接支持的执行单元.
python库对线程提供了2个模块:_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。
threading.current_thread().name
t = threading.Thread(target=zidingyi_loop, name='loopThread') #zidingyi_loop是个自定义循环函数
t.start()
t.join()
current_thread()函数,它永远返回当前线程的实例。
主线程实例的名字叫MainThread,子线程的名字在创建时指定。如果不起名字Python就自动给线程命名为Thread-1,Thread-2……
Lock
线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。
lock = threading.Lock()
...
lock.acquire()
...
lock.release()
当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。...所以我们用try...finally来确保锁一定会被释放。
多核CPU能否并行执行多个python线程问题
因为Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁.
所以,在Python中,可以使用多线程,但不要指望能有效利用多核。如果一定要通过多线程利用多核,那只能通过C扩展来实现,不过这样就失去了Python简单易用的特点。
不过,也不用过于担心,Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。
小结
多线程编程,模型复杂,容易发生冲突,必须用锁加以隔离,同时,又要小心死锁的发生。
Python解释器由于设计时有GIL全局锁,导致了多线程无法利用多核。多线程的并发在Python中就是一个美丽的梦。
ThreadLocal
分布式进程
正则表达式
基础
.表示任意1个字符
表示任意个字符(包括0个),用+表示至少1个字符,用?表示0个或1个字符,用{n}表示n个字符,用{n,m}表示n-m个
[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
表示行的开头,\d表示必须以数字开头。
$表示行的结束,\d$表示必须以数字结束。
re 模块
import re
test = 'abc123'
if re.match(r'^a.*3$', test):
print('ok')
else:
print('failed')
切分字符串
re.split(r'[\s,;]+', 'a,b;; c d')
['a', 'b', 'c', 'd']
分组
^(\d{3})-(\d{3,8})$分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码.
group(0)永远是原始字符串,group(1)、group(2)……表示第1、2、……个子串.还有groups().
m = re.match(r'^(\d{3})-(\d{3,8})$', '028-82272588')
print(m.group(0)) #028-82272588
print(m.group(2)) #82272588
print(m.groups()) #('028', '82272588')
编译
可以将需要使用很多次的正则编译一下,之后直接调用,免去了每次都要先编译后调用的消耗.
re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$')
re_telephone.match('010-12345').groups() #('010', '12345')
常用内建模块
datetime
获取当前日期和时间
datatime是模块,还包含一个datetime类.
获取指定日期和时间
dt = datetime(2015, 4, 19, 12, 20) # 用指定日期时间创建datetime
print(dt)
2015-04-19 12:20:00
datetime转换为timestamp
dt = datetime(2015, 4, 19, 12, 20) # 用指定日期时间创建datetime
dt.timestamp() # 把datetime转换为timestamp
1429417200.0
timestamp转换为datetime
t = 1429417200.0
print(datetime.fromtimestamp(t))
2015-04-19 12:20:00
str转换为datetime
cday = datetime.strptime('2015-6-1 18:19:59', '%Y-%m-%d %H:%M:%S')
print(cday)
2015-06-01 18:19:59
datetime转换为str
now = datetime.now()
print(now.strftime('%a, %b %d %H:%M'))
Mon, May 05 16:28
datetime加减
from datetime import datetime, timedelta
now + timedelta(days=2, hours=12)
datetime.datetime(2015, 5, 21, 4, 57, 3, 540997)
collections
collections提供了很多有用的集合类.
namedtuple
deque
defaultdict
OrderDict
Couter
base64
Base64是一种任意二进制到文本字符串的编码方法,常用于在URL、Cookie、网页中传输少量二进制数据。
struct
hashlib
MD5
SHA1
比SHA1更安全的算法是SHA256和SHA512,不过越安全的算法不仅越慢,而且摘要长度更长。
md5经常用在用户注册密码存储,以及文件校验等场景.
摘要算法在很多地方都有广泛的应用。要注意摘要算法不是加密算法,不能用于加密(因为无法通过摘要反推明文),只能用于防篡改,但是它的单向计算特性决定了可以在不存储明文口令的情况下验证用户口令。
itertools
itertools模块提供的全部是处理迭代功能的函数,它们的返回值不是list,而是Iterator,只有用for循环迭代的时候才真正计算。
contextlib
XML
HTMLParser
urllib
常用第三方模块
virtualenv
图形界面
网络编程/socket/邮件
数据库
web开发/HTTP/HTML/WSGI/web框架/模板
底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口,让我们专心用Python编写Web业务。
这个接口就是WSGI:Web Server Gateway Interface。
使用web框架
使用模板
查看当前安装了哪些第三方模块 pip
pip list
如:
(awspython) ubuntu@ip-172-31-44-231:~/.virtualenvs/awspython$ pip list
Package Version
boto 2.49.0
boto3 1.9.179
botocore 1.12.179
docutils 0.14
jmespath 0.9.4
pip 19.1.1
python-dateutil 2.8.0
s3transfer 0.2.1
setuptools 41.0.1
six 1.12.0
urllib3 1.25.3
wheel 0.33.4
查看python安装的所有模块(含自带)
help()
help> modules
Please wait a moment while I gather a list of all available modules...
future abc hmac selectors
...
_compat_pickle boto locale sre_compile
_compression boto3 logging sre_constants
...