51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

楼主: 八戒你干嘛
打印 上一主题 下一主题

[python] 【转】Python教程

[复制链接]
  • TA的每日心情
    无聊
    前天 09:07
  • 签到天数: 11 天

    连续签到: 2 天

    [LV.3]测试连长

    61#
     楼主| 发表于 2017-7-6 15:27:51 | 只看该作者
    本帖最后由 八戒你干嘛 于 2017-7-6 16:24 编辑

    HTTP协议简介[转]
    在Web应用中,服务器把网页传给浏览器,实际上就是把网页的HTML代码发送给浏览器,让浏览器显示出来。而浏览器和服务器之间的传输协议是HTTP,所以:
    • HTML是一种用来定义网页的文本,会HTML,就可以编写网页;
    • HTTP是在网络上传输HTML的协议,用于浏览器和服务器的通信。

    在举例子之前,我们需要安装Google的Chrome浏览器。
    为什么要使用Chrome浏览器而不是IE呢?因为IE实在是太慢了,并且,IE对于开发和调试Web应用程序完全是一点用也没有。
    我们需要在浏览器很方便地调试我们的Web应用,而Chrome提供了一套完整地调试工具,非常适合Web开发。
    安装好Chrome浏览器后,打开Chrome,在菜单中选择“视图”,“开发者”,“开发者工具”,就可以显示开发者工具:
    Elements显示网页的结构,Network显示浏览器和服务器的通信。我们点Network,确保第一个小红灯亮着,Chrome就会记录所有浏览器和服务器之间的通信:
    当我们在地址栏输入www.sina.com.cn时,浏览器将显示新浪的首页。在这个过程中,浏览器都干了哪些事情呢?通过Network的记录,我们就可以知道。在Network中,定位到第一条记录,点击,右侧将显示Request Headers,点击右侧的view source,我们就可以看到浏览器发给新浪服务器的请求:
    最主要的头两行分析如下,第一行:
    GET / HTTP/1.1
    GET表示一个读取请求,将从服务器获得网页数据,/表示URL的路径,URL总是以/开头,/就表示首页,最后的HTTP/1.1指示采用的HTTP协议版本是1.1。目前HTTP协议的版本就是1.1,但是大部分服务器也支持1.0版本,主要区别在于1.1版本允许多个HTTP请求复用一个TCP连接,以加快传输速度。
    从第二行开始,每一行都类似于Xxx: abcdefg:
    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响应的Body就是HTML源码,我们在菜单栏选择“视图”,“开发者”,“查看网页源码”就可以在浏览器中直接查看HTML源码:
    当浏览器读取到新浪首页的HTML源码后,它会解析HTML,显示页面,然后,根据HTML里面的各种链接,再发送HTTP请求给新浪服务器,拿到相应的图片、视频、Flash、JavaScript脚本、CSS等各种资源,最终显示出一个完整的页面。所以我们在Network下面能看到很多额外的HTTP请求。
    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指定;
    以及其他相关的Header;
    通常服务器的HTTP响应会携带内容,也就是有一个Body,包含响应的内容,网页的HTML源码就在Body中。
    步骤3:如果浏览器还需要继续向服务器请求其他资源,比如图片,就再次发出HTTP请求,重复步骤1、2。
    Web采用的HTTP协议采用了非常简单的请求-响应模式,从而大大简化了开发。当我们编写一个页面时,我们只需要在HTTP请求中把HTML发送出去,不需要考虑如何附带图片、视频等,浏览器如果需要请求图片和视频,它会发送另一个HTTP请求,因此,一个HTTP请求只处理一个资源。
    HTTP协议同时具备极强的扩展性,虽然浏览器请求的是http://www.sina.com.cn/首页,但是新浪在HTML中可以链入其他服务器的资源,比如<img src="http://i1.sinaimg.cn/home/2013/1008/U8455P30DT20131008135420.png">,从而将请求压力分散到各个服务器上,并且,一个站点可以链接到其他站点,无数个站点互相链接起来,就形成了World Wide Web,简称WWW。
    HTTP格式
    每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的。
    HTTP协议是一种文本协议,所以,它的格式也非常简单。HTTP GET请求的格式:
    GET /path HTTP/1.1Header1: Value1Header2: Value2Header3: Value3
    每个Header一行一个,换行符是\r\n。
    HTTP POST请求的格式:
    POST /path HTTP/1.1Header1: Value1Header2: Value2Header3: Value3body data goes here...
    当遇到连续两个\r\n时,Header部分结束,后面的数据全部是Body。
    HTTP响应的格式:
    200 OKHeader1: Value1Header2: Value2Header3: Value3body data goes here...
    HTTP响应如果包含body,也是通过\r\n\r\n来分隔的。请再次注意,Body的数据类型由Content-Type头来确定,如果是网页,Body就是文本,如果是图片,Body就是图片的二进制数据。
    当存在Content-Encoding时,Body数据是被压缩的,最常见的压缩方式是gzip,所以,看到Content-Encoding: gzip时,需要将Body数据先解压缩,才能得到真正的数据。压缩的目的在于减少Body的大小,加快网络传输。


    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    前天 09:07
  • 签到天数: 11 天

    连续签到: 2 天

    [LV.3]测试连长

    62#
     楼主| 发表于 2017-7-6 15:33:11 | 只看该作者
    本帖最后由 八戒你干嘛 于 2017-7-6 16:26 编辑

    HTML简介[转]
    网页就是HTML?这么理解大概没错。因为网页中不但包含文字,还有图片、视频、Flash小游戏,有复杂的排版、动画效果,所以,HTML定义了一套语法规则,来告诉浏览器如何把一个丰富多彩的页面显示出来。
    HTML长什么样?上次我们看了新浪首页的HTML源码,如果仔细数数,竟然有6000多行!
    所以,学HTML,就不要指望从新浪入手了。我们来看看最简单的HTML长什么样:
    <html><head>  <title>Hello</title></head><body>  <h1>Hello, world!</h1></body></html>
    可以用文本编辑器编写HTML,然后保存为hello.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>Hello, world!</h1></body></html>
    点击标题后效果如下:
    小结
    如果要学习Web开发,首先要对HTML、CSS和JavaScript作一定的了解。HTML定义了页面的内容,CSS来控制页面元素的样式,而JavaScript负责页面的交互逻辑。
    讲解HTML、CSS和JavaScript就可以写3本书,对于优秀的Web开发人员来说,精通HTML、CSS和JavaScript是必须的。
    当我们用Python或者其他语言开发Web应用时,我们就是要在服务器端动态创建出HTML,这样,浏览器就会向不同的用户显示出不同的Web页面。


    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    前天 09:07
  • 签到天数: 11 天

    连续签到: 2 天

    [LV.3]测试连长

    63#
     楼主| 发表于 2017-7-6 15:35:02 | 只看该作者
    本帖最后由 八戒你干嘛 于 2017-7-6 16:28 编辑

    WSGI接口[转]
    了解了HTTP协议和HTML文档,我们其实就明白了一个Web应用的本质就是:
    • 浏览器发送一个HTTP请求;
    • 服务器收到请求,生成一个HTML文档;
    • 服务器把HTML文档作为HTTP响应的Body发送给浏览器;
    • 浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示。

    所以,最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。Apache、Nginx、Lighttpd等这些常见的静态服务器就是干这件事情的。
    如果要动态生成HTML,就需要把上述步骤自己来实现。不过,接受HTTP请求、解析HTTP请求、发送HTTP响应都是苦力活,如果我们自己来写这些底层代码,还没开始写动态HTML呢,就得花个把月去读HTTP规范。
    正确的做法是底层代码由专门的服务器软件实现,我们用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响应的函数。

    在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发送给浏览器。
    有了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标准,但是不考虑任何运行效率,仅供开发和测试使用。
    运行WSGI服务
    我们先编写hello.py,实现Web应用程序的WSGI处理函数:
    # hello.pydef application(environ, start_response):    start_response('200 OK', [('Content-Type', 'text/html')])    return [b'<h1>Hello, web!</h1>']
    然后,再编写一个server.py,负责启动WSGI服务器,加载application()函数:
    # 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()
    确保以上两个文件在同一个目录下,然后在命令行输入python server.py来启动WSGI服务器:
    注意:如果8000端口已被其他程序占用,启动将失败,请修改成其他端口。
    启动成功后,打开浏览器,输入http://localhost:8000/,就可以看到结果了:
    在命令行可以看到wsgiref打印的log信息:
    按Ctrl+C终止服务器。
    如果你觉得这个Web应用太简单了,可以稍微改造一下,从environ里读取PATH_INFO,这样可以显示更加动态的内容:
    # hello.pydef application(environ, start_response):    start_response('200 OK', [('Content-Type', 'text/html')])    body = '<h1>Hello, %s!</h1>' % (environ['PATH_INFO'][1:] or 'web')    return [body.encode('utf-8')]
    你可以在地址栏输入用户名作为URL的一部分,将返回Hello, xxx!:
    是不是有点Web App的感觉了?
    小结
    无论多么复杂的Web应用程序,入口都是一个WSGI处理函数。HTTP请求的所有输入信息都可以通过environ获得,HTTP响应的输出都可以通过start_response()加上函数返回值作为Body。
    复杂的Web应用程序,光靠一个WSGI函数来处理还是太底层了,我们需要在WSGI之上再抽象出Web框架,进一步简化Web开发。


    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    前天 09:07
  • 签到天数: 11 天

    连续签到: 2 天

    [LV.3]测试连长

    64#
     楼主| 发表于 2017-7-6 15:44:59 | 只看该作者
    本帖最后由 八戒你干嘛 于 2017-7-6 16:30 编辑

    使用Web框架[转]
    了解了WSGI框架,我们发现:其实一个Web App,就是写一个WSGI的处理函数,针对每个HTTP请求进行响应。
    但是如何处理HTTP请求不是问题,问题是如何处理100个不同的URL。
    每一个URL可以对应GET和POST请求,当然还有PUT、DELETE等请求,但是我们通常只考虑最常见的GET和POST请求。
    一个最简单的想法是从environ变量里取出HTTP请求的信息,然后逐个判断:
    def application(environ, start_response):    method = environ['REQUEST_METHOD']    path = environ['PATH_INFO']    if method=='GET' and path=='/':        return handle_home(environ, start_response)    if method=='POST' and path='/signin':        return handle_signin(environ, start_response)    ...
    只是这么写下去代码是肯定没法维护了。
    代码这么写没法维护的原因是因为WSGI提供的接口虽然比HTTP接口高级了不少,但和Web App的处理逻辑比,还是比较低级,我们需要在WSGI接口之上能进一步抽象,让我们专注于用一个函数处理一个URL,至于URL到函数的映射,就交给Web框架来做。
    由于用Python开发一个Web框架十分容易,所以Python有上百个开源的Web框架。这里我们先不讨论各种Web框架的优缺点,直接选择一个比较流行的Web框架——Flask来使用。
    用Flask编写Web App比WSGI接口简单(这不是废话么,要是比WSGI还复杂,用框架干嘛?),我们先用pip安装Flask:
    $ pip install flask
    然后写一个app.py,处理3个URL,分别是:
    • GET /:首页,返回Home;
    • GET /signin:登录页,显示登录表单;
    • POST /signin:处理登录表单,显示登录结果。

    注意噢,同一个URL/signin分别有GET和POST两种请求,映射到两个处理函数中。
    Flask通过Python的装饰器在内部自动地把URL和函数给关联起来,所以,我们写出来的代码就像这样:
    from flask import Flaskfrom flask import requestapp = 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上监听:
    $ python app.py  * Running on http://127.0.0.1:5000/
    打开浏览器,输入首页地址http://localhost:5000/:
    首页显示正确!
    再在浏览器地址栏输入http://localhost:5000/signin,会显示登录表单:
    输入预设的用户名admin和口令password,登录成功:
    输入其他错误的用户名和口令,登录失败:
    实际的Web App应该拿到用户名和口令后,去数据库查询再比对,来判断用户是否能登录成功。
    除了Flask,常见的Python Web框架还有:
    • Django:全能型Web框架;
    • web.py:一个小巧的Web框架;
    • Bottle:和Flask类似的Web框架;
    • Tornado:Facebook的开源异步Web框架。

    当然了,因为开发Python的Web框架也不是什么难事,我们后面也会讲到开发Web框架的内容。
    小结
    有了Web框架,我们在编写Web应用时,注意力就从WSGI处理函数转移到URL+对应的处理函数,这样,编写Web App就更加简单了。
    在编写URL处理函数时,除了配置URL外,从HTTP请求拿到用户数据也是非常重要的。Web框架都提供了自己的API来实现这些功能。Flask通过request.form['name']来获取表单的内容。

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    前天 09:07
  • 签到天数: 11 天

    连续签到: 2 天

    [LV.3]测试连长

    65#
     楼主| 发表于 2017-7-6 15:52:47 | 只看该作者
    本帖最后由 八戒你干嘛 于 2017-7-6 16:32 编辑

    使用模板[转]
    Web框架把我们从WSGI中拯救出来了。现在,我们只需要不断地编写函数,带上URL,就可以继续Web App的开发了。
    但是,Web App不仅仅是处理逻辑,展示给用户的页面也非常重要。在函数中返回一个包含HTML的字符串,简单的页面还可以,但是,想想新浪首页的6000多行的HTML,你确信能在Python的字符串中正确地写出来么?反正我是做不到。
    俗话说得好,不懂前端的Python工程师不是好的产品经理。有Web开发经验的同学都明白,Web App最复杂的部分就在HTML页面。HTML不仅要正确,还要通过CSS美化,再加上复杂的JavaScript脚本来实现各种交互和动画效果。总之,生成HTML页面的难度很大。
    由于在Python代码里拼字符串是不现实的,所以,模板技术出现了。
    使用模板,我们需要预先准备一个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_templateapp = 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在同级目录下:
    启动python app.py,看看使用模板的页面效果:
    通过MVC,我们在Python代码中处理M:Model和C:Controller,而V:View是通过模板处理的,这样,我们就成功地把Python代码和HTML代码最大限度地分离了。
    使用模板的另一大好处是,模板改起来很方便,而且,改完保存后,刷新浏览器就能看到最新的效果,这对于调试HTML、CSS和JavaScript的前端工程师来说实在是太重要了。
    在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 }}的模板。

    小结
    有了MVC,我们就分离了Python代码和HTML代码。HTML代码全部放到模板里,写起来更有效率。


    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    前天 09:07
  • 签到天数: 11 天

    连续签到: 2 天

    [LV.3]测试连长

    66#
     楼主| 发表于 2017-7-6 16:58:11 | 只看该作者
    异步IO【转】
    在IO编程一节中,我们已经知道,CPU的速度远远快于磁盘、网络等IO。在一个线程中,CPU执行代码的速度极快,然而,一旦遇到IO操作,如读写文件、发送网络数据时,就需要等待IO操作完成,才能继续进行下一步操作。这种情况称为同步IO。
    在IO操作的过程中,当前线程被挂起,而其他需要CPU执行的代码就无法被当前线程执行了。
    因为一个IO操作就阻塞了当前线程,导致其他代码无法执行,所以我们必须使用多线程或者多进程来并发执行代码,为多个用户服务。每个用户都会分配一个线程,如果遇到IO导致线程被挂起,其他用户的线程不受影响。
    多线程和多进程的模型虽然解决了并发问题,但是系统不能无上限地增加线程。由于系统切换线程的开销也很大,所以,一旦线程数量过多,CPU的时间就花在线程切换上了,真正运行代码的时间就少了,结果导致性能严重下降。
    由于我们要解决的问题是CPU高速执行能力和IO设备的龟速严重不匹配,多线程和多进程只是解决这一问题的一种方法。
    另一种解决IO问题的方法是异步IO。当代码需要执行一个耗时的IO操作时,它只发出IO指令,并不等待IO结果,然后就去执行其他代码了。一段时间后,当IO返回结果时,再通知CPU进行处理。
    可以想象如果按普通顺序写出的代码实际上是没法完成异步IO的:
    do_some_code()f = open('/path/to/file', 'r')r = f.read() # <== 线程停在此处等待IO操作结果# IO操作完成后线程才能继续执行:do_some_code(r)
    所以,同步IO模型的代码是无法实现异步IO模型的。
    异步IO模型需要一个消息循环,在消息循环中,主线程不断地重复“读取消息-处理消息”这一过程:
    loop = get_event_loop()while True:    event = loop.get_event()    process_event(event)
    消息模型其实早在应用在桌面应用程序中了。一个GUI程序的主线程就负责不停地读取消息并处理消息。所有的键盘、鼠标等消息都被发送到GUI程序的消息队列中,然后由GUI程序的主线程处理。
    由于GUI线程处理键盘、鼠标等消息的速度非常快,所以用户感觉不到延迟。某些时候,GUI线程在一个消息处理的过程中遇到问题导致一次消息处理时间过长,此时,用户会感觉到整个GUI程序停止响应了,敲键盘、点鼠标都没有反应。这种情况说明在消息模型中,处理一个消息必须非常迅速,否则,主线程将无法及时处理消息队列中的其他消息,导致程序看上去停止响应。
    消息模型是如何解决同步IO必须等待IO操作这一问题的呢?当遇到IO操作时,代码只负责发出IO请求,不等待IO结果,然后直接结束本轮消息处理,进入下一轮消息处理过程。当IO操作完成后,将收到一条“IO完成”的消息,处理该消息时就可以直接获取IO操作结果。
    在“发出IO请求”到收到“IO完成”的这段时间里,同步IO模型下,主线程只能挂起,但异步IO模型下,主线程并没有休息,而是在消息循环中继续处理其他消息。这样,在异步IO模型下,一个线程就可以同时处理多个IO请求,并且没有切换线程的操作。对于大多数IO密集型的应用程序,使用异步IO将大大提升系统的多任务处理能力。


    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    前天 09:07
  • 签到天数: 11 天

    连续签到: 2 天

    [LV.3]测试连长

    67#
     楼主| 发表于 2017-7-6 17:00:24 | 只看该作者
    协程[转]
    在学习异步IO模型前,我们先来了解协程。
    协程,又称微线程,纤程。英文名Coroutine。
    协程的概念很早就提出来了,但直到最近几年才在某些语言(如Lua)中得到广泛应用。
    子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。
    所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。
    子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。
    协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
    注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。比如子程序A、B:
    def A():    print('1')    print('2')    print('3')def B():    print('x')    print('y')    print('z')
    假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:
    12xy3z
    但是在A中是没有调用B的,所以协程的调用比函数调用理解起来要难一些。
    看起来A、B的执行有点像多线程,但协程的特点在于是一个线程执行,那和多线程比,协程有何优势?
    最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
    第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
    因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
    Python对协程的支持是通过generator实现的。
    在generator中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值。
    但是Python的yield不但可以返回一个值,它还可以接收调用者发出的参数。
    来看例子:
    传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。
    如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:
    def consumer():    r = ''    while True:        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)
    执行结果:
    [PRODUCER] Producing 1...[CONSUMER] Consuming 1...[PRODUCER] Consumer return: 200 OK[PRODUCER] Producing 2...[CONSUMER] Consuming 2...[PRODUCER] Consumer return: 200 OK[PRODUCER] Producing 3...[CONSUMER] Consuming 3...[PRODUCER] Consumer return: 200 OK[PRODUCER] Producing 4...[CONSUMER] Consuming 4...[PRODUCER] Consumer return: 200 OK[PRODUCER] Producing 5...[CONSUMER] Consuming 5...[PRODUCER] Consumer return: 200 OK
    注意到consumer函数是一个generator,把一个consumer传入produce后:
    • 首先调用c.send(None)启动生成器;
    • 然后,一旦生产了东西,通过c.send(n)切换到consumer执行;
    • consumer通过yield拿到消息,处理,又通过yield把结果传回;
    • produce拿到consumer处理的结果,继续生产下一条消息;
    • produce决定不生产了,通过c.close()关闭consumer,整个过程结束。

    整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。
    最后套用Donald Knuth的一句话总结协程的特点:
    “子程序就是协程的一种特例。”


    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    前天 09:07
  • 签到天数: 11 天

    连续签到: 2 天

    [LV.3]测试连长

    68#
     楼主| 发表于 2017-7-6 17:04:54 | 只看该作者
    asyncio[转]
    asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。
    asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。
    用asyncio实现Hello world代码如下:
    import asyncio@asyncio.coroutinedef hello():    print("Hello world!")    # 异步调用asyncio.sleep(1):    r = yield from asyncio.sleep(1)    print("Hello again!")# 获取EventLoop:loop = asyncio.get_event_loop()# 执行coroutineloop.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了,因此可以实现并发执行。
    我们用Task封装两个coroutine试试:
    import threadingimport asyncio@asyncio.coroutinedef hello():    print('Hello world! (%s)' % threading.currentThread())    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()
    观察执行过程:
    Hello world! (<_MainThread(MainThread, started 140735195337472)>)Hello world! (<_MainThread(MainThread, started 140735195337472)>)(暂停约1秒)Hello again! (<_MainThread(MainThread, started 140735195337472)>)Hello again! (<_MainThread(MainThread, started 140735195337472)>)
    由打印的当前线程名称可以看出,两个coroutine是由同一个线程并发执行的。
    如果把asyncio.sleep()换成真正的IO操作,则多个coroutine就可以由一个线程并发执行。
    我们用asyncio的异步网络连接来获取sina、sohu和163的网站首页:
    import asyncio@asyncio.coroutinedef 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()
    执行结果如下:
    wget www.sohu.com...wget www.sina.com.cn...wget www.163.com...(等待一段时间)(打印出sohu的header)www.sohu.com header > HTTP/1.1 200 OKwww.sohu.com header > Content-Type: text/html...(打印出sina的header)www.sina.com.cn header > HTTP/1.1 200 OKwww.sina.com.cn header > Date: Wed, 20 May 2015 04:56:33 GMT...(打印出163的header)www.163.com header > HTTP/1.0 302 Moved Temporarilywww.163.com header > Server: Cdn Cache Server V2.0...
    可见3个连接由一个线程通过coroutine并发完成。
    小结
    asyncio提供了完善的异步IO支持;
    异步操作需要在coroutine中通过yield from完成;
    多个coroutine可以封装成一组Task然后并发执行。


    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    前天 09:07
  • 签到天数: 11 天

    连续签到: 2 天

    [LV.3]测试连长

    69#
     楼主| 发表于 2017-7-6 17:05:57 | 只看该作者
    async/await[转]
    用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。
    让我们对比一下上一节的代码:
    @asyncio.coroutinedef hello():    print("Hello world!")    r = yield from asyncio.sleep(1)    print("Hello again!")
    用新语法重新编写如下:
    async def hello():    print("Hello world!")    r = await asyncio.sleep(1)    print("Hello again!")
    剩下的代码保持不变。
    小结
    Python从3.5版本开始为asyncio提供了async和await的新语法;
    注意新语法只能用在Python 3.5以及后续版本,如果使用3.4版本,则仍需使用上一节的方案。
    练习
    将上一节的异步获取sina、sohu和163的网站首页源码用新语法重写并运行


    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    前天 09:07
  • 签到天数: 11 天

    连续签到: 2 天

    [LV.3]测试连长

    70#
     楼主| 发表于 2017-7-6 17:07:09 | 只看该作者
    aiohttp[转]
    asyncio可以实现单线程并发IO操作。如果仅用在客户端,发挥的威力不大。如果把asyncio用在服务器端,例如Web服务器,由于HTTP连接就是IO操作,因此可以用单线程+coroutine实现多用户的高并发支持。
    asyncio实现了TCP、UDP、SSL等协议,aiohttp则是基于asyncio实现的HTTP框架。
    我们先安装aiohttp:
    pip install aiohttp
    然后编写一个HTTP服务器,分别处理以下URL:
    • / - 首页返回b'<h1>Index</h1>';
    • /hello/{name} - 根据URL参数返回文本hello, %s!。

    代码如下:
    import asynciofrom aiohttp import webasync def index(request):    await asyncio.sleep(0.5)    return web.Response(body=b'<h1>Index</h1>')async def hello(request):    await asyncio.sleep(0.5)    text = '<h1>hello, %s!</h1>' % request.match_info['name']    return web.Response(body=text.encode('utf-8'))async def init(loop):    app = web.Application(loop=loop)    app.router.add_route('GET', '/', index)    app.router.add_route('GET', '/hello/{name}', hello)    srv = await loop.create_server(app.make_handler(), '127.0.0.1', 8000)    print('Server started at http://127.0.0.1:8000...')    return srvloop = asyncio.get_event_loop()loop.run_until_complete(init(loop))loop.run_forever()
    注意aiohttp的初始化函数init()也是一个coroutine,loop.create_server()则利用asyncio创建TCP服务。


    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    前天 09:07
  • 签到天数: 11 天

    连续签到: 2 天

    [LV.3]测试连长

    71#
     楼主| 发表于 2017-7-6 17:12:13 | 只看该作者
    实战【转】
    看完了教程,是不是有这么一种感觉:看的时候觉得很简单,照着教程敲代码也没啥大问题。
    于是准备开始独立写代码,就发现不知道从哪开始下手了。
    这种情况是完全正常的。好比学写作文,学的时候觉得简单,写的时候就无从下笔了。
    虽然这个教程是面向小白的零基础Python教程,但是我们的目标不是学到60分,而是学到90分。
    所以,用Python写一个真正的Web App吧!
    目标
    我们设定的实战目标是一个Blog网站,包含日志、用户和评论3大部分。
    很多童鞋会想,这是不是太简单了?
    比如webpy.org上就提供了一个Blog的例子,目测也就100行代码。
    但是,这样的页面:
    你拿得出手么?
    我们要写出用户真正看得上眼的页面,首页长得像这样:
    评论区:
    还有极其强大的后台管理页面:
    是不是一下子变得高端大气上档次了?
    项目名称
    必须是高端大气上档次的名称,命名为awesome-python3-webapp。
    项目计划
    项目计划开发周期为16天。每天,你需要完成教程中的内容。如果你觉得编写代码难度实在太大,可以参考一下当天在GitHub上的代码。
    以此类推,要预览awesome-python3-webapp的最终页面效果。


    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    前天 09:07
  • 签到天数: 11 天

    连续签到: 2 天

    [LV.3]测试连长

    73#
     楼主| 发表于 2017-7-7 09:19:54 | 只看该作者
    Day 1 - 搭建开发环境[转]
    搭建开发环境
    首先,确认系统安装的Python版本是3.5.x:
    $ python3 --versionPython 3.5.1
    然后,用pip安装开发Web App需要的第三方库:
    异步框架aiohttp:
    $pip3 install aiohttp
    前端模板引擎jinja2:
    $ pip3 install jinja2
    MySQL 5.x数据库,从官方网站下载并安装,安装完毕后,请务必牢记root口令。为避免遗忘口令,建议直接把root口令设置为password;
    MySQL的Python异步驱动程序aiomysql:
    $ pip3 install aiomysql项目结构
    选择一个工作目录,然后,我们建立如下的目录结构:
    awesome-python3-webapp/  <-- 根目录|+- backup/               <-- 备份目录|+- conf/                 <-- 配置文件|+- dist/                 <-- 打包目录|+- www/                  <-- Web目录,存放.py文件|  ||  +- static/            <-- 存放静态文件|  ||  +- templates/         <-- 存放模板文件|+- ios/                  <-- 存放iOS App工程|+- LICENSE               <-- 代码LICENSE
    创建好项目的目录结构后,建议同时建立git仓库并同步至GitHub,保证代码修改的安全。
    要了解git和GitHub的用法,请移步Git教程。
    开发工具
    自备,推荐用Sublime Text,请参考使用文本编辑器。


    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    前天 09:07
  • 签到天数: 11 天

    连续签到: 2 天

    [LV.3]测试连长

    74#
     楼主| 发表于 2017-7-7 09:25:28 | 只看该作者
    Day 2 - 编写Web App骨架[转]
    由于我们的Web App建立在asyncio的基础上,因此用aiohttp写一个基本的app.py:
    import logging; logging.basicConfig(level=logging.INFO)import asyncio, os, json, timefrom datetime import datetimefrom aiohttp import webdef index(request):    return web.Response(body=b'<h1>Awesome</h1>')@asyncio.coroutinedef init(loop):    app = web.Application(loop=loop)    app.router.add_route('GET', '/', index)    srv = yield from loop.create_server(app.make_handler(), '127.0.0.1', 9000)    logging.info('server started at http://127.0.0.1:9000...')    return srvloop = asyncio.get_event_loop()loop.run_until_complete(init(loop))loop.run_forever()
    运行python app.py,Web App将在9000端口监听HTTP请求,并且对首页/进行响应:
    $ python3 app.pyINFO:root:server started at http://127.0.0.1:9000...
    这里我们简单地返回一个Awesome字符串,在浏览器中可以看到效果:
    这说明我们的Web App骨架已经搭好了,可以进一步往里面添加更多的东西。


    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    前天 09:07
  • 签到天数: 11 天

    连续签到: 2 天

    [LV.3]测试连长

    75#
     楼主| 发表于 2017-7-7 09:30:07 | 只看该作者
    Day 3 - 编写ORM[转]
    在一个Web App中,所有数据,包括用户信息、发布的日志、评论等,都存储在数据库中。在awesome-python3-webapp中,我们选择MySQL作为数据库。
    Web App里面有很多地方都要访问数据库。访问数据库需要创建数据库连接、游标对象,然后执行SQL语句,最后处理异常,清理资源。这些访问数据库的代码如果分散到各个函数中,势必无法维护,也不利于代码复用。
    所以,我们要首先把常用的SELECT、INSERT、UPDATE和DELETE操作用函数封装起来。
    由于Web框架使用了基于asyncio的aiohttp,这是基于协程的异步模型。在协程中,不能调用普通的同步IO操作,因为所有用户都是由一个线程服务的,协程的执行速度必须非常快,才能处理大量用户的请求。而耗时的IO操作不能在协程中以同步的方式调用,否则,等待一个IO操作时,系统无法响应任何其他用户。
    这就是异步编程的一个原则:一旦决定使用异步,则系统每一层都必须是异步,“开弓没有回头箭”。
    幸运的是aiomysql为MySQL数据库提供了异步IO的驱动。
    创建连接池
    我们需要创建一个全局的连接池,每个HTTP请求都可以从连接池中直接获取数据库连接。使用连接池的好处是不必频繁地打开和关闭数据库连接,而是能复用就尽量复用。
    连接池由全局变量__pool存储,缺省情况下将编码设置为utf8,自动提交事务:
    @asyncio.coroutinedef create_pool(loop, **kw):    logging.info('create database connection pool...')    global __pool    __pool = yield from aiomysql.create_pool(        host=kw.get('host', 'localhost'),        port=kw.get('port', 3306),        user=kw['user'],        password=kw['password'],        db=kw['db'],        charset=kw.get('charset', 'utf8'),        autocommit=kw.get('autocommit', True),        maxsize=kw.get('maxsize', 10),        minsize=kw.get('minsize', 1),        loop=loop    )Select
    要执行SELECT语句,我们用select函数执行,需要传入SQL语句和SQL参数:
    @asyncio.coroutinedef select(sql, args, size=None):    log(sql, args)    global __pool    with (yield from __pool) as conn:        cur = yield from conn.cursor(aiomysql.DictCursor)        yield from cur.execute(sql.replace('?', '%s'), args or ())        if size:            rs = yield from cur.fetchmany(size)        else:            rs = yield from cur.fetchall()        yield from cur.close()        logging.info('rows returned: %s' % len(rs))        return rs
    SQL语句的占位符是?,而MySQL的占位符是%s,select()函数在内部自动替换。注意要始终坚持使用带参数的SQL,而不是自己拼接SQL字符串,这样可以防止SQL注入攻击。
    注意到yield from将调用一个子协程(也就是在一个协程中调用另一个协程)并直接获得子协程的返回结果。
    如果传入size参数,就通过fetchmany()获取最多指定数量的记录,否则,通过fetchall()获取所有记录。
    Insert, Update, Delete
    要执行INSERT、UPDATE、DELETE语句,可以定义一个通用的execute()函数,因为这3种SQL的执行都需要相同的参数,以及返回一个整数表示影响的行数:
    @asyncio.coroutinedef execute(sql, args):    log(sql)    with (yield from __pool) as conn:        try:            cur = yield from conn.cursor()            yield from cur.execute(sql.replace('?', '%s'), args)            affected = cur.rowcount            yield from cur.close()        except BaseException as e:            raise        return affected
    execute()函数和select()函数所不同的是,cursor对象不返回结果集,而是通过rowcount返回结果数。
    ORM
    有了基本的select()和execute()函数,我们就可以开始编写一个简单的ORM了。
    设计ORM需要从上层调用者角度来设计。
    我们先考虑如何定义一个User对象,然后把数据库表users和它关联起来。
    from orm import Model, StringField, IntegerFieldclass User(Model):    __table__ = 'users'    id = IntegerField(primary_key=True)    name = StringField()
    注意到定义在User类中的__table__、id和name是类的属性,不是实例的属性。所以,在类级别上定义的属性用来描述User对象和表的映射关系,而实例属性必须通过__init__()方法去初始化,所以两者互不干扰:
    # 创建实例:user = User(id=123, name='Michael')# 存入数据库:user.insert()# 查询所有User对象:users = User.findAll()


    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    前天 09:07
  • 签到天数: 11 天

    连续签到: 2 天

    [LV.3]测试连长

    76#
     楼主| 发表于 2017-7-7 09:30:47 | 只看该作者
    定义Model
    首先要定义的是所有ORM映射的基类Model:
    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 getValue(self, key):        return getattr(self, key, None)    def getValueOrDefault(self, key):        value = getattr(self, key, None)        if value is None:            field = self.__mappings__[key]            if field.default is not None:                value = field.default() if callable(field.default) else field.default                logging.debug('using default value for %s: %s' % (key, str(value)))                setattr(self, key, value)        return value
    Model从dict继承,所以具备所有dict的功能,同时又实现了特殊方法__getattr__()和__setattr__(),因此又可以像引用普通字段那样写:
    >>> user['id']123>>> user.id123
    以及Field和各种Field子类:
    class Field(object):    def __init__(self, name, column_type, primary_key, default):        self.name = name        self.column_type = column_type        self.primary_key = primary_key        self.default = default    def __str__(self):        return '<%s, %s:%s>' % (self.__class__.__name__, self.column_type, self.name)
    映射varchar的StringField:
    class StringField(Field):    def __init__(self, name=None, primary_key=False, default=None, ddl='varchar(100)'):        super().__init__(name, ddl, primary_key, default)
    注意到Model只是一个基类,如何将具体的子类如User的映射信息读取出来呢?答案就是通过metaclass:ModelMetaclass:
    class ModelMetaclass(type):    def __new__(cls, name, bases, attrs):        # 排除Model类本身:        if name=='Model':            return type.__new__(cls, name, bases, attrs)        # 获取table名称:        tableName = attrs.get('__table__', None) or name        logging.info('found model: %s (table: %s)' % (name, tableName))        # 获取所有的Field和主键名:        mappings = dict()        fields = []        primaryKey = None        for k, v in attrs.items():            if isinstance(v, Field):                logging.info('  found mapping: %s ==> %s' % (k, v))                mappings[k] = v                if v.primary_key:                    # 找到主键:                    if primaryKey:                        raise RuntimeError('Duplicate primary key for field: %s' % k)                    primaryKey = k                else:                    fields.append(k)        if not primaryKey:            raise RuntimeError('Primary key not found.')        for k in mappings.keys():            attrs.pop(k)        escaped_fields = list(map(lambda f: '`%s`' % f, fields))        attrs['__mappings__'] = mappings # 保存属性和列的映射关系        attrs['__table__'] = tableName        attrs['__primary_key__'] = primaryKey # 主键属性名        attrs['__fields__'] = fields # 除主键外的属性名        # 构造默认的SELECT, INSERT, UPDATE和DELETE语句:        attrs['__select__'] = 'select `%s`, %s from `%s`' % (primaryKey, ', '.join(escaped_fields), tableName)        attrs['__insert__'] = 'insert into `%s` (%s, `%s`) values (%s)' % (tableName, ', '.join(escaped_fields), primaryKey, create_args_string(len(escaped_fields) + 1))        attrs['__update__'] = 'update `%s` set %s where `%s`=?' % (tableName, ', '.join(map(lambda f: '`%s`=?' % (mappings.get(f).name or f), fields)), primaryKey)        attrs['__delete__'] = 'delete from `%s` where `%s`=?' % (tableName, primaryKey)        return type.__new__(cls, name, bases, attrs)
    这样,任何继承自Model的类(比如User),会自动通过ModelMetaclass扫描映射关系,并存储到自身的类属性如__table__、__mappings__中。
    然后,我们往Model类添加class方法,就可以让所有子类调用class方法:
    class Model(dict):    ...    @classmethod    @asyncio.coroutine    def find(cls, pk):        ' find object by primary key. '        rs = yield from select('%s where `%s`=?' % (cls.__select__, cls.__primary_key__), [pk], 1)        if len(rs) == 0:            return None        return cls(**rs[0])
    User类现在就可以通过类方法实现主键查找:
    user = yield from User.find('123')
    往Model类添加实例方法,就可以让所有子类调用实例方法:
    class Model(dict):    ...    @asyncio.coroutine    def save(self):        args = list(map(self.getValueOrDefault, self.__fields__))        args.append(self.getValueOrDefault(self.__primary_key__))        rows = yield from execute(self.__insert__, args)        if rows != 1:            logging.warn('failed to insert record: affected rows: %s' % rows)
    这样,就可以把一个User实例存入数据库:
    user = User(id=123, name='Michael')yield from user.save()
    最后一步是完善ORM,对于查找,我们可以实现以下方法:
    • findAll() - 根据WHERE条件查找;
    • findNumber() - 根据WHERE条件查找,但返回的是整数,适用于select count(*)类型的SQL。

    以及update()和remove()方法。
    所有这些方法都必须用@asyncio.coroutine装饰,变成一个协程。
    调用时需要特别注意:
    user.save()
    没有任何效果,因为调用save()仅仅是创建了一个协程,并没有执行它。一定要用:
    yield from user.save()
    才真正执行了INSERT操作。
    最后看看我们实现的ORM模块一共多少行代码?累计不到300多行。用Python写一个ORM是不是很容易呢?

    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    前天 09:07
  • 签到天数: 11 天

    连续签到: 2 天

    [LV.3]测试连长

    77#
     楼主| 发表于 2017-7-7 09:37:02 | 只看该作者
    Day 4 - 编写Model[转]
    有了ORM,我们就可以把Web App需要的3个表用Model表示出来:
    import time, uuidfrom orm import Model, StringField, BooleanField, FloatField, TextFielddef next_id():    return '%015d%s000' % (int(time.time() * 1000), uuid.uuid4().hex)class User(Model):    __table__ = 'users'    id = StringField(primary_key=True, default=next_id, ddl='varchar(50)')    email = StringField(ddl='varchar(50)')    passwd = StringField(ddl='varchar(50)')    admin = BooleanField()    name = StringField(ddl='varchar(50)')    image = StringField(ddl='varchar(500)')    created_at = FloatField(default=time.time)class Blog(Model):    __table__ = 'blogs'    id = StringField(primary_key=True, default=next_id, ddl='varchar(50)')    user_id = StringField(ddl='varchar(50)')    user_name = StringField(ddl='varchar(50)')    user_image = StringField(ddl='varchar(500)')    name = StringField(ddl='varchar(50)')    summary = StringField(ddl='varchar(200)')    content = TextField()    created_at = FloatField(default=time.time)class Comment(Model):    __table__ = 'comments'    id = StringField(primary_key=True, default=next_id, ddl='varchar(50)')    blog_id = StringField(ddl='varchar(50)')    user_id = StringField(ddl='varchar(50)')    user_name = StringField(ddl='varchar(50)')    user_image = StringField(ddl='varchar(500)')    content = TextField()    created_at = FloatField(default=time.time)
    在编写ORM时,给一个Field增加一个default参数可以让ORM自己填入缺省值,非常方便。并且,缺省值可以作为函数对象传入,在调用save()时自动计算。
    例如,主键id的缺省值是函数next_id,创建时间created_at的缺省值是函数time.time,可以自动设置当前日期和时间。
    日期和时间用float类型存储在数据库中,而不是datetime类型,这么做的好处是不必关心数据库的时区以及时区转换问题,排序非常简单,显示的时候,只需要做一个float到str的转换,也非常容易。
    初始化数据库表
    如果表的数量很少,可以手写创建表的SQL脚本:
    -- schema.sqldrop database if exists awesome;create database awesome;use awesome;grant select, insert, update, delete on awesome.* to 'www-data'@'localhost' identified by 'www-data';create table users (    `id` varchar(50) not null,    `email` varchar(50) not null,    `passwd` varchar(50) not null,    `admin` bool not null,    `name` varchar(50) not null,    `image` varchar(500) not null,    `created_at` real not null,    unique key `idx_email` (`email`),    key `idx_created_at` (`created_at`),    primary key (`id`)) engine=innodb default charset=utf8;create table blogs (    `id` varchar(50) not null,    `user_id` varchar(50) not null,    `user_name` varchar(50) not null,    `user_image` varchar(500) not null,    `name` varchar(50) not null,    `summary` varchar(200) not null,    `content` mediumtext not null,    `created_at` real not null,    key `idx_created_at` (`created_at`),    primary key (`id`)) engine=innodb default charset=utf8;create table comments (    `id` varchar(50) not null,    `blog_id` varchar(50) not null,    `user_id` varchar(50) not null,    `user_name` varchar(50) not null,    `user_image` varchar(500) not null,    `content` mediumtext not null,    `created_at` real not null,    key `idx_created_at` (`created_at`),    primary key (`id`)) engine=innodb default charset=utf8;
    如果表的数量很多,可以从Model对象直接通过脚本自动生成SQL脚本,使用更简单。
    把SQL脚本放到MySQL命令行里执行:
    $ mysql -u root -p < schema.sql
    我们就完成了数据库表的初始化。
    编写数据访问代码
    接下来,就可以真正开始编写代码操作对象了。比如,对于User对象,我们就可以做如下操作:
    import ormfrom models import User, Blog, Commentdef test():    yield from orm.create_pool(user='www-data', password='www-data', database='awesome')    u = User(name='Test', email='test@example.com', passwd='1234567890', image='about:blank')    yield from u.save()for x in test():    pass
    可以在MySQL客户端命令行查询,看看数据是不是正常存储到MySQL里面了。


    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    前天 09:07
  • 签到天数: 11 天

    连续签到: 2 天

    [LV.3]测试连长

    78#
     楼主| 发表于 2017-7-7 09:40:14 | 只看该作者
    Day 5 - 编写Web框架[转]
    在正式开始Web开发前,我们需要编写一个Web框架。
    aiohttp已经是一个Web框架了,为什么我们还需要自己封装一个?
    原因是从使用者的角度来说,aiohttp相对比较底层,编写一个URL的处理函数需要这么几步:
    第一步,编写一个用@asyncio.coroutine装饰的函数:
    @asyncio.coroutinedef handle_url_xxx(request):    pass
    第二步,传入的参数需要自己从request中获取:
    url_param = request.match_info['key']query_params = parse_qs(request.query_string)
    最后,需要自己构造Response对象:
    text = render('template', data)return web.Response(text.encode('utf-8'))
    这些重复的工作可以由框架完成。例如,处理带参数的URL/blog/{id}可以这么写:
    @get('/blog/{id}')def get_blog(id):    pass
    处理query_string参数可以通过关键字参数**kw或者命名关键字参数接收:
    @get('/api/comments')def api_comments(*, page='1'):    pass
    对于函数的返回值,不一定是web.Response对象,可以是str、bytes或dict。
    如果希望渲染模板,我们可以这么返回一个dict:
    return {    '__template__': 'index.html',    'data': '...'}
    因此,Web框架的设计是完全从使用者出发,目的是让使用者编写尽可能少的代码。
    编写简单的函数而非引入request和web.Response还有一个额外的好处,就是可以单独测试,否则,需要模拟一个request才能测试。
    @get和@post
    要把一个函数映射为一个URL处理函数,我们先定义@get():
    def get(path):    '''    Define decorator @get('/path')    '''    def decorator(func):        @functools.wraps(func)        def wrapper(*args, **kw):            return func(*args, **kw)        wrapper.__method__ = 'GET'        wrapper.__route__ = path        return wrapper    return decorator
    这样,一个函数通过@get()的装饰就附带了URL信息。
    @post与@get定义类似。
    定义RequestHandler
    URL处理函数不一定是一个coroutine,因此我们用RequestHandler()来封装一个URL处理函数。
    RequestHandler是一个类,由于定义了__call__()方法,因此可以将其实例视为函数。
    RequestHandler目的就是从URL函数中分析其需要接收的参数,从request中获取必要的参数,调用URL函数,然后把结果转换为web.Response对象,这样,就完全符合aiohttp框架的要求:
    class RequestHandler(object):    def __init__(self, app, fn):        self._app = app        self._func = fn        ...    @asyncio.coroutine    def __call__(self, request):        kw = ... 获取参数        r = yield from self._func(**kw)        return r
    再编写一个add_route函数,用来注册一个URL处理函数:
    def add_route(app, fn):    method = getattr(fn, '__method__', None)    path = getattr(fn, '__route__', None)    if path is None or method is None:        raise ValueError('@get or @post not defined in %s.' % str(fn))    if not asyncio.iscoroutinefunction(fn) and not inspect.isgeneratorfunction(fn):        fn = asyncio.coroutine(fn)    logging.info('add route %s %s => %s(%s)' % (method, path, fn.__name__, ', '.join(inspect.signature(fn).parameters.keys())))    app.router.add_route(method, path, RequestHandler(app, fn))
    最后一步,把很多次add_route()注册的调用:
    add_route(app, handles.index)add_route(app, handles.blog)add_route(app, handles.create_comment)...
    变成自动扫描:
    # 自动把handler模块的所有符合条件的函数注册了:add_routes(app, 'handlers')
    add_routes()定义如下:
    def add_routes(app, module_name):    n = module_name.rfind('.')    if n == (-1):        mod = __import__(module_name, globals(), locals())    else:        name = module_name[n+1:]        mod = getattr(__import__(module_name[:n], globals(), locals(), [name]), name)    for attr in dir(mod):        if attr.startswith('_'):            continue        fn = getattr(mod, attr)        if callable(fn):            method = getattr(fn, '__method__', None)            path = getattr(fn, '__route__', None)            if method and path:                add_route(app, fn)
    最后,在app.py中加入middleware、jinja2模板和自注册的支持:
    app = web.Application(loop=loop, middlewares=[    logger_factory, response_factory])init_jinja2(app, filters=dict(datetime=datetime_filter))add_routes(app, 'handlers')add_static(app)middleware
    middleware是一种拦截器,一个URL在被某个函数处理前,可以经过一系列的middleware的处理。
    一个middleware可以改变URL的输入、输出,甚至可以决定不继续处理而直接返回。middleware的用处就在于把通用的功能从每个URL处理函数中拿出来,集中放到一个地方。例如,一个记录URL日志的logger可以简单定义如下:
    @asyncio.coroutinedef logger_factory(app, handler):    @asyncio.coroutine    def logger(request):        # 记录日志:        logging.info('Request: %s %s' % (request.method, request.path))        # 继续处理请求:        return (yield from handler(request))    return logger
    而response这个middleware把返回值转换为web.Response对象再返回,以保证满足aiohttp的要求:
    @asyncio.coroutinedef response_factory(app, handler):    @asyncio.coroutine    def response(request):        # 结果:        r = yield from handler(request)        if isinstance(r, web.StreamResponse):            return r        if isinstance(r, bytes):            resp = web.Response(body=r)            resp.content_type = 'application/octet-stream'            return resp        if isinstance(r, str):            resp = web.Response(body=r.encode('utf-8'))            resp.content_type = 'text/html;charset=utf-8'            return resp        if isinstance(r, dict):            ...
    有了这些基础设施,我们就可以专注地往handlers模块不断添加URL处理函数了,可以极大地提高开发效率。


    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    前天 09:07
  • 签到天数: 11 天

    连续签到: 2 天

    [LV.3]测试连长

    79#
     楼主| 发表于 2017-7-7 09:41:19 | 只看该作者
    Day 6 - 编写配置文件[转]
    有了Web框架和ORM框架,我们就可以开始装配App了。
    通常,一个Web App在运行时都需要读取配置文件,比如数据库的用户名、口令等,在不同的环境中运行时,Web App可以通过读取不同的配置文件来获得正确的配置。
    由于Python本身语法简单,完全可以直接用Python源代码来实现配置,而不需要再解析一个单独的.properties或者.yaml等配置文件。
    默认的配置文件应该完全符合本地开发环境,这样,无需任何设置,就可以立刻启动服务器。
    我们把默认的配置文件命名为config_default.py:
    # config_default.pyconfigs = {    'db': {        'host': '127.0.0.1',        'port': 3306,        'user': 'www-data',        'password': 'www-data',        'database': 'awesome'    },    'session': {        'secret': 'AwEsOmE'    }}
    上述配置文件简单明了。但是,如果要部署到服务器时,通常需要修改数据库的host等信息,直接修改config_default.py不是一个好办法,更好的方法是编写一个config_override.py,用来覆盖某些默认设置:
    # config_override.pyconfigs = {    'db': {        'host': '192.168.0.100'    }}
    把config_default.py作为开发环境的标准配置,把config_override.py作为生产环境的标准配置,我们就可以既方便地在本地开发,又可以随时把应用部署到服务器上。
    应用程序读取配置文件需要优先从config_override.py读取。为了简化读取配置文件,可以把所有配置读取到统一的config.py中:
    # config.pyconfigs = config_default.configstry:    import config_override    configs = merge(configs, config_override.configs)except ImportError:    pass
    这样,我们就完成了App的配置。


    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    无聊
    前天 09:07
  • 签到天数: 11 天

    连续签到: 2 天

    [LV.3]测试连长

    80#
     楼主| 发表于 2017-7-7 09:43:06 | 只看该作者
    Day 7 - 编写MVC[转]
    现在,ORM框架、Web框架和配置都已就绪,我们可以开始编写一个最简单的MVC,把它们全部启动起来。
    通过Web框架的@get和ORM框架的Model支持,可以很容易地编写一个处理首页URL的函数:
    @get('/')def index(request):    users = yield from User.findAll()    return {        '__template__': 'test.html',        'users': users    }
    '__template__'指定的模板文件是test.html,其他参数是传递给模板的数据,所以我们在模板的根目录templates下创建test.html:
    <!DOCTYPE html><html><head>    <meta charset="utf-8" />    <title>Test users - Awesome Python Webapp</title></head><body>    <h1>All users</h1>    {% for u in users %}    <p>{{ u.name }} / {{ u.email }}</p>    {% endfor %}</body></html>
    接下来,如果一切顺利,可以用命令行启动Web服务器:
    $ python3 app.py
    然后,在浏览器中访问http://localhost:9000/。
    如果数据库的users表什么内容也没有,你就无法在浏览器中看到循环输出的内容。可以自己在MySQL的命令行里给users表添加几条记录,然后再访问:


    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    回复 支持 反对

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-9-21 23:39 , Processed in 0.116032 second(s), 21 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表