从零开始学习PYTHON3讲义(十三)

记事本的升级版:网络记事本

《从零开始PYTHON3》第十三讲

网络编程的火热和重要性这里就不多说了,我们直接来看看Python在互联网编程方面的表现。

Python有很多网络编程的第三方扩展包,这里推荐一个我认为最易用的:Flask。安装方法跟其它的包一样:

#首先使用管理员模式执行cmd命令行,然后执行:
pip install flask  #某些系统是pip3 install flask

网络编程基本知识

我们直接以一个示例开始,引入网络编程的概念:

#网络编程演示

#引入网络库
from flask import Flask

#定义一个网络应用
app = Flask(__name__)

@app.route('/')
def index():
    return "你好,世界!"

#执行网络框架
app.run()

Flask是一个网络编程的框架。“程序框架”这个名词我们第一次见到。以前我们见过的程序,所有的控制权都在我们自己手里。程序启动后,从开始顺序执行,根据逻辑会有分支,中间会调用我们定义的函数,直到所有语句执行完成。
框架的意思是,由“框架”帮你完成所有繁琐、枯燥的基本工作,你只需要写跟你相关的一小部分内容。在这个过程中,程序的开始、初始化、定义等各个部分,都有一定的规范和要求,基本属于格式化的内容,大多数照抄过来就可以。而框架会接管程序运行的整个生命期,只在必要的某个时候,调用你自己写的部分程序代码,完成你要求的功能。
框架使用的一般模式都是在程序代码中引入库、初始化库,接着编写自己的程序代码嵌入到应当起作用的位置,最后把程序的控制权交给框架来掌控(运行库模块)。

在上面的例子中,必须由我们自己编写的只有9-11三行代码,其它代码都是规范性的模板,也就是前面说的照抄的、框架的内容。所有的语句,能懂更好,不懂也无需深究,因为那是框架所维护的。上例中9-11三行代码的意思是:

  • 第9行,@app.route函数定义了一个访问路径,“/”是网站主页的意思。也就是说,当访问当我们这个小网站主页的时候,就触发了第9行至第11行的程序。其它的访问,我们忽略不管。

  • 第10行、第11行定义了一个函数,上面说了,第9行定义的访问路径,跟用户实际访问的路径吻合的情况下,这个函数才被激活执行。函数只是返回了一个字符串“你好,世界”。这行文字会显示在访问者的浏览器中。

myweb1

作为编程的初学者课程,我需要多解释一些重要的概念。如果你已经了解了,可以忽略这部分:

前面所说的“访问路径”,在互联网中称为URL,想访问互联网上任何一个网站或者任何一个资源,你都需要在浏览器中输入相应的URL,所以也叫URL地址。比如你想访问寓乐湾官网,一般是在浏览器中输入地址:
       http://www.stemedu.cn/
也有特例,现在手机的app很多,打开app,直接就是网站相应的功能。这里面当然有手机端的程序,但需要与网络交互的部分,实际就是把URL直接写到了手机端的程序中,所以最终仍然是使用URL。只是手机app隐藏了URL不让你看到也不用你输入。
下面是一个更完整的URL的示例: url

  • URL一开始的“http://”是指的这个网站需要使用http协议访问。协议其实就是一种网络通信标准,指的是网页浏览器使用什么样的方式同网络服务器通讯沟通,你可以当做这是另外一种简易语言,浏览器和服务器之间,双方通用的语言。这种“语言”绝大多数功能都是由程序自动处理的,所以一般不需要我们了解太多。
  • 接着的www.stemedu.cn是域名,每个网站都有自己的域名,这跟门牌号码是一样的道理,好的域名曾经价值连城。
  • “:80”是网站服务器提供网页浏览服务的端口号,因为在http协议中,规定了默认的网页浏览服务的端口号就是80。所以平时我们访问各个网站,都是省略端口号的。如果采用了别的端口号码,则必须明确的写出来。比如我们开发程序通常都会使用其它的端口号,所以在调试程序的时候,就要明确的写出来端口号。
  • 端口号的概念仅供了解。现代的每台电脑都很强大,可以同时做很多事情。对外提供服务的时候,每个服务都要占用一个或者多个端口来对外提供服务。如果把电脑比喻为银行,那每个端口就相当于银行的一个办事窗口。每个服务都不能发生冲突和误解,所以都要使用不同的端口号。Flask在开发的时候使用端口5000。正式的网页服务就是上面所说的80端口。
  • 最后的斜线“/”及后面的部分,是在特定服务器上特定路径中的特定文件内容。可以有多级文件夹和最后的资源文件,之间使用“/”分割。如果你使用的是mac电脑或者linux电脑,你会发现网络URL的路径部分跟文件的路径几乎完全是对应的,也都使用同样的分隔斜线“/”,Windows则是使用相反的“\”。
    每个网站肯定都有很多页面,比如很多个商品、很多门课程、很多篇新闻,这都需要用不同的URL路径来区分。
    我们在上例第9行的程序中定义,如果有人访问“/”,也就是网站的根目录,或者也叫首页。会由我们的程序响应访问请求,并处理相应的内容。也就是由其后的函数来处理。
    那么URL地址中的http协议和域名谁处理呢?http协议由Flask框架自动处理,而域名则是经由互联网公用的地址服务(DNS)翻译成一台电脑的IP地址,这一般是由你上网的运营商提供的,比如中国电信。

我们学习编程,用的是自己的电脑,这个电脑当然没有申请域名。那么只能使用电脑的IP地址。
IP地址是每台上网的电脑都具备的一个识别标志,在一个局域网络中是唯一的。类似“192.168.1.100”这样的形式,算得上是真正的网络上的“门牌号码”。举例来说的话,IP地址就好比某人住在“长安街1号”,“长安街1号”就相当于一个IP地址。但如果你有域名,那可以直接说,“明太祖朱元璋”他老人家的故居,“朱元璋故居”就相当于使用域名了。最终这两者都是等价的,实际上计算机本身会更喜欢使用IP地址,因为计算机更适合处理数字。但为了让人类能更容易的使用,所以通常使用域名。
比如你的电脑IP地址是192.168.1.100,那当有别人在浏览器中访问http://192.168.1.100:5000/的时候,通过Flask的框架处理,程序的控制权将转到上面程序第10行、第11行定义的函数中。“:5000”是我们的网页提供服务的端口号,前面说过Python Flask会默认会使用端口5000号,因为不是默认端口,所以输入完整URL的时候要输入端口号。正式对外提供服务的时候都会使用http协议默认规定的端口80,就无需额外输入端口号了。

那么不知道你自己的IP地址怎么办?每台电脑,在访问自己的时候可以使用127.0.0.1,这是操作系统规定的自己本机的IP地址。所以访问自己的程序可以用http://127.0.0.1:5000/这样的形式。

IP地址、端口的概念,理解不了可以先记住http://127.0.0.1:5000/这个URL地址,访问这个地址就访问了我们编写的网站。先不需要深究,你去记别人门牌号码的时候也是一样的,记住就好,不用问为啥是xxx号,对不对?

这是最简单的互联网程序,但万事开头难,你需要预先弄明白一些互联网的基础知识。真的编程,到了Python中,还是非常简单的。


模块化

再多补充一个Python的知识,上面程序中的第7行,是定义了一个Flask变量。其中使用了参数__name__,这是一个Python提供的系统变量,其内容就是当前Python程序“模块的名称”。模块名称是这样定义的:Python主程序在执行的过程中,__name____main__,如果是使用import引入的库,在库文件中的代码得到的__name__值,就是去除后缀之后的Python程序的名字。为了说明这一点,我们看一个例子,首先假设有一个程序code2.py,内容只有一行:

print("code2.py:",__name__)

#假设我们只运行code2.py,将显示:code2.py:__main__
#这是因为此时code2.py被当做主程序运行

我们另外写一个程序,比如叫code3.py,内容为:

import code2
print("code3.py:",__name__)

#执行后将显示:
# code2.py: code2
# code3.py: __main__
#这说明code3.py被当做主程序运行,code2是一个模块

这个功能对于编写逻辑复杂的Python程序非常有用。比如使用这个功能,我们优化上面的程序,使得程序既可以独立运行,也可以配合其它程序一起执行。
原因很简单,对于一个大的网站来说,肯定不可能一蹴而就,需要一点点的建立,那我们的程序肯定也需要有很多个,每个程序可能只处理某一个URL路径相关的工作。

#网络编程演示4
#作者:andrew

#引入网络库
from flask import Flask

#定义一个网络应用
app = Flask(__name__)

@app.route('/')
def index():
    return "你好,世界,我现在已经模块化了!"

#如果是主程序而不是模块,运行整个应用
if __name__ == '__main__':
    app.run()

这个程序只是增加了第15行,意思是,如果当前程序以主程序方式运行,就执行Flask框架。也就是自己就成为一个服务,并且只有一个主页面。
而如果没有以主程序的方式执行的时候,第15行条件为假,将不执行Flask框架。此时当然会有其它配合工作的主程序执行Flask框架,从而综合很多个Python程序一起工作,共同成为一个功能更多的网站。


网络程序基本结构

一个网站程序的基本结构通常是这样:

webpic1

我们的程序运行在中间标识为“后台”的部分,接受前端用户浏览器或者手机App的访问。我们的程序会做很多计算和逻辑处理的工作,如果碰到了需要大量数据支持的工作,通常需要访问更靠后部的数据库。
数据库不是我们课程的范围,我们这里略过不讲。在后面的程序中,我们会跟第十讲中的记事本程序一样,采用文本文件来模拟数据库的功能。
当然数据库仍然是非常重要的技术,建议有兴趣的同学另外选择相关课程学习。


从浏览器向服务器传递数据

前面的例子,我们已经可以响应一个页面的访问。这个过程是单向的,就是接受到请求之后,单方面把数据传回到网页。
在很多场景中,都需要由浏览器传递数据到网站,最常用的就是搜索,比如你在谷歌网站的搜索框中输入了要搜索的关键字,谷歌网站的程序,需要获取到你输入的关键字,完成搜索功能,然后才把结果反馈给你的浏览器。这个过程是双向的。我们下面演示如何获取用户输入的内容:

#网络编程演示5
#作者:andrew

#引入网络库
from flask import Flask

#定义一个网络应用
app = Flask(__name__)

@app.route('/<name>')
def index(name):
    return "你好,"+name+"!"

#如果是主程序而不是模块,运行整个应用
if __name__ == '__main__':
    app.run()

仍然只看第10至第12行的内容,这是我们自己写的。其它部分仍然是拷贝过来就好。
第10行在路径的定义中,使用了尖括号定义了一个资源名称name,这表示,如果有类似这样的访问:http://127.0.0.1:5000/xxxxxx,那么在斜线之后的所有内容,会被当做数据,传递给Python中的name变量。
接下来11行,你会发现自定义的函数也多了一个参数,也就是刚才说的name变量,这个名字,跟上面对URL路径进行匹配的时候指定的名字,要相同。
12行返回的字符串值,也包含了name的值,这只是想让你看到,程序确实收到了从浏览器URL中传递给Python的参数。执行后,假设我们访问http://127.0.0.1:5000/寓乐湾得到的结果会是这样:
webpic2


从服务器向浏览器传递数据的多种方式

上面的几个例子,我们都是直接返回了字符串到浏览器,简单的显示文字还是能做到的。但可能大多同学都知道,浏览器需要使用HTML语言编写网页,才能完成很多功能,图文并茂。这当然又需要大量的学习。

不过不用担心,现在的互联网编程,分工已经非常的精细。这部分的工作,将会由前端HTML程序员或者手机App程序员完成,我们只需要Python写好服务器端的程序就好。而有兴趣从事前端工作和艺术设计工作的,可以另外参加相关的课程,来学习前端如何配合Python一起工作。

如果需要传递很多数量、很多类型的数据,使用上面这种简单字符串的形式,显然不能满足应用。于是诞生了很多双方数据的封装格式的标准,比较常用的是JSON格式。
JSON格式起源于Javascript语言中对数据的包装方式,我们不用了解细节,只要知道如何使用就好。 下面我们看一个例子,来演示上面说到的这几种形式:

#引入json转换库
from flask import jsonify  
#定义一个列表,模拟数据库
data=["你好","世界","这是","Python"]

@app.route('/list1')
def list1():
    str = ""
    for s in data:
        str += s + "\n"  #传统命令行格式
    return str
@app.route('/list2')
def list2():
    str = ""
    for s in data:
        str += s + "<br>" #HTML格式
    return str
@app.route('/list3')
def list3():
    return jsonify(data) #JSON格式

#如果是主程序而不是模块,运行整个应用
if __name__ == '__main__':
    app.run()

上面的程序中,除了Flask框架模板之外,我们定义了一个列表:data,其中包含4个字符串元素。
接着我们定义了3个URL访问,如果访问/list1,我们使用字符串+换行符的方式返回列表内容。转义符\n表示换行字符,我们在第三讲讲过。使用for循环遍历列表,我们在第六讲学过。/list2路径跟/list1很类似,但使用了html语言中的<br>标签,表示在浏览器中换行。
/list3看上去最简单,使用了我们第一次见的函数jsonify,这是Flask库中定义的一个函数,功能是把参数转换为json格式的字符串,最终我们返回了这个json字符串给访问者。

我们分别访问三个URL,将得到三个不同结果,先列在下面: webstr1

webstr2

webstr3 在/list1的访问结果中,“\n”换行符可以在我们通常的命令行程序中完成换行,在浏览器中,没有效果。
/list2的访问中,正常的换行,这是因为html的标签执行正常。
/list3的访问结果看上去一团混乱,无法辨识,这是因为使用了不同的字符编码,不过这种无法辨识,并不影响前端程序的处理。所以如果有前端网页程序正常处理的话,就可以正常显示。这部分有点抽象,后面我们还会看到更清晰的例子。

这个程序中,我们演示了不同数据处理方式,在浏览器中不同的效果。我们最重要记住的就是一个函数jsonify。


挑战

上面所讲述的基本知识已经足够了,我们开始本讲的挑战:把第十章中的记事本程序,迁移到网络上,称为一个网页版的记事本。

通常一个网络程序开发的流程是这样:

webprg1 在这个流程中,需要后台程序开发的,主要是红字的部分。与后台开发直接相关联的是前端设计的网页和前端程序。没有前端程序的配合,我们只能使用json返回数据,但无法被用户识别使用。

现在假设前端程序员已经完成了一个网页,包括网页的内容、格式和其中的程序,这个网页叫做index.html。我们还有一个责任是当用户访问的时候,我们首先把这个网页反馈给浏览器。

此后的操作,用户实际都是跟网页打交道,比如阅读信息,比如输入内容,比如点击按钮。网页需要向后台查询的地方,会通过URL调用后端的程序,并接受后端返回的数据,并将数据显示给用户看。

#引入网络库
from flask import Flask
#引入json转换库
from flask import jsonify  
#引入记事本文件名定义
from common import *

......

#首页,直接返回一个静态的网页文件
@app.route('/')
def index():
    return app.send_static_file('index.html')

上面这段代码,我们省略了很多重复性的内容,其中第6行可能看起来眼熟,我们继续使用了第十章中定义的只有一行的一个小模块,其中包含了记事本文件的名称。
第11行定义了接受主页“/”的访问,当访问的时候,返回静态网页文件index.html给客户。使用的函数是app.send_static_file。这里的静态,指的是整个文件实际就是一个文本的html文件,中间不包含在服务器端运行的内容。
跟静态网页相对的有一个概念叫“动态网页”,动态网页不是指网页画面上有动画,而是指“网页动态生成”,也叫“服务器端渲染”。这是是早期网站开发中经常用到的技术,比如传统的PHP程序、JSP程序都属于这种。
网页会在服务器端首先运行,经过处理才返回给用户。现在已经越来越少这样使用了,因为会额外为服务器增加负载,也占用了更多的网络流量,所以我们只要知道有动态网页、服务器端渲染这回事就够。
新的编程方式正如我们前面所介绍的,采用前后端的程序分离,分别使用不同的技术完成。前后端之间使用JSON或同类技术进行极简的数据传递。因为这些新技术的出现和流行,所以虽然我们刚刚学习网络编程,但我们并不一定比一些工作很长时间的人理念上落后。
下面来看我们笔记本程序的例子:

#列出当前所有记录
@app.route('/list')
def listdaily():
    #所有读出的记录记录到列表变量中
    # (文件是每行一条记录,
    #  列表变量是每个元素一条记录)
    data=[]
    fd=open(filename,"r")
    for line in fd:
        data.append(line)
    fd.close()
    #把列表生成JSON格式返回到网页
    return jsonify(data)

这段程序读取记事本文件,将记事本所有的内容,以json的方式返回给浏览器。URL是/list,函数内容没什么好讲的,都已经学过,并且也算练习过多次。只是在程序的最后,调用前面介绍过的jsonify函数,把数据json化,返回给浏览器前端程序。
这个URL在index.html网页一打开的时候调用,从而将当前的记事本内容显示到网页上。
继续看下一段程序:

#添加一条记录
@app.route('/add/<msg>')
def adddaily(msg):
    #打开文件,写出一条信息
    fd=open(filename,"a")
    fd.write(msg)
    fd.write("\n")
    fd.close()
    return "OK"

上面的程序定义了增加新记事条目的功能。向服务器程序传递信息的功能本讲开始讲过了,这里完全相同的处理方式。再后面文件操作的内容跟第十讲中的功能没有区别。 myweb1 上图是我们新的记事本在网页中运行的样子,完全完成后,增加、查看、删除三个功能都集成在一个页面。打开网页就有了以前存在的记事列表。在上面的输入框中,可以输入新的内容,输入完成点击增加,就会增加到记事本条目中。选择列表中全面的单选框,然后单击删除按钮,可以删掉这一条记事。
这个小程序移植到网络版的好处,是在手机的浏览器中也可以很好的运行,不用必须是一台电脑。


练习时间

记事本程序一共三项功能,列出记事本内容、增加新的记事,还有删除记事内容。我们前面已经完成了程序的基本框架、列出内容和增加记事,删除记事的功能,我觉得可以作为练习自己来尝试。因为网页同后台程序之间的URL接口是提前定义好的,不能违背,所以我们把这个删除功能的定义部分给出写出来,你在这个基础上完成:

#删除一条记录
@app.route('/del/<id>')
def deldaily(id):





    #返回一个执行成功信息
    return "OK"

中间空白的部分,是你要接着写的部分。空行数量仅供示意,你不用受程序行数的限制。


本讲小结

  • 网络程序开发是互联网时代最重要的技能之一,使用Python开发网络程序非常简单
  • 虽然网络程序开发并不难,但互联网发展很快,出现了很多的概念需要有了解,比如网址、HTML、JSON、浏览器、前端、后端
  • 在新式的扩展库中,框架的概念很流行,指的是由“扩展库程序”接管程序的大部分功能,只在涉及具体工作的时候才调用由用户编写的具体功能,从而简化复杂的程序开发工作
  • Python的网络编程,就是用户编写对应到某具体网址的函数,当浏览器访问到指定网址是,函数被激活,完成某些工作,并返回结果到浏览器

练习答案

请参考myweb.py程序源码。


附件

程序中所使用的网页文件源码,文件名:static/index.html:

<html lang="zh-cn">
<head>
    <meta charset="utf-8">
    <title>网页记事本</title>
</head>
<body>
新笔记:<input id='newtext' type="text" width=100></input>
<input type="button" value='添加' onclick="addButtonClick();return 0;"></input>
<p>笔记列表:</p>
<div id=list></div>
<input type="button" value='删除' onclick="delButtonClick();return 0;"></input>
<script src=https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js></script>
<script>
    baseurl="";//"http://127.0.0.1:5000";
    function showList(block,result){
        block.empty();
        block.append("<table board=1 width=200>")
        $.each(result, function(i, field){
            block.append("<tr>")
            block.append("<td width='10%'><input name='index' type='radio' value='"+i+"'></input></td>");
            block.append("<td width='*'>"+field + "</td>");
            block.append("</tr>")
        });
        block.append("</table>")
    };
    function getData(url){
        $.getJSON(url,function(result){
            showList($("#list"),result);
        });
    }
    function delButtonClick(){
        //alert($("input[name='index']:checked").val());
        if($("input[name='index']:checked").val() == undefined)
            {
                alert("选一个要删除的笔记.");
                return;
            }
        $.getJSON(baseurl+"/del/"+$("input[name='index']:checked").val(),null);
        setTimeout(function(){getData(baseurl+"/list");},1000);
    }
    function addButtonClick(){
        $.getJSON(baseurl+"/add/"+$("#newtext").val(),null);
        setTimeout(function(){getData(baseurl+"/list");},1000);
        //$("#newtext").attr("value","");
        $("#newtext").val("");
    }
    $(document).ready(function(){
        getData(baseurl+"/list");
    });
</script>
</body>
</html>

注1:本课程并不是网页制作或者js语言课程,所以以上网页内容仅供实验使用,并非本课程重点。在实际工作中,网页的制作会由前端工程师完成。
注2: 其它各讲中所使用到的源码,会在全部连载完成后集中整理并提供打包下载。