导图社区 Python教程思维导图
下图基于廖老师的课程内容制作而成,整体内容比较详细,访问数据库、电子邮件、网络编程、图形界面、常用第三方模块等内容全部都浓缩在下图之中,值得一看。
编辑于2020-02-03 06:31:05Python教程-廖雪峰(python3)
12 常用内建模块
datatime
获取当前日期和时间
datetime.now()返回当前日期和时间,其类型是datetime
datetime是模块,datetime模块还包含一个datetime类,通过from datetime import datetime导入的才是datetime这个类
获取指定日期和时间,可以直接用参数构造一个datetime
datetime转换为timestamp
我们把1970年1月1日 00:00:00 UTC+00:00时区的时刻称为epoch time,记为0(1970年以前的时间timestamp为负数),当前时间就是相对于epoch time的秒数,称为timestamp。
把一个datetime类型转换为timestamp只需要简单调用timestamp()方法
timestamp转换为datetime,使用datetime提供的fromtimestamp()方法
转换为本地时间
转换为UTC标准时区时间
str转换为datetime,通过datetime.strptime()实现,需要一个日期和时间的格式化字符串:
datetime转换为str,转换方法是通过strftime()实现的,同样需要一个日期和时间的格式化字符串:
datetime加减,加减可以直接用+和-运算符,不过需要导入timedelta这个类:
本地时间转换为UTC时间
datetime类型有一个时区属性tzinfo,但是默认为None,所以无法区分这个datetime到底是哪个时区,除非强行给datetime设置一个时区:
时区转换
我们可以先通过utcnow()拿到当前的UTC时间,利用带时区的datetime,通过astimezone()方法,可以转换到任意时区。
collections
collections是Python内建的一个集合模块,提供了许多有用的集合类
namedtuple
用namedtuple可以很方便地定义一种数据类型,它具备tuple的不变性,又可以根据属性来引用
用坐标和半径表示一个圆:
圆心横坐标:Circle.x 圆心纵坐标:Circle.y 半径:Circle.r
deque
使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低。deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈
deque除了实现list的append()和pop()外,还支持appendleft()和popleft(),这样就可以非常高效地往头部添加或删除元素。
defaultdict
使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict:
注意默认值是调用函数返回的,而函数在创建defaultdict对象时传入。
OrderedDict
使用dict时,Key是无序的。在对dict做迭代时,我们无法确定Key的顺序。 如果要保持Key的顺序,可以用OrderedDict:
OrderedDict的Key会按照插入的顺序排列,不是Key本身排序
OrderedDict可以实现一个FIFO(先进先出)的dict,当容量超出限制时,先删除最早添加的Key
ChainMap
ChainMap可以把一组dict串起来并组成一个逻辑上的dict。ChainMap本身也是一个dict,但是查找的时候,会按照顺序在内部的dict依次查找。
传入参数,且参数优先级不同
Counter
Counter是一个简单的计数器,例如,统计字符出现的个数
base64
Base64是一种用64个字符来表示任意二进制数据的方法。
首先,准备一个包含64个字符的数组:
然后,对二进制数据进行处理,每3个字节一组,一共是3x8=24bit,划为4组,每组正好6个bit:
这样我们得到4个数字作为索引,然后查表,获得相应的4个字符,就是编码后的字符串。
Python内置的base64可以直接进行base64的编解码:
如果要编码的二进制数据不是3的倍数,最后会剩下1个或2个字节怎么办?Base64用\x00字节在末尾补足后,再在编码的末尾加上1个或2个=号,表示补了多少字节,解码的时候,会自动去掉。
由于标准的Base64编码后可能出现字符+和/,在URL中就不能直接作为参数,所以又有一种"url safe"的base64编码,其实就是把字符+和/分别变成-和_:
struct
解决bytes和其他二进制数据类型的转换
struct的pack函数把任意数据类型变成bytes
unpack把bytes变成相应的数据类型
hashlib
hashlib提供了常见的摘要算法,如MD5,SHA1等等。摘要算法又称哈希算法、散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。
以常见的摘要算法MD5为例,计算出一个字符串的MD5值:
如果数据量很大,可以分块多次调用update(),最后计算的结果是一样的
摘要算法应用
加密存储用户密码
hmac
使用hmac实现带key的哈希
itertools
contextlib
urllib
urllib提供了一系列用于操作URL的功能
Get
urllib的request模块可以非常方便地抓取URL内容,也就是发送一个GET请求到指定的页面,然后返回HTTP的响应:对豆瓣的一个URLhttps://api.douban.com/v2/book/2129650进行抓取,并返回响应:
可以看到HTTP响应的头和JSON数据:
如果我们要想模拟浏览器发送GET请求,就需要使用Request对象,通过往Request对象添加HTTP头,我们就可以把请求伪装成浏览器。例如,模拟iPhone 6去请求豆瓣首页:
Post
如果要以POST发送一个请求,只需要把参数data以bytes形式传入。
我们模拟一个微博登录,先读取登录的邮箱和口令,然后按照weibo.cn的登录页的格式以username=xxx&password=xxx的编码传入:
Handler
如果还需要更复杂的控制,比如通过一个Proxy去访问网站,我们需要利用ProxyHandler来处理,示例代码如下:
XML
DOM vs SAX
操作XML有两种方法:DOM和SAX。
DOM会把整个XML读入内存,解析为树,因此占用内存大,解析慢,优点是可以任意遍历树的节点。
SAX是流模式,边读边解析,占用内存小,解析快,缺点是我们需要自己处理事件。
正常情况下,优先考虑SAX,因为DOM实在太占内存。
在Python中使用SAX解析XML非常简洁,通常我们关心的事件是start_element,end_element和char_data,准备好这3个函数,然后就可以解析xml了。
<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)
生成XML
HTMLParser
用来解析HTML,HTML本质上是XML的子集,但是HTML的语法没有XML那么严格,所以不能用标准的DOM或SAX来解析HTML。
13 常用第三方模块
所有的第三方模块都会在PyPI - the Python Package Index上注册,只要找到对应的模块名字,即可用pip安装 在安装第三方模块一节中,我们强烈推荐安装Anaconda,安装后,数十个常用的第三方模块就已经就绪,不用pip手动安装。
Pillow
图像处理标准库
操作图像
图像打开、缩放
图像旋转、切片、滤镜、输出文字、调色板
绘图方法
官方文档:https://pillow.readthedocs.org/
requests
处理URL资源
使用requests
通过GET访问一个页面
对于带参数的URL,传入一个dict作为params参数:
无论响应是文本还是二进制内容,我们都可以用content属性获得bytes对象:
对于特定类型的响应,例如JSON,可以直接获取:
需要传入HTTP Header时,我们传入一个dict作为headers参数:
要发送POST请求,只需要把get()方法变成post(),然后传入data参数作为POST请求的数据:
requests默认使用application/x-www-form-urlencoded对POST数据编码。如果要传递JSON数据,可以直接传入json参数:
在读取文件时,注意务必使用'rb'即二进制模式读取,这样获取的bytes长度才是文件的长度 把post()方法替换为put(),delete()等,就可以以PUT或DELETE方式请求资源。
除了能轻松获取响应内容外,requests对获取HTTP响应的其他信息也非常简单。例如,获取响应头:
requests对Cookie做了特殊处理,使得我们不必解析Cookie就可以轻松获取指定的Cookie:
要在请求中传入Cookie,只需准备一个dict传入cookies参数:
指定超时,传入以秒为单位的timeout参数:
r = requests.get(url, timeout=2.5) # 2.5秒后超时
chardet
检测编码
安装chardet
pip install chardet
使用chardet
检测编码
检测出的编码是ascii,注意到还有个confidence字段,表示检测的概率是1.0(即100%)。
https://chardet.readthedocs.io/en/latest/supported-encodings.html
psutil
psutil = process and system utilities,它不仅可以通过一两行代码实现系统监控,还可以跨平台使用,支持Linux/UNIX/OSX/Windows等
安装psutil
$ pip install psutil
获取CPU信息
统计CPU的用户/系统/空闲时间:
获取内存信息
psutil.virtual_memory()
psutil.swap_memory()
获取磁盘信息
psutil.disk_partitions() # 磁盘分区信息
psutil.disk_usage('/') # 磁盘使用情况
psutil.disk_io_counters() # 磁盘IO
获取网络信息
psutil可以获取网络接口和网络连接信息:
psutil.net_io_counters() # 获取网络读写字节/包的个数
psutil.net_if_addrs() # 获取网络接口信息
psutil.net_if_stats() # 获取网络接口状态
要获取当前网络连接信息,使用net_connections(): psutil.net_connections()
获取进程信息
psutil.pids() # 所有进程ID
p.name() # 进程名称
p.exe() # 进程exe路径
p.cwd() # 进程工作目录
p.cmdline() # 进程启动的命令行
psutil还提供了一个test()函数,可以模拟出ps命令的效果:
14 virtualenv
用来创建独立的python运行环境
pip安装virtualenv(pip3 install virtualenv)
1 创建目录
2 创建一个独立的python运行环境,命名为venv
命令virtualenv就可以创建一个独立的Python运行环境,我们还加上了参数--no-site-packages,这样,已经安装到系统Python环境中的所有第三方包都不会复制过来,这样,我们就得到了一个不带任何第三方包的“干净”的Python运行环境。
新建的Python环境被放到当前目录下的venv目录。有了venv这个Python环境,可以用source进入该环境:
3 正常安装第三方包,并运行python命令
在venv环境下,用pip安装的包都被安装到venv这个环境下,系统Python环境不受任何影响。也就是说,venv环境是专门针对myproject这个应用创建的。
4 退出当前的venv环境,使用deactivate命令:
原理就是把系统Python复制一份到virtualenv的环境,用命令source venv/bin/activate进入一个virtualenv环境时,virtualenv会修改相关环境变量,让命令python和pip均指向当前的virtualenv环境。
15 图形界面
图形界面
图形界面第三方库
Tk
wxWidgets
QT
GTK
python自带的库是支持TK的Tkinter,无需安装
Tkinter
1 导入Tkinter包所有内容
from tkinter import *
2 从Frame派生一个Application类,这是所有Widget的父容器:
在GUI中,每个Button、Label、输入框等,都是一个Widget。Frame则是可以容纳其他Widget的Widget,所有的Widget组合起来就是一棵树。
在GUI中,每个Button、Label、输入框等,都是一个Widget。Frame则是可以容纳其他Widget的Widget,所有的Widget组合起来就是一棵树。
pack()方法把Widget加入到父容器中,并实现布局。pack()是最简单的布局,grid()可以实现更复杂的布局。
在createWidgets()方法中,我们创建一个Label和一个Button,当Button被点击时,触发self.quit()使程序退出
3 实例化Application,并启动消息循环:
16 网络编程
网络通信是两台计算机上的两个进程之间的通信。比如,浏览器进程和新浪服务器上的某个Web服务进程在通信,而QQ进程是和腾讯的某个服务器上的某个进程在通信。 用Python进行网络编程,就是在Python程序本身这个进程内,连接别的服务器进程的通信端口进行通信。
TCP/IP简介
IP协议负责把数据从一台计算机通过网络发送到另一台计算机。数据被分割成一小块一小块,然后通过IP包发送出去。由于互联网链路复杂,两台计算机之间经常有多条线路,因此,路由器就负责决定如何把一个IP包转发出去。IP包的特点是按块发送,途径多个路由,但不保证能到达,也不保证顺序到达。
TCP协议则是建立在IP协议之上的。TCP协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP协议会通过握手建立连接,然后,对每个IP包编号,确保对方按顺序收到,如果包丢掉了,就自动重发。
许多常用的更高级的协议都是建立在TCP协议基础上的,比如用于浏览器的HTTP协议、发送邮件的SMTP协议等。
一个TCP报文除了包含要传输的数据外,还包含源IP地址和目标IP地址,源端口和目标端口。
端口:在两台计算机通信时,只发IP地址是不够的,因为同一台计算机上跑着多个网络程序。一个TCP报文来了之后,到底是交给浏览器还是QQ,就需要端口号来区分。每个网络程序都向操作系统申请唯一的端口号,这样,两个进程在两台计算机之间建立网络连接就需要各自的IP地址和各自的端口号。
一个进程也可能同时与多个计算机建立链接,因此它会申请很多端口。
TCP编程
socket
Socket是网络编程的一个抽象概念。通常我们用一个Socket表示“打开了一个网络链接”,而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可。
客户端
大多数连接都是可靠的TCP连接。创建TCP连接时,主动发起连接的叫客户端,被动响应连接的叫服务器。 当我们在浏览器中访问新浪时,我们自己的计算机就是客户端,浏览器会主动向新浪的服务器发起连接。如果一切顺利,新浪的服务器接受了我们的连接,一个TCP连接就建立起来的,后面的通信就是发送网页内容了
创建一个基于TCP连接的Socket,可以这样做:
创建Socket时,AF_INET指定使用IPv4协议,如果要用更先进的IPv6,就指定为AF_INET6。SOCK_STREAM指定使用面向流的TCP协议,这样,一个Socket对象就创建成功,但是还没有建立连接。
连接新浪服务器的代码如下:
参数是一个tuple,包含地址和端口号。
建立TCP连接后,我们就可以向新浪服务器发送请求,要求返回首页的内容:
接收新浪服务器返回的数据
接收数据时,调用recv(max)方法,一次最多接收指定的字节数,因此,在一个while循环中反复接收,直到recv()返回空数据,表示接收完毕,退出循环。
接收完数据后,调用close()方法关闭Socket,这样,一次完整的网络通信就结束了:
接收到的数据包括HTTP头和网页本身,我们只需要把HTTP头和网页分离一下,把HTTP头打印出来,网页内容保存到文件:
服务器
服务器进程首先要绑定一个端口并监听来自其他客户端的连接。如果某个客户端连接过来了,服务器就与该客户端建立Socket连接,随后的通信就靠这个Socket连接了。 服务器会打开固定端口(比如80)监听,每来一个客户端连接,就创建该Socket连接。由于服务器会有大量来自客户端的连接,所以,服务器要能够区分一个Socket连接是和哪个客户端绑定的。一个Socket依赖4项:服务器地址、服务器端口、客户端地址、客户端端口来唯一确定一个Socket。 服务器还需要同时响应多个客户端的请求,所以,每个连接都需要一个新的进程或者新的线程来处理,否则,服务器一次就只能服务一个客户端了。
创建一个基于IPv4和TCP协议的Socket:
绑定端口
我们要绑定监听的地址和端口。服务器可能有多块网卡,可以绑定到某一块网卡的IP地址上,也可以用0.0.0.0绑定到所有的网络地址,还可以用127.0.0.1绑定到本机地址。127.0.0.1是一个特殊的IP地址,表示本机地址,如果绑定到这个地址,客户端必须同时在本机运行才能连接,也就是说,外部的计算机无法连接进来。 端口号需要预先指定。因为我们写的这个服务不是标准服务,所以用9999这个端口号。请注意,小于1024的端口号必须要有管理员权限才能绑定
调用listen()方法开始监听端口,传入的参数指定等待连接的最大数量:
服务器程序通过一个永久循环来接受来自客户端的连接,accept()会等待并返回一个客户端的连接:
每个连接都必须创建新线程(或进程)来处理,否则,单线程在处理连接的过程中,无法接受其他客户端的连接:
连接建立后,服务器首先发一条欢迎消息,然后等待客户端数据,并加上Hello再发送给客户端。如果客户端发送了exit字符串,就直接关闭连接。
客户端程序:
UDP编程
和TCP区别
TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据。相对TCP,UDP则是面向无连接的协议
使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了
虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。
步骤
服务器
和TCP类似,使用UDP的通信双方也分为客户端和服务器。服务器首先需要绑定端口:
SOCK_DGRAM指定了这个Socket的类型是UDP。
绑定端口和TCP一样,但是不需要调用listen()方法,而是直接接收来自任何客户端的数据:
recvfrom()方法返回数据和客户端的地址与端口,这样,服务器收到数据后,直接调用sendto()就可以把数据用UDP发给客户端。
客户端
客户端使用UDP时,首先仍然创建基于UDP的Socket,然后,不需要调用connect(),直接通过sendto()给服务器发数据:
17 电子邮件
编写程序来发送和接收邮件,本质上就是: 1 编写MUA把邮件发到MTA; 2 编写MUA从MDA上收邮件
SMTP发送邮件
python对SMTP支持有smtplib和email两个模块,email负责构造邮件,smtplib负责发送邮件。
步骤
1 构造邮件
注意到构造MIMEText对象时,第一个参数就是邮件正文,第二个参数是MIME的subtype,传入'plain'表示纯文本,最终的MIME就是'text/plain',最后一定要用utf-8编码保证多语言兼容性。
2 通过SMTP发送出去
我们用set_debuglevel(1)就可以打印出和SMTP服务器交互的所有信息。SMTP协议就是简单的文本命令和响应。login()方法用来登录SMTP服务器,sendmail()方法就是发邮件,由于可以一次发给多个人,所以传入一个list,邮件正文是一个str,as_string()把MIMEText对象变成str。
3 必须把From、To和Subject添加到MIMEText中,才是一封完整的邮件:
from email import encoders from email.header import Header from email.mime.text import MIMEText from email.utils import parseaddr, formataddr import smtplib def _format_addr(s): name, addr = parseaddr(s) return formataddr((Header(name, 'utf-8').encode(), addr)) from_addr = input('From: ') password = input('Password: ') to_addr = input('To: ') smtp_server = input('SMTP server: ') msg = MIMEText('hello, send by Python...', 'plain', 'utf-8') msg['From'] = _format_addr('Python爱好者 <%s>' % from_addr) msg['To'] = _format_addr('管理员 <%s>' % to_addr) msg['Subject'] = Header('来自SMTP的问候……', 'utf-8').encode() server = smtplib.SMTP(smtp_server, 25) server.set_debuglevel(1) server.login(from_addr, password) server.sendmail(from_addr, [to_addr], msg.as_string()) server.quit()
我们编写了一个函数_format_addr()来格式化一个邮件地址。注意不能简单地传入name <addr@example.com>,因为如果包含中文,需要通过Header对象进行编码。 msg['To']接收的是字符串而不是list,如果有多个邮件地址,用,分隔即可。
发送HTML邮件
如果我们要发送HTML邮件,而不是普通的纯文本文件怎么办?方法很简单,在构造MIMEText对象时,把HTML字符串传进去,再把第二个参数由plain变为html就可以了:
发送附件
带附件的邮件可以看做包含若干部分的邮件:文本和各个附件本身,所以,可以构造一个MIMEMultipart对象代表邮件本身,然后往里面加上一个MIMEText作为邮件正文,再继续往里面加上表示附件的MIMEBase对象即可:
# 邮件对象: msg = MIMEMultipart() msg['From'] = _format_addr('Python爱好者 <%s>' % from_addr) msg['To'] = _format_addr('管理员 <%s>' % to_addr) msg['Subject'] = Header('来自SMTP的问候……', 'utf-8').encode() # 邮件正文是MIMEText: msg.attach(MIMEText('send with file...', 'plain', 'utf-8')) # 添加附件就是加上一个MIMEBase,从本地读取一个图片: with open('/Users/michael/Downloads/test.png', 'rb') as f: # 设置附件的MIME和文件名,这里是png类型: mime = MIMEBase('image', 'png', filename='test.png') # 加上必要的头信息: mime.add_header('Content-Disposition', 'attachment', filename='test.png') mime.add_header('Content-ID', '<0>') mime.add_header('X-Attachment-Id', '0') # 把附件的内容读进来: mime.set_payload(f.read()) # 用Base64编码: encoders.encode_base64(mime) # 添加到MIMEMultipart: msg.attach(mime)
按正常发送流程把msg(注意类型已变为MIMEMultipart)发送出去,就可以收到如下带附件的邮件:
发送图片
要把图片嵌入到邮件正文中,我们只需按照发送附件的方式,先把邮件作为附件添加进去,然后,在HTML中通过引用src="cid:0"就可以把附件作为图片嵌入了。如果有多个图片,给它们依次编号,然后引用不同的cid:x即可。
把上面代码加入MIMEMultipart的MIMEText从plain改为html,然后在适当的位置引用图片:
同时支持HTML和Plain格式
在发送HTML的同时再附加一个纯文本,如果收件人无法查看HTML格式的邮件,就可以自动降级查看纯文本邮件。 利用MIMEMultipart就可以组合一个HTML和Plain,要注意指定subtype是alternative:
msg = MIMEMultipart('alternative') msg['From'] = ... msg['To'] = ... msg['Subject'] = ... msg.attach(MIMEText('hello', 'plain', 'utf-8')) msg.attach(MIMEText('<html><body><h1>Hello</h1></body></html>', 'html', 'utf-8')) # 正常发送msg对象...
加密SMTP
使用标准的25端口连接SMTP服务器时,使用的是明文传输,发送邮件的整个过程可能会被窃听。要更安全地发送邮件,可以加密SMTP会话,实际上就是先创建SSL安全连接,然后再使用SMTP协议发送邮件。
Gmail,提供的SMTP服务必须要加密传输。
只需要在创建SMTP对象后,立刻调用starttls()方法,就创建了安全连接。后面的代码和前面的发送邮件代码完全一样。
POP3收取邮件
Python内置一个poplib模块,实现了POP3协议,可以直接用来收邮件。
POP3协议收取的不是一个已经可以阅读的邮件本身,而是邮件的原始文本,这和SMTP协议很像,SMTP发送的也是经过编码后的一大段文本。 要把POP3收取的文本变成可以阅读的邮件,还需要用email模块提供的各种类来解析原始文本,变成可阅读的邮件对象。
第一步:用poplib把邮件的原始文本下载到本地;
第二步:用email解析原始文本,还原为邮件对象。
18 访问数据库
数据库类别
付费的商用数据库
Oracle
SQL Server,微软自家产品,Windows定制专款
DB2,IBM的产品,听起来挺高端;
Sybase,曾经跟微软是好基友,后来关系破裂,现在家境惨淡。
免费的开源数据库
MySQL,大家都在用,一般错不了
PostgreSQL,学术气息有点重,其实挺不错,但知名度没有MySQL高
sqlite,嵌入式数据库,适合桌面和移动应用
使用SQLite
SQLite是一种嵌入式数据库,它的数据库就是一个文件。由于SQLite本身是C写的,而且体积很小,所以,经常被集成到各种应用程序中,甚至在iOS和Android的App中都可以集成。 Python就内置了SQLite3,所以,在Python中使用SQLite,不需要安装任何东西,直接使用。
基本概念
表是数据库中存放关系数据的集合,一个数据库里面通常都包含多个表,比如学生的表,班级的表,学校的表,等等。表和表之间通过外键关联。
要操作关系数据库,首先需要连接到数据库,一个数据库连接称为Connection;
连接到数据库后,需要打开游标,称之为Cursor,通过Cursor执行SQL语句,然后,获得执行结果。
Python定义了一套操作数据库的API接口,任何数据库要连接到Python,只需要提供符合Python标准的数据库驱动即可。
交互
import sqlite3
# 导入SQLite驱动:
conn = sqlite3.connect('test.db')
# 连接到SQLite数据库 # 数据库文件是test.db # 如果文件不存在,会自动在当前目录创建:
cursor = conn.cursor()
# 创建一个Cursor:
cursor.execute('create table user (id varchar(20) primary key, name varchar(20))')
# 执行一条SQL语句,创建user表:
cursor.execute('insert into user (id, name) values (\'1\', \'Michael\')')
# 继续执行一条SQL语句,插入一条记录:
cursor.rowcount
# 通过rowcount获得插入的行数:
cursor.close()
# 关闭Cursor:
conn.commit()
# 提交事务:
conn.close()
# 关闭Connection:
查询记录
>>> 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()
注意
使用Python的DB-API时,只要搞清楚Connection和Cursor对象,打开后一定记得关闭,就可以放心地使用。
使用Cursor对象执行insert,update,delete语句时,执行结果由rowcount返回影响的行数,就可以拿到执行结果。
使用Cursor对象执行select语句时,通过featchall()可以拿到结果集。结果集是一个list,每个元素都是一个tuple,对应一行记录。
如果SQL语句带有参数,那么需要把参数按照位置传递给execute()方法,有几个?占位符就必须对应几个参数,例如:
使用MySQL
MySQL是Web世界中使用最广泛的数据库服务器。SQLite的特点是轻量级、可嵌入,但不能承受高并发访问,适合桌面和移动应用。而MySQL是为服务器端设计的数据库,能承受高并发访问,同时占用的内存也远远大于SQLite。 MySQL内部有多种数据库引擎,最常用的引擎是支持数据库事务的InnoDB。
安装MySQL
安装时,MySQL会提示输入root用户的口令,请务必记清楚。如果怕记不住,就把口令设置为password。
在Windows上,安装时请选择UTF-8编码,以便正确地处理中文。
在Mac或Linux上,需要编辑MySQL的配置文件,把数据库默认的编码全部改为UTF-8。MySQL的配置文件默认存放在/etc/my.cnf或者/etc/mysql/my.cnf:
安装MySQL驱动
由于MySQL服务器以独立的进程运行,并通过网络对外服务,所以,需要支持Python的MySQL驱动来连接到MySQL服务器。MySQL官方提供了mysql-connector-python驱动,但是安装的时候需要给pip命令加上参数--allow-external:
$ pip install mysql-connector-python --allow-external mysql-connector-python
如果上面的命令安装失败,可以试试另一个驱动:
$ pip install mysql-connector
由于Python的DB-API定义都是通用的,所以,操作MySQL的数据库代码和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']) >>> cursor.rowcount 1 # 提交事务: >>> conn.commit() >>> cursor.close() # 运行查询: >>> cursor = conn.cursor() >>> cursor.execute('select * from user where id = %s', ('1',)) >>> values = cursor.fetchall() >>> values [('1', 'Michael')] # 关闭Cursor和Connection: >>> cursor.close() True >>> conn.close()
使用SQLAlchemy
SQLAlchemy框架,ORM技术:Object-Relational Mapping,把关系数据库的表结构映射到对象上。
19 web开发
HTTP协议简介
在Web应用中,服务器把网页传给浏览器,实际上就是把网页的HTML代码发送给浏览器,让浏览器显示出来。
HTML是一种用来定义网页的文本,会HTML,就可以编写网页; HTTP是在网络上传输HTML的协议,用于浏览器和服务器的通信。
网页开发者工具
Elements显示网页的结构,Network显示浏览器和服务器的通信。我们点Network,确保第一个小红灯亮着,Chrome就会记录所有浏览器和服务器之间的通信:
Network
Request Headers 点击右侧的view source,我们就可以看到浏览器发给服务器的请求:
GET / HTTP/1.1
GET表示一个读取请求,将从服务器获得网页数据,/表示URL的路径,URL总是以/开头,/就表示首页,最后的HTTP/1.1指示采用的HTTP协议版本是1.1。
Host: www.sina.com.cn
表示请求的域名是www.sina.com.cn。如果一台服务器有多个网站,服务器就需要通过Host来区分浏览器请求的是哪个网站。
Response Headers 点击view source,显示服务器返回的原始响应数据: HTTP响应分为Header和Body两部分(Body是可选项),我们在Network中看到的Header最重要的几行如下:
200 OK
200表示一个成功的响应,后面的OK是说明。失败的响应有404 Not Found:网页不存在,500 Internal Server Error:服务器内部出错,等等。
Content-Type: text/html
Content-Type指示响应的内容,这里是text/html表示HTML网页。请注意,浏览器就是依靠Content-Type来判断响应的内容是网页还是图片,是视频还是音乐。浏览器并不靠URL来判断响应的内容,所以,即使URL是http://example.com/abc.jpg,它也不一定就是图片。
HTTP响应的Body就是HTML源码,我们在菜单栏选择“视图”,“开发者”,“查看网页源码”就可以在浏览器中直接查看HTML源码:
当浏览器读取到新浪首页的HTML源码后,它会解析HTML,显示页面,然后,根据HTML里面的各种链接,再发送HTTP请求给新浪服务器,拿到相应的图片、视频、Flash、JavaScript脚本、CSS等各种资源,最终显示出一个完整的页面。所以我们在Network下面能看到很多额外的HTTP请求。
HTTP请求
步骤1:浏览器首先向服务器发送HTTP请求,请求包括:
方法:GET还是POST,GET仅请求资源,POST会附带用户数据; 路径:/full/url/path; 域名:由Host头指定:Host: www.sina.com.cn 以及其他相关的Header; 如果是POST,那么请求还包括一个Body,包含用户数据。
步骤2:服务器向浏览器返回HTTP响应,响应包括:
响应代码:200表示成功,3xx表示重定向,4xx表示客户端发送的请求有错误,5xx表示服务器端处理时发生了错误; 响应类型:由Content-Type指定,例如:Content-Type: text/html;charset=utf-8表示响应类型是HTML文本,并且编码是UTF-8,Content-Type: image/jpeg表示响应类型是JPEG格式的图片; 以及其他相关的Header; 通常服务器的HTTP响应会携带内容,也就是有一个Body,包含响应的内容,网页的HTML源码就在Body中。
步骤3:如果浏览器还需要继续向服务器请求其他资源,比如图片,就再次发出HTTP请求,重复步骤1、2。
Web采用的HTTP协议采用了非常简单的请求-响应模式,从而大大简化了开发。当我们编写一个页面时,我们只需要在HTTP响应中把HTML发送出去,不需要考虑如何附带图片、视频等,浏览器如果需要请求图片和视频,它会发送另一个HTTP请求,因此,一个HTTP请求只处理一个资源。
HTTP格式
每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的。
HTTP GET请求的格式:
GET /path HTTP/1.1 Header1: Value1 Header2: Value2 Header3: Value3
每个Header一行一个,换行符是\r\n。
HTTP POST请求的格式:
POST /path HTTP/1.1 Header1: Value1 Header2: Value2 Header3: Value3 body data goes here...
当遇到连续两个\r\n时,Header部分结束,后面的数据全部是Body。
HTTP响应的格式:
200 OK Header1: Value1 Header2: Value2 Header3: Value3 body data goes here...
HTTP响应如果包含body,也是通过\r\n\r\n来分隔的。请再次注意,Body的数据类型由Content-Type头来确定,如果是网页,Body就是文本,如果是图片,Body就是图片的二进制数据。
当存在Content-Encoding时,Body数据是被压缩的,最常见的压缩方式是gzip,所以,看到Content-Encoding: gzip时,需要将Body数据先解压缩,才能得到真正的数据。压缩的目的在于减少Body的大小,加快网络传输。
要详细了解HTTP协议,推荐“HTTP: The Definitive Guide”一书(http://shop.oreilly.com/product/9781565925090.do),非常不错,有中文译本: HTTP权威指南(http://t.cn/R7FguRq)
HTML简介
网页就是HTML?这么理解大概没错。因为网页中不但包含文字,还有图片、视频、Flash小游戏,有复杂的排版、动画效果,所以,HTML定义了一套语法规则,来告诉浏览器如何把一个丰富多彩的页面显示出来。
最简单的HTML
<html> <head> <title>Hello</title> </head> <body> <h1>Hello, world!</h1> </body> </html>
HTML文档就是一系列的Tag组成,最外层的Tag是<html>。规范的HTML也包含<head>...</head>和<body>...</body>(注意不要和HTTP的Header、Body搞混了),由于HTML是富文档模型,所以,还有一系列的Tag用来表示链接、图片、表格、表单等等。
CSS简介
CSS是Cascading Style Sheets(层叠样式表)的简称,CSS用来控制HTML里的所有元素如何展现,比如,给标题元素<h1>加一个样式,变成48号字体,灰色,带阴影:
<html> <head> <title>Hello</title> <style> h1 { color: #333333; font-size: 48px; text-shadow: 3px 3px 3px #666666; } </style> </head> <body> <h1>Hello, world!</h1> </body> </html>
JavaScript简介
JavaScript虽然名称有个Java,但它和Java真的一点关系没有。JavaScript是为了让HTML具有交互性而作为脚本语言添加的,JavaScript既可以内嵌到HTML中,也可以从外部链接到HTML中。如果我们希望当用户点击标题时把标题变成红色,就必须通过JavaScript来实现:
<html> <head> <title>Hello</title> <style> h1 { color: #333333; font-size: 48px; text-shadow: 3px 3px 3px #666666; } </style> <script> function change() { document.getElementsByTagName('h1')[0].style.color = '#ff0000'; } </script> </head> <body> <h1 onclick="change()">Hello, world!</h1> </body> </html>
子主题
讲解HTML、CSS和JavaScript就可以写3本书,对于优秀的Web开发人员来说,精通HTML、CSS和JavaScript是必须的,这里推荐一个在线学习网站w3schools: http://www.w3schools.com/ 以及一个对应的中文版本: http://www.w3school.com.cn/ 当我们用Python或者其他语言开发Web应用时,我们就是要在服务器端动态创建出HTML,这样,浏览器就会向不同的用户显示出不同的Web页面。
WSGI接口
一个Web应用的本质就是:
浏览器发送一个HTTP请求;
服务器收到请求,生成一个HTML文档
服务器把HTML文档作为HTTP响应的Body发送给浏览器
浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示。
最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。Apache、Nginx、Lighttpd等这些常见的静态服务器就是干这件事情的。
底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口,让我们专心用Python编写Web业务。这个接口就是WSGI:Web Server Gateway Interface。
它只要求Web开发者实现一个函数,就可以响应HTTP请求。我们来看一个最简单的Web版本的“Hello, web!”:
接收两个参数
environ:一个包含所有HTTP请求信息的dict对象;
start_response:一个发送HTTP响应的函数。
在application()函数中,调用: start_response('200 OK', [('Content-Type', 'text/html')]) 就发送了HTTP响应的Header,注意Header只能发送一次,也就是只能调用一次start_response()函数。start_response()函数接收两个参数,一个是HTTP响应码,一个是一组list表示的HTTP Header,每个Header用一个包含两个str的tuple表示。
通常情况下,都应该把Content-Type头发送给浏览器。其他很多常用的HTTP Header也应该发送。 然后,函数的返回值b'<h1>Hello, web!</h1>'将作为HTTP响应的Body发送给浏览器。
application()函数必须由WSGI服务器来调用。
Python内置了一个WSGI服务器,这个模块叫wsgiref,它是用纯Python编写的WSGI服务器的参考实现。所谓“参考实现”是指该实现完全符合WSGI标准,但是不考虑任何运行效率,仅供开发和测试使用。
运行WSGI服务
先编写hello.py,实现Web应用程序的WSGI处理函数:
再编写一个server.py,负责启动WSGI服务器,加载application()函数:
确保以上两个文件在同一个目录下,然后在命令行输入python server.py来启动WSGI服务器:
启动成功后,打开浏览器,输入http://localhost:8000/,就可以看到结果了:
使用WEB框架
其实一个Web App,就是写一个WSGI的处理函数,针对每个HTTP请求进行响应。
处理多个不同的URL,使用WEB框架来处理URL到函数的映射,我们专注于用一个函数处理一个URL。利用flask来编写Web App
安装Flask
pip install flask
写一个app.py,处理三个URL,分别是: GET /:首页,返回Home; GET /signin:登录页,显示登录表单; POST /signin:处理登录表单,显示登录结果。 同一个URL/signin分别有GET和POST两种请求,映射到两个处理函数中。 Flask通过Python的装饰器在内部自动地把URL和函数给关联起来,所以,我们写出来的代码就像这样:
from flask import Flask from flask import request app = Flask(__name__) @app.route('/', methods=['GET', 'POST']) def home(): return '<h1>Home</h1>' @app.route('/signin', methods=['GET']) def signin_form(): return '''<form action="/signin" method="post"> <p><input name="username"></p> <p><input name="password" type="password"></p> <p><button type="submit">Sign In</button></p> </form>''' @app.route('/signin', methods=['POST']) def signin(): # 需要从request对象读取表单内容: if request.form['username']=='admin' and request.form['password']=='password': return '<h3>Hello, admin!</h3>' return '<h3>Bad username or password.</h3>' if __name__ == '__main__': app.run()
运行python app.py,Flask自带的Server在端口5000上监听:
浏览器访问
http://localhost:5000/
http://localhost:5000/signin
常见python web框架
Django:全能型Web框架; web.py:一个小巧的Web框架; Bottle:和Flask类似的Web框架; Tornado:Facebook的开源异步Web框架。
使用模板
使用模板,我们需要预先准备一个HTML文档,这个HTML文档不是普通的HTML,而是嵌入了一些变量和指令,然后,根据我们传入的数据,替换后,得到最终的HTML,发送给用户:
这就是传说中的MVC:Model-View-Controller,中文名“模型-视图-控制器”。
Python处理URL的函数就是C:Controller,Controller负责业务逻辑,比如检查用户名是否存在,取出用户信息等等;
包含变量{{ name }}的模板就是V:View,View负责显示逻辑,通过简单地替换一些变量,View最终输出的就是用户看到的HTML。
MVC中的Model在哪?Model是用来传给View的,这样View在替换变量的时候,就可以从Model中取出相应的数据。
20 异步IO
协程
协程,又称微线程,纤程。英文名Coroutine。
asyncio
asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。
asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。
async/await
用asyncio提供的@asyncio.coroutine可以把一个generator标记为coroutine类型,然后在coroutine内部用yield from调用另一个coroutine实现异步操作。
为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法async和await,可以让coroutine的代码更简洁易读。
async和await是针对coroutine的新语法,要使用新的语法,只需要做两步简单的替换:
1 把@asyncio.coroutine替换为async;
2 把yield from替换为await。
aiohttp
asyncio可以实现单线程并发IO操作。如果仅用在客户端,发挥的威力不大。如果把asyncio用在服务器端,例如Web服务器,由于HTTP连接就是IO操作,因此可以用单线程+coroutine实现多用户的高并发支持。
asyncio实现了TCP、UDP、SSL等协议,aiohttp则是基于asyncio实现的HTTP框架。
11 正则表达式
用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的。
如何用字符来描述字符
\d 匹配一个数字
\w 匹配一个字母或数字
\s 匹配一个空格(也包括Tab等空白符)
. 匹配任意字符
* 表示任意个字符(包括0个)
+ 表示至少一个字符
? 表示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,所以(P|p)ython可以匹配'Python'或者'python'
^表示行的开头,^\d表示必须以数字开头
$表示行的结束,\d$表示必须以数字结束
re模块
Python提供re模块,包含所有正则表达式的功能。
由于Python的字符串本身也用\转义,所以要特别注意:
s = 'ABC\\-001' # Python的字符串 # 对应的正则表达式字符串变成: # 'ABC\-001'
因此我们强烈建议使用Python的r前缀,就不用考虑转义的问题了:
s = r'ABC\-001' # Python的字符串 # 对应的正则表达式字符串不变: # 'ABC\-001'
判断正则表达式是否匹配:
match()方法判断是否匹配,如果匹配成功,返回一个Match对象,否则返回None。常见的判断方法就是:
切分字符串
正常的切分代码:
无法识别连续的空格,用正则表达式试试:
加入,或者加入;
分组
除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()表示的就是要提取的分组(Group)
^(\d{3})-(\d{3,8})$分别定义了两个组,可以直接从匹配的字符串中提取出区号和本地号码:
如果正则表达式中定义了组,就可以在Match对象上用group()方法提取出子串来。 注意到group(0)永远是原始字符串,group(1)、group(2)……表示第1、2、……个子串。
贪婪匹配
正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。举例如下,匹配出数字后面的0:
由于\d+采用贪婪匹配,直接把后面的0全部匹配了,结果0*只能匹配空字符串了。
必须让\d+采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0匹配出来,加个?就可以让\d+采用非贪婪匹配:
编译
当我们在Python中使用正则表达式时,re模块内部会干两件事情:
编译正则表达式,如果正则表达式的字符串本身不合法,会报错;
用编译后的正则表达式去匹配字符串
如果一个正则表达式要重复使用几千次,出于效率的考虑,我们可以预编译该正则表达式,接下来重复使用时就不需要编译这个步骤了,直接匹配:
编译后生成Regular Expression对象,由于该对象自己包含了正则表达式,所以调用对应的方法时不用给出正则字符串。
10 进程和线程
进程和线程
操作系统中,一个任务就是一个进程,一个进程可以包含一个或多个线程
多进程
Unix/Linux:利用fork()创建多进程
普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
windows:利用multiprocessing模块,multiprocessing模块提供了一个Process类来代表一个进程对象
创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动
join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步
Pool:启动大量子进程,利用进程池的方式批量创建子进程
子进程
subprocess模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出
在Python代码中运行命令nslookup www.python.org,这和命令行直接运行的效果是一样的:
如果子进程还需要输入,则可以通过communicate()方法输入
进程间通信
Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据
多线程
ThreadLocal
进程 vs. 线程
分布式进程
9 IO编程
Input/Output,也就是输入和输出
同步/异步IO:是否等待IO执行的结果
文件读写
读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)
读文件
步骤
具体步骤
使用Python内置的open()函数,传入文件名和标示符:
接下来,调用read()方法可以一次读取文件的全部内容,Python把内容读到内存,用一个str对象表示:
最后一步是调用close()方法关闭文件。文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的:
try ... finally,避免读写文件错误
with语句简化代码,自动调用close()方法
read()会一次性读取文件的全部内容,文件过大可采用:
反复调用read(size)方法,每次最多读取size个字节的内容
调用readline()可以每次读取一行内容,调用readlines()一次读取所有内容并按行返回list
file-like Object
像open()函数返回的这种有个read()方法的对象,在Python中统称为file-like Object。除了file外,还可以是内存的字节流,网络流,自定义流等等。file-like Object不要求从特定类继承,只要写个read()方法就行。
二进制文件
默认的读取的是文本文件,读取二进制文件,并且是UTF-8编码的文本文件。要读取二进制文件,比如图片、视频等等,用'rb'模式打开文件
字符编码
读取非UTF-8编码的文本文件,需要给open()函数传入encoding参数,例如,读取GBK编码的文件:
有些编码不规范的文件,你可能会遇到UnicodeDecodeError,因为在文本文件中可能夹杂了一些非法编码的字符。遇到这种情况,open()函数还接收一个errors参数,表示如果遇到编码错误后如何处理。最简单的方式是直接忽略:
写文件
写文件和读文件是一样的,唯一区别是调用open()函数时,传入标识符'w'或者'wb'表示写文本文件或写二进制文件:
要写入特定编码的文本文件,请给open()函数传入encoding参数,将字符串自动转换成指定编码。
以'w'模式写入文件时,如果文件已存在,会直接覆盖(相当于删掉后新写入一个文件)。如果我们希望追加到文件末尾怎么办?可以传入'a'以追加(append)模式写入。
StringIO和BytesIO
StringIO
在内存中读写str
需要先创建一个StringIO,然后,像文件一样写入即可吗,getvalue()方法用于获得写入后的str
读取StringIO,可以用一个str初始化StringIO,然后,像读文件一样读取
BytesIO
操作二进制数据,就需要使用BytesIO
创建一个BytesIO,然后写入一些bytes
StringIO类似,可以用一个bytes初始化BytesIO,然后,像读文件一样读取
操作文件和目录
Python内置的os模块也可以直接调用操作系统提供的接口函数
环境变量
操作系统中定义的环境变量,全部保存在os.environ这个变量中
os.environ查看环境变量
os.environ.get('key')查看某个环境变量的值,如os.environ.get('PATH')
操作文件和目录
操作文件和目录的函数一部分放在os模块中,一部分放在os.path模块中
查看目录
os.path.abspath('.')
创建目录
os.mkdir('/Users/michael/testdir')
删除目录
os.rmdir('/Users/michael/testdir')
合并路径:os.path.join('/Users/michael', 'testdir')
'/Users/michael/testdir'
不要直接拼字符串,而要通过os.path.join()函数,这样可以正确处理不同操作系统的路径分隔符
拆分路径时,也不要直接去拆字符串,而要通过os.path.split()函数,这样可以把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名:
过滤文件
列出当前目录下的所有目录,只需要一行代码:
列出所有的.py文件,也只需一行代码:
序列化
定义
把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,在其他语言中也被称之为serialization,marshalling,flattening等等,序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上
把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling
python实现
Python提供了pickle模块来实现序列化
把一个对象序列化并写入文件
pickle.dumps()方法把任意对象序列化成一个bytes,然后,就可以把这个bytes写入文件。或者用另一个方法pickle.dump()直接把对象序列化后写入一个file-like Object
反序列化
要把对象从磁盘读到内存时,可以先把内容读到一个bytes,然后用pickle.loads()方法反序列化出对象,也可以直接用pickle.load()方法从一个file-like Object中直接反序列化出对象
JSON
在不同编程语言之间传递对象,须把对象序列化为标准格式,JSON。通过Python内置的json模块提供了非常完善的Python对象到JSON格式的转换。
把Python对象变成一个JSON:
把JSON反序列化为Python对象,用loads()或者对应的load()方法
JSON进阶
运用可选参数default就是把任意一个对象变成一个可序列为JSON的对象
8错误、调试和测试
错误处理
try
当我们认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。
如果发生了不同类型的错误,应该由不同的except语句块处理。可以有多个except来捕获不同类型的错误
如果没有错误发生,可以在except语句块后面加一个else,当没有错误发生时,会自动执行else语句
Python的错误其实也是class,所有的错误类型都继承自BaseException,所以在使用except时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”。
第二个except永远也捕获不到UnicodeError,因为UnicodeError是ValueError的子类,如果有,也被第一个except给捕获了。
try...except捕获错误还有一个巨大的好处,就是可以跨越多层调用,比如函数main()调用foo(),foo()调用bar(),结果bar()出错了,这时,只要main()捕获到了,就可以处理
不需要在每个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以了。这样一来,就大大减少了写try...except...finally的麻烦。
调用栈
记录错误
如果不捕获错误,自然可以让Python解释器来打印出错误堆栈,但程序也被结束了。既然我们能捕获错误,就可以把错误堆栈打印出来,然后分析错误原因,同时,让程序继续执行下去。
Python内置的logging模块可以非常容易地记录错误信息
出错,但程序打印完错误信息后会继续执行,并正常退出:
抛出错误
错误是class,捕获一个错误就是捕获到该class的一个实例。因此,错误并不是凭空产生的,而是有意创建并抛出的。Python的内置函数会抛出很多类型的错误,我们自己编写的函数也可以抛出错误。
如果要抛出错误,首先根据需要,可以定义一个错误的class,选择好继承关系,然后,用raise语句抛出一个错误的实例:
执行,可以最后跟踪到我们自己定义的错误:
只有在必要的时候才定义我们自己的错误类型。如果可以选择Python已有的内置的错误类型(比如ValueError,TypeError),尽量使用Python内置的错误类型。
当前函数不知道应该怎么处理该错误,最恰当的方式是继续往上抛,让顶层调用者去处理。
调试
用print()把可能有问题的变量打印出来
断言(assert)
凡是用print()来辅助查看的地方,都可以用断言(assert)来替代
如果断言失败,assert语句本身就会抛出AssertionError
启动Python解释器时可以用-O参数来关闭assert
logging
把print()替换为logging是第3种方式,和assert比,logging不会抛出错误,而且可以输出到文件
允许你指定记录信息的级别,有debug,info,warning,error等几个级别,当我们指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debug和info就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
logging的另一个好处是通过简单的配置,一条语句可以同时输出到不同的地方,比如console和文件。
pdb
启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态
pdb.set_trace()
在可能出错的地方放一个pdb.set_trace(),就可以设置一个断点,程序会自动在pdb.set_trace()暂停并进入pdb调试环境,可以用命令p查看变量,或者用命令c继续运行
IDE
支持调试功能的IDE
Visual Studio Code:https://code.visualstudio.com/,需要安装Python插件。 PyCharm:http://www.jetbrains.com/pycharm/ 另外,Eclipse加上pydev插件也可以调试Python程序。
单元测试
单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
为了编写单元测试,我们需要引入Python自带的unittest模块
文档测试
7面向对象高级编程
使用__slots__
用来限制实例的属性,只允许添加特定的属性
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性
__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的
使用@property
装饰器(decorator)可以给函数动态加上功能
类的方法,装饰器一样起作用。Python内置的@property装饰器就是负责把一个方法变成属性调用的
多重继承
通过多重继承,一个子类就可以同时获得多个父类的所有功能
MixIn
在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为MixIn。为了更好地看出继承关系,我们把Runnable和Flyable改为RunnableMixIn和FlyableMixIn。
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系
定制类
__str__
打印实例名
__repr__()
不调用print,直接显示变量,两者区别是__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,__repr__()是为调试服务的
__iter__
使类具有可迭代特性,须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环
以斐波那契数列为例,写一个Fib类,可以作用于for循环:
__getitem__
使实例可以像list那样按照下标取出元素
__getattr__
动态返回一个属性
__call__
一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用instance.method()来调用。
对于任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用
使用枚举类
用来定义常量
通常定义常量,用大写变量通过整数来定义,比较简单,但仍然是变量
为这样的枚举类型定义一个class类型,然后,每个常量都是class的一个唯一实例
使用元类
type()
动态创建类
metaclass
元类
先定义metaclass,就可以创建类,最后创建实例。
metaclass允许你创建类或者修改类,类是metaclass创建出来的“实例”
对象关系映射Object Relational Mapping(ORM)
6面向对象编程
面向对象和面向过程
Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递
类和实例
类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同
类是通过class关键字定义
class Student(object): pass
子主题
class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的
创建实例:定义好了Student类,就可以根据Student类创建出Student的实例,创建实例是通过类名+()实现的
变量bart指向的就是一个Student的实例,后面的0x10a67a590是内存地址,每个object的地址都不一样,而Student本身则是一个类
给实例绑定属性
直接通过 实例名.属性名
通过类__init__方法
有了__init__方法,创建实例不可传入空参数,必须传入与__init__方法匹配的参数,self不需要传
数据封装:在类的内部直接定义访问数据的函数
封装数据的函数是和类本身相关联的,称之为类的方法
定义方法
除第一个参数是self外,其他的和普通函数一样
调用方法
只需在实例变量上直接调用,除self不用传递,其他参数正常传入
封装可以增加新的方法
可以给Student类增加新的方法,比如get_grade:
访问限制
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问
无法从外部访问实例变量.__name和实例变量.__score了
如果要从外部访问或修改私有变量,可以通过定义方法解决
给Student类增加get_name和get_score这样的方法:
再给Student类增加set_score方法:
私有变量仍然可以通过特殊办法访问
不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量
继承和多态
当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)
子类可获得父类的全部功能
子类和父类存在相同的方法时,子类的方法会覆盖父类的方法,代码运行总是调用子类的方法,继承的另一个好处:多态
子主题
获取对象信息
使用type()
判断对象类型
实用isinstance()
对于class的继承关系来说,type很不方便
isinstance可以判断某个对象是否事某种类型
还可以判队某个对象是否事某些类型的一种
使用dir()
获得对象所有属性和方法
实例属性和类属性
给实例绑定属性的方法可以通过实例变量,活着通过self变量
实例属性比类属性优先级高,辉有限访问实例属性
5模块
使用模块
模块代码组成
第一行:注释可以让这个hello.py文件直接在Unix/Linux/Mac上运行, 第二行:注释表示.py文件本身使用标准UTF-8编码 第四行:对模块的文档注释,第一个字符串都是对模块的文档注释 第六行:作者信息 代码部分
作用域
在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_前缀来实现的。
正常的函数和变量名是公开的(public),可以被直接引用,比如:abc,x123,PI等
类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author__,__name__就是特殊变量,hello模块定义的文档注释也可以用特殊变量__doc__访问,我们自己的变量一般不要用这种变量名
导入模块
import 模块
import sys
可以利用sys变量访问模块所有功能
安装模块
python库网站
pypi.python.org
pip install 模块名
pip install Pillow
安装常用模块
Anaconda
模块搜索路径
sys模块的path变量中
import sys sys.path
添加自己的搜索目录
修改sys.path
import sys sys.path.append('/Users/michael/my_py_scripts')
运行时修改,运行结束后失效
设置环境变量PYTHONPATH
与设置Path环境变量类似
只需添加自己的搜索路径,python本身路径不受影响
4.函数式编程
特点
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。
允许把函数本身作为参数传入另一个函数,还允许返回一个函数
高阶函数
特点
定义:变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数
变量可以指向函数:函数本身也可以赋值给变量,即:变量可以指向函数
函数名也是变量:函数名其实就是指向函数的变量
map/reduce
map
接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回
reduce
把一个函数作用在一个序列[x1, x2, x3, ...],这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算
filter
Python内建的filter()函数用于过滤序列,接收一个函数和一个序列,把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素
sorted
接收一个key函数来实现自定义的排序
返回函数
函数作为返回值
内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”
闭包
返回的函数并没有立刻执行,而是直到调用了f()才执行
匿名函数
传入函数时,不需要显式地定义函数,直接传入匿名函数
lambda x: x * x
键字lambda表示匿名函数,冒号前面的x表示函数参数。
匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数
装饰器
在代码运行期间,动态增加功能的方式,称为装饰器(decorator)
decorator是一个返回函数的高阶函数
偏函数
functools中的一个工具,偏函数(Partial function)
把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单
3.高级特性
切片
tuple、字符串同样可以使用切片操作
list切片
L[0:3]:从索引0开始取,直到索引3为止,但不包括索引3
L[-10:]:后十个数
L[:10:2]:前10个数,每两个取一个
L[::5]:所有数,每5个取一个
迭代
给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)
可迭代对象都可以迭代,如dict,字符串等
判断迭代对象是否可迭代
通过collections模块的Iterable类型判断
列表生成式(List Comprehensions)
两层循环
多个变量
生成器
在循环的过程中不断推算出后续的元素,在循环的过程中不断推算出后续的元素。在循环的过程中不断推算出后续的元素。
创建生成器
把列表生成式的[]改为()
一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator
迭代器
可迭代对象:Iterable,可用于for循环数据类型
判断对象是否是Iterable对象
集合数据类型
list、tuple、dict、set、str等
generator
生成器和带yield的generator function
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator
生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator
2.函数
help(函数名):获取帮助
调用函数
abs(-1)
max(2,3,3,-4)
数据类型转换
int(12.13)
str(100)
定义函数
定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回
导入定义函数
如果你已经把my_abs()的函数定义保存为abstest.py文件了,那么,可以在该文件的当前目录下启动Python解释器,用from abstest import my_abs来导入my_abs()函数
空函数
利用pass语句
返回多个值
函数的参数
位置参数
传入函数值按照位置一次赋给参数
power(5, 2):5和2依次赋给x和n
默认参数
注意
必选参数在前,默认参数在后
如何设置默认参数
当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数
默认参数必须指向不变对象!
可变参数
由于参数个数不确定,可以把a,b,c……作为一个list或tuple传进来
我们把函数的参数改为可变参数:
定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*号。在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。
如果已经有一个list或者tuple,要调用一个可变参数怎么办?可以这样做:
关键字参数
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
命名关键字参数
限制关键字参数的名字
只接收city和job作为关键字参数。这种方式定义的函数如下
如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错
参数组合
可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用
参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数
递归函数
函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数
1.python基础
代码格式
#开头是注释
语句以:结尾,缩进语句为代码块
缩进单位:四个空格
大小写敏感
数据类型和变量
整数
大小没有限制
浮点数
字符串
以单引号‘’或双引号“”括起来的任意文本
转义字符
换行:\n
制表符:\t
\ :\\
多行内容:'''.......'''
布尔值
True
False
运算方法
an
or
not
空值
none,不可以理解为0
变量
变量名必须为大小写英文、数字和_组成,不能用数字开头
可以是数字或任意数据类型
a = 1
t_007 = 'T007'
Answer=True
常量
通常用大写变量名表示常量
PI=3.14159265359
字符串和编码
字符编码
ASCII编码
1个字节
A:0100 0001
GB2312编码
Unicode编码
2个字节
A: 0000 0000 0100 0001
UTF-8编码
可变长编码,根据不同数字大小变成1-6个字节
字符串
字符串函数
ord():获取字符的整数表示
chr():编码转换为对应字符
类型:str
格式化
用%实现
'Hello,%s' %'world'
'Hello, world'
'Hi, %s, you have $%d.' %('Michael', 10000)
'Hi, Michael, you have $1000000.'
常见占位符
整数 %d
浮点数 %f
字符串 %s
十六进制整数 %x
%:%%
format()
format()传入参数一次替换字符串内的占位符{0}、{1}...
'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
使用list和tuple
list
是一种有序的集合,可随时添加和删除其中元素,用[]表示
classmates = ['Michael', 'Bob', 'Tracy']
索引
从0开始
classmates[1]
最后一个可以用-1索引
classmates[-1]
list函数
len():获取元素个数
追加:.append
classmates.append('Adam')
指定位置追加:.insert
classmates.insert(1,'Jack')
删除元素:pop
删除末尾
classmates.pop()
删除指定位置
classmates.pop(i)
替换,直接赋值
classmates[1] = 'Sarah'
list内部数据类型可以不同
L = ['Apple', 123, True]
元素可以是另一个list
s = ['python', 'java', ['asp', 'php'], 'scheme']
tupple
另一种有序列表叫元组,初始化后不能改变,用()表示
classmates = ('Michael', 'Bob', 'Tracy')
空tuple
t = ()
一个元素tuple
t = (1,)
可变tuple
tuble内部包含list
t = ('a', 'b', ['A', 'B'])
条件判断
条件判断为True,执行缩进语句
else语句:if判断为False,则执行else语句
多重判断,用elif
循环
for...in循环,依次把list或tuple中的每个元素迭代出来
while循环,只要条件满足,就不断循环,条件不满足时退出循环
使用dict和set
dict
使用键-值{key:value}存储, 用大括号表示{ }
d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
赋值
初始化赋值
通过key赋值,如果key不存在会报错
d['Jack'] = 90
函数
删除:pop(key)
d.pop('Bob')
set
set和dict类似,也是一组key的集合,但不存储value
s = set([1, 2, 3])
增加元素
add(key)
删除元素
remove(key)