在Python中,全部数据类型均可以视为对象,固然也能够自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。html
面向对象的设计思想是抽象出Class,根据Class建立Instance。web
面向对象的抽象程度又比函数要高,由于一个Class既包含数据,又包含操做数据的方法。数据库
定义以下:django
class Student(object): def __init__(self, name, score): self.name = name self.score = score
class
后面紧接着是类名,即Student
,类名一般是大写开头的单词,紧接着是(object)
,表示该类是从哪一个类继承下来的,继承的概念咱们后面再讲,一般,若是没有合适的继承类,就使用object
类,这是全部类最终都会继承的类。编程
建立实例是经过类名+()实现的:bart=Student()json
能够自由地给一个实例变量绑定属性。flask
和静态语言不一样,Python容许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不一样实例,但拥有的变量名称均可能不一样。浏览器
好比,给实例bart
绑定一个name
属性:ruby
>>> bart.name = 'Bart Simpson' >>> bart.name 'Bart Simpson'
在建立实例的时候,能够将属性绑定,是经过特殊方法 __init__ (先后分别有两个下划线)。服务器
注意到__init__
方法的第一个参数永远是self
,表示建立的实例自己,所以,在__init__
方法内部,就能够把各类属性绑定到self
,由于self
就指向建立的实例自己。
有了__init__
方法,在建立实例的时候,就不能传入空的参数了,必须传入与__init__
方法匹配的参数,但self
不须要传,Python解释器本身会把实例变量传进去:
>>> bart = Student('Bart Simpson', 59) >>> bart.name 'Bart Simpson' >>> bart.score 59
外部不直接访问类中的属性,而是经过类中的方法来间接访问。
class Student(object): def __init__(self, name, score): self.name = name self.score = score def print_score(self): print('%s: %s' % (self.name, self.score))
要定义一个方法,除了第一个参数是self
外,其余和普通函数同样。要调用一个方法,只须要在实例变量上直接调用,除了self
不用传递,其余参数正常传入。
这样一来,咱们从外部看Student
类,就只须要知道,建立实例须要给出name
和score
,而如何打印,都是在Student
类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。
用Python进行网络编程,就是在Python程序自己这个进程内,链接别的服务器进程的通讯端口进行通讯。
互联网协议包含了上百种协议标准,可是最重要的两个协议是TCP和IP协议,因此,你们把互联网的协议简称TCP/IP协议。
通讯的时候,双方必须知道对方的标识,比如发邮件必须知道对方的邮件地址。互联网上每一个计算机的惟一标识就是IP地址,相似123.123.123.123
。若是一台计算机同时接入到两个或更多的网络,好比路由器,它就会有两个或多个IP地址,因此,IP地址对应的其实是计算机的网络接口,一般是网卡。
IP协议负责把数据从一台计算机经过网络发送到另外一台计算机。数据被分割成一小块一小块,而后经过IP包发送出去。因为互联网链路复杂,两台计算机之间常常有多条线路,所以,路由器就负责决定如何把一个IP包转发出去。IP包的特色是按块发送,途径多个路由,但不保证能到达,也不保证顺序到达。
TCP协议则是创建在IP协议之上的。TCP协议负责在两台计算机之间创建可靠链接,保证数据包按顺序到达。TCP协议会经过握手创建链接,而后,对每一个IP包编号,确保对方按顺序收到,若是包丢掉了,就自动重发。
许多经常使用的更高级的协议都是创建在TCP协议基础上的,好比用于浏览器的HTTP协议、发送邮件的SMTP协议等。
一个TCP报文除了包含要传输的数据外,还包含源IP地址和目标IP地址,源端口和目标端口。
端口有什么做用?在两台计算机通讯时,只发IP地址是不够的,由于同一台计算机上跑着多个网络程序。一个TCP报文来了以后,究竟是交给浏览器仍是QQ,就须要端口号来区分。每一个网络程序都向操做系统申请惟一的端口号,这样,两个进程在两台计算机之间创建网络链接就须要各自的IP地址和各自的端口号。
一个进程也可能同时与多个计算机创建连接,所以它会申请不少端口。
大多数链接都是可靠的TCP链接。建立TCP链接时,主动发起链接的叫客户端,被动响应链接的叫服务器。
举个例子,经过Socket向百度发请求
import socket s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect(('www.baidu.com',80)) # 发送数据: s.send(b'GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n') # 接收数据: buffer = [] while True: # 每次最多接收1k字节: 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('baidu.html', 'wb') as f: f.write(html)
解释:
建立Socket
时,AF_INET
指定使用IPv4协议,若是要用更先进的IPv6,就指定为AF_INET6
。SOCK_STREAM
指定使用面向流的TCP协议
客户端要主动发起TCP链接,必须知道服务器的IP地址和端口号,参数是一个tuple
TCP链接建立的是双向通道,双方均可以同时给对方发数据。可是谁先发谁后发,怎么协调,要根据具体的协议来决定。例如,HTTP协议规定客户端必须先发请求给服务器,服务器收到后才发数据给客户端。
发送的文本格式必须符合HTTP标准,若是格式没问题,接下来就能够接收新浪服务器返回的数据了。
接收数据时,调用recv(max)
方法,一次最多接收指定的字节数,所以,在一个while循环中反复接收,直到recv()
返回空数据,表示接收完毕,退出循环。
接收完数据后,调用close()
方法关闭Socket,这样,一次完整的网络通讯就结束了。接收到的数据包括HTTP头和网页自己,咱们只须要把HTTP头和网页分离一下,把HTTP头打印出来,网页内容保存到文件。
服务器进程首先要绑定一个端口并监听来自其余客户端的链接。若是某个客户端链接过来了,服务器就与该客户端创建Socket链接,随后的通讯就靠这个Socket链接了。
因此,服务器会打开固定端口(好比80)监听,每来一个客户端链接,就建立该Socket链接。因为服务器会有大量来自客户端的链接,因此,服务器要可以区分一个Socket链接是和哪一个客户端绑定的。一个Socket依赖4项:服务器地址、服务器端口、客户端地址、客户端端口来惟一肯定一个Socket。
可是服务器还须要同时响应多个客户端的请求,因此,每一个链接都须要一个新的进程或者新的线程来处理,不然,服务器一次就只能服务一个客户端了。
咱们来编写一个简单的服务器程序,它接收客户端链接,把客户端发过来的字符串加上Hello
再发回去。
服务端
import socket import threading import time # 链接创建后,服务器首先发一条欢迎消息,而后等待客户端数据,并加上Hello再发送给客户端。若是客户端发送了exit字符串,就直接关闭链接。 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) # 请注意,小于1024的端口号必需要有管理员权限才能绑定 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()
客户端
import socket 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()
须要打开两个命令行窗口,一个运行服务器程序,另外一个运行客户端程序,就能够看到效果了 。
须要注意的是,客户端程序运行完毕就退出了,而服务器程序会永远运行下去,必须按Ctrl+C退出程序。
Python的诞生历史比Web还要早,因为Python是一种解释型的脚本语言,开发效率高,因此很是适合用来作Web开发。
Python有上百种Web开发框架,有不少成熟的模板技术,选择Python开发Web应用,不但开发效率高,并且运行速度快。
一个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。
WSGI接口定义很是简单,它只要求Web开发者实现一个函数,就能够响应HTTP请求。咱们来看一个最简单的Web版本的“Hello, web!”:
def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return [b'<h1>Hello, web!</h1>']
上面的application()
函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:
environ:一个包含全部HTTP请求信息的dict
对象;
start_response:一个发送HTTP响应的函数,只能调一次。
函数的返回值b'<h1>Hello, web!</h1>'
将做为HTTP响应的Body发送给浏览器。
有了WSGI,咱们关心的就是如何从environ
这个dict
对象拿到HTTP请求信息,而后构造HTML,经过start_response()
发送Header,最后返回Body。
整个application()
函数自己没有涉及到任何解析HTTP的部分,也就是说,底层代码不须要咱们本身编写,咱们只负责在更高层次上考虑如何响应请求就能够了。
不过,等等,这个application()
函数怎么调用?若是咱们本身调用,两个参数environ
和start_response
咱们无法提供,返回的bytes
也无法发给浏览器。
因此application()
函数必须由WSGI服务器来调用。有不少符合WSGI规范的服务器,咱们能够挑选一个来用。可是如今,咱们只想尽快测试一下咱们编写的application()
函数真的能够把HTML输出到浏览器,因此,要赶忙找一个最简单的WSGI服务器,把咱们的Web应用程序跑起来。
好消息是Python内置了一个WSGI服务器,这个模块叫wsgiref,它是用纯Python编写的WSGI服务器的参考实现。所谓“参考实现”是指该实现彻底符合WSGI标准,可是不考虑任何运行效率,仅供开发和测试使用。
hello.py
def application(environ,start_response): start_response('200 OK', [('Content-Type', 'text/html')]) #return [b'<h1>Hello, web!</h1>'] #改造一下 body = '<h1>Hello, %s!</h1>' % (environ['PATH_INFO'][1:] or 'web') return [body.encode('utf-8')]
server.py
# 从WSgiref模板导入 from wsgiref.simple_server import make_server # 导入咱们编写的application函数 from hello import application # 建立一个服务器,IP地址为空,端口是8000,处理函数是application: httpd = make_server('', 8000, application) print('Serving HTTP on port 8000...') # 开始监听HTTP请求: httpd.serve_forever()
执行server.py,而后打开浏览器:http://localhost:8000/ 或者 http://localhost:8000/huy
不管多么复杂的Web应用程序,入口都是一个WSGI处理函数。HTTP请求的全部输入信息均可以经过environ
得到,HTTP响应的输出均可以经过start_response()
加上函数返回值做为Body。
复杂的Web应用程序,光靠一个WSGI函数来处理仍是太底层了,咱们须要在WSGI之上再抽象出Web框架,进一步简化Web开发。
用Python开发一个Web框架十分容易,因此Python有上百个开源的Web框架。这里咱们先不讨论各类Web框架的优缺点,直接选择一个比较流行的Web框架——Flask来使用。
先用pip
安装Flask: pip install flask
而后写一个xxx.py
,处理3个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 page</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()
Flask自带的Server在端口5000
上监听,输入首页地址http://localhost:5000/
:
再输入 http://localhost:5000/signin
输入预设的用户名admin
和口令password
,登陆成功。。输个错误密码试试。。
实际的Web App应该拿到用户名和口令后,去数据库查询再比对,来判断用户是否能登陆成功。
除了Flask,常见的Python Web框架还有:
固然了,由于开发Python的Web框架也不是什么难事,咱们后面也会讲到开发Web框架的内容。
有了Web框架,咱们在编写Web应用时,注意力就从WSGI处理函数转移到URL+对应的处理函数,这样,编写Web App就更加简单了。
在编写URL处理函数时,除了配置URL外,从HTTP请求拿到用户数据也是很是重要的。Web框架都提供了本身的API来实现这些功能。Flask经过request.form['name']
来获取表单的内容。
使用模板,咱们须要预先准备一个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中取出相应的数据。
上面的例子中,Model就是一个dict
:{ 'name': 'Michael' }
只是由于Python支持关键字参数,不少Web框架容许传入关键字参数,而后,在框架内部组装出一个dict
做为Model。
如今,咱们把上次直接输出字符串做为HTML的例子用高端大气上档次的MVC模式改写一下:
from flask import Flask, request, render_template app = Flask(__name__) @app.route('/', methods=['GET', 'POST']) def home(): return render_template('home.html') @app.route('/signin', methods=['GET']) def signin_form(): return render_template('form.html') @app.route('/signin', methods=['POST']) def signin(): username = request.form['username'] password = request.form['password'] if username=='admin' and password=='password': return render_template('signin-ok.html', username=username) return render_template('form.html', message='Bad username or password', username=username) if __name__ == '__main__': app.run()
Flask经过render_template()
函数来实现模板的渲染。和Web框架相似,Python的模板也有不少种。Flask默认支持的模板是jinja2,因此咱们先直接安装jinja2:
$ pip install jinja2
而后,开始编写jinja2模板:
home.html 用来显示首页的模板: <html> <head> <title>Home</title> </head> <body> <h1 style="font-style:italic">Home</h1> </body> </html> form.html 用来显示登陆表单的模板: <html> <head> <title>Please Sign In</title> </head> <body> {% if message %} <p style="color:red">{{ message }}</p> {% endif %} <form action="/signin" method="post"> <legend>Please sign in:</legend> <p><input name="username" placeholder="Username" value="{{ username }}"></p> <p><input name="password" placeholder="Password" type="password"></p> <p><button type="submit">Sign In</button></p> </form> </body> </html> signin-ok.html 登陆成功的模板: <html> <head> <title>Welcome, {{ username }}</title> </head> <body> <p>Welcome, {{ username }}!</p> </body> </html>
登陆失败的模板呢?咱们在form.html
中加了一点条件判断,把form.html
重用为登陆失败的模板。
最后,必定要把模板放到正确的templates
目录下,templates
和app.py
在同级目录下:
启动:http://localhost:5000/
再输入 http://localhost:5000/signin
在Jinja2模板中,咱们用{{ name }}
表示一个须要替换的变量。不少时候,还须要循环、条件判断等指令语句,在Jinja2中,用{% ... %}
表示指令。
好比循环输出页码:
{% for i in page_list %}
<a href="/page/{{ i }}">{{ i }}</a> {% endfor %}
若是page_list
是一个list:[1, 2, 3, 4, 5]
,上面的模板将输出5个超连接。
除了Jinja2,常见的模板还有:
Mako:用<% ... %>
和${xxx}
的一个模板;
Cheetah:也是用<% ... %>
和${xxx}
的一个模板;
Django:Django是一站式框架,内置一个用{% ... %}
和{{ xxx }}
的模板。
CPU的速度远远快于磁盘、网络等IO。在一个线程中,CPU执行代码的速度极快,然而,一旦遇到IO操做,如读写文件、发送网络数据时,就须要等待IO操做完成,才能继续进行下一步操做。这种状况称为同步IO。
由于一个IO操做就阻塞了当前线程,致使其余代码没法执行,因此咱们必须使用多线程或者多进程来并发执行代码,为多个用户服务。每一个用户都会分配一个线程,若是遇到IO致使线程被挂起,其余用户的线程不受影响。
多线程和多进程的模型虽然解决了并发问题,可是系统不能无上限地增长线程。因为系统切换线程的开销也很大,因此,一旦线程数量过多,CPU的时间就花在线程切换上了,真正运行代码的时间就少了,结果致使性能严重降低。
因为咱们要解决的问题是CPU高速执行能力和IO设备的龟速严重不匹配,多线程和多进程只是解决这一问题的一种方法。
另外一种解决IO问题的方法是异步IO。当代码须要执行一个耗时的IO操做时,它只发出IO指令,并不等待IO结果,而后就去执行其余代码了。一段时间后,当IO返回结果时,再通知CPU进行处理。
异步IO模型须要一个消息循环,在消息循环中,主线程不断地重复“读取消息-处理消息”这一过程
又称微线程,纤程。英文名Coroutine。
子程序,或者称为函数,在全部语言中都是层级调用,好比A调用B,B在执行过程当中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。因此子程序调用是经过栈实现的,一个线程就是执行一个子程序。
子程序调用老是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不一样。
协程看上去也是子程序,但执行过程当中,在子程序内部可中断,而后转而执行别的子程序,在适当的时候再返回来接着执行(不是函数调用,有点相似CPU的中断)。
特色
协程的特色在因而一个线程执行,那和多线程比,协程有何优点?
最大的优点就是协程极高的执行效率。由于子程序切换不是线程切换,而是由程序自身控制,所以,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优点就越明显。
第二大优点就是不须要多线程的锁机制,由于只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只须要判断状态就行了,因此执行效率比多线程高不少。
由于协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可得到极高的性能。
Python对协程的支持是经过generator(生成器)实现的。
注:若是一个函数定义中包含yield关键字,那么这个函数就再也不是一个普通函数,而是一个generator。。遇到语句返回,再次执行时从上次返回的语句处继续执行。yieldyield
在generator中,咱们不但能够经过for
循环来迭代,还能够不断调用next()
函数获取由yield
语句返回的下一个值。
可是Python的yield
不但能够返回一个值,它还能够接收调用者发出的参数。
asyncio
的编程模型就是一个消息循环。咱们从asyncio
模块中直接获取一个EventLoop
的引用,而后把须要执行的协程扔到EventLoop
中执行,就实现了异步IO。
用asyncio
实现Hello world
代码以下:
import asyncio @asyncio.coroutine def hello(): print("Hello world!") # 异步调用asyncio.sleep(1): r = yield from asyncio.sleep(1) print("Hello again!") # 获取EventLoop: loop = asyncio.get_event_loop() # 执行coroutine loop.run_until_complete(hello()) loop.close()
@asyncio.coroutine
把一个generator标记为coroutine类型(协程),而后,咱们就把这个coroutine
扔到EventLoop
中执行。
hello()
会首先打印出Hello world!
,而后,yield from
语法可让咱们方便地调用另外一个generator
。因为asyncio.sleep()
也是一个coroutine
,因此线程不会等待asyncio.sleep()
,而是直接中断并执行下一个消息循环。当asyncio.sleep()
返回时,线程就能够从yield from
拿到返回值(此处是None
),而后接着执行下一行语句。
把asyncio.sleep(1)
当作是一个耗时1秒的IO操做,在此期间,主线程并未等待,而是去执行EventLoop
中其余能够执行的coroutine
了,所以能够实现并发执行。
用asyncio
的异步网络链接来获取sina、sohu和163的网站首页:
import asyncio @asyncio.coroutine def wget(host): print('wget %s...' % host) connect = asyncio.open_connection(host, 80) reader, writer = yield from connect header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host writer.write(header.encode('utf-8')) yield from writer.drain() while True: line = yield from reader.readline() if line == b'\r\n': break print('%s header > %s' % (host, line.decode('utf-8').rstrip())) # Ignore the body, close the socket writer.close() loop = asyncio.get_event_loop() tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']] loop.run_until_complete(asyncio.wait(tasks)) loop.close()
asyncio
提供了完善的异步IO支持;
异步操做须要在coroutine
中经过yield from
完成;
多个coroutine
能够封装成一组Task而后并发执行。
用asyncio
提供的@asyncio.coroutine
能够把一个generator标记为coroutine类型,而后在coroutine内部用yield from
调用另外一个coroutine实现异步操做。
为了简化并更好地标识异步IO,从Python 3.5开始引入了新的语法async
和await
,可让coroutine的代码更简洁易读。
请注意,async
和await
是针对coroutine的新语法,要使用新的语法,只须要作两步简单的替换:
@asyncio.coroutine
替换为async
;yield from
替换为await
。async def hello(): print("Hello world!") r = await asyncio.sleep(1) print("Hello again!")
asyncio
能够实现单线程并发IO操做。若是仅用在客户端,发挥的威力不大。若是把asyncio
用在服务器端,例如Web服务器,因为HTTP链接就是IO操做,所以能够用单线程+coroutine
实现多用户的高并发支持。
asyncio
实现了TCP、UDP、SSL等协议,aiohttp
则是基于asyncio
实现的HTTP框架。
咱们先安装aiohttp
:
pip install aiohttp
而后编写一个HTTP服务器,分别处理如下URL:
/
/about
代码以下:
import asyncio from aiohttp import web routes = web.RouteTableDef() @routes.get('/') async def index(request): await asyncio.sleep(2) return web.json_response({ 'name': 'index' }) @routes.get('/about') async def about(request): await asyncio.sleep(0.5) return web.Response(text="<h1>about us</h1>") def init(): app = web.Application() app.add_routes(routes) web.run_app(app) init()
运行后输入:http://localhost:8080/