Flask框架学习整理——从零开始入门Flask

Posted 时生丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flask框架学习整理——从零开始入门Flask相关的知识,希望对你有一定的参考价值。

文章目录

Flask框架

一、 简介

Flask是一个非常小的PythonWeb框架,被称为微型框架;只提供了一个稳健的核心,其他功能全部是通过扩展实现的;意思就是我们可以根据项目的需要量身定制,也意味着我们需要学习各种扩展库的使用。

二、 概要

1)安装: pip install flask
2)组成:WSGI系统、调试、路由
3)模板引擎:Jinja2(由Flask核心开发者人员开发)
4)使用到装饰器:以@开头的代码方法

三、 知识点(附代码)

1. Flask基础入门

1)路由route的创建:

route装饰器详解

  • 通过创建路由并关联函数,实现一个基本的网页:
from flask import Flask

# 用当前脚本名称实例化Flask对象,方便flask从该脚本文件中获取需要的内容
app = Flask(__name__)

#程序实例需要知道每个url请求所对应的运行代码是谁。
#所以程序中必须要创建一个url请求地址到python运行函数的一个映射。
#处理url和视图函数之间的关系的程序就是"路由",在Flask中,路由是通过@app.route装饰器(以@开头)来表示的
@app.route("/")
#url映射的函数,要传参则在上述route(路由)中添加参数申明
def index():
    return "Hello World!"

# 直属的第一个作为视图函数被绑定,第二个就是普通函数
# 路由与视图函数需要一一对应
# def not():
#     return "Not Hello World!"

# 启动一个本地开发服务器,激活该网页
app.run()
  • 通过路由的methods指定url允许的请求格式:
from flask import Flask

app = Flask(__name__)

#methods参数用于指定允许的请求格式
#常规输入url的访问就是get方法
@app.route("/hello",methods=['GET','POST'])
def hello():
    return "Hello World!"
#注意路由路径不要重名,映射的视图函数也不要重名
@app.route("/hi",methods=['POST'])
def hi():
    return "Hi World!"

app.run()
  • 通过路由在url内添加参数,其关联的函数可以接收该参数:
from flask import Flask

app = Flask(__name__)

# 可以在路径内以/<参数名>的形式指定参数,默认接收到的参数类型是string

'''#######################
以下为框架自带的转换器,可以置于参数前将接收的参数转化为对应类型
string 接受任何不包含斜杠的文本
int 接受正整数
float 接受正浮点数
path 接受包含斜杠的文本
########################'''

@app.route("/index/<int:id>",)
def index(id):
    if id == 1:
        return 'first'
    elif id == 2:
        return 'second'
    elif id == 3:
        return 'thrid'
    else:
        return 'hello world!'

if __name__=='__main__':
    app.run()
  • 除了原有的转换器,我们也可以自定义转换器(pip install werkzeug):
from werkzeug.routing import BaseConverter #导入转换器的基类,用于继承方法
from flask import Flask

app = Flask(__name__)

# 自定义转换器类
class RegexConverter(BaseConverter):
    def __init__(self,url_map,regex):
        # 重写父类定义方法
        super(RegexConverter,self).__init__(url_map)
        self.regex = regex

    def to_python(self, value):
        # 重写父类方法,后续功能已经实现好了
        print('to_python方法被调用')
        return value

# 将自定义的转换器类添加到flask应用中
# 具体过程是添加到Flask类下url_map属性(一个Map类的实例)包含的转换器字典属性中
app.url_map.converters['re'] = RegexConverter
# 此处re后括号内的匹配语句,被自动传给我们定义的转换器中的regex属性
# value值会与该语句匹配,匹配成功则传达给url映射的视图函数
@app.route("/index/<re('1\\d10'):value>")
def index(value):
    print(value)
    return "Hello World!"

if __name__=='__main__':
    app.run(debug=True)

2)endpoint的作用

  • 说明:每个app中都存在一个url_map,这个url_map中包含了url到endpoint的映射;
  • 作用:当request请求传来一个url的时候,会在url_map中先通过rule找到endpoint,然后再在view_functions中根据endpoint再找到对应的视图函数view_func
from flask import Flask

app = Flask(__name__)

# endpoint默认为视图函数的名称
@app.route('/test')
def test():
    return 'test success!'
# 我们也可以在路由中修改endpoint(当视图函数名称很长时适用)
# 相当于为视图函数起别名
@app.route('/hello',endpoint='our_set')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    print(app.view_functions)
    print(app.url_map)
    app.run()
  • 可以通过view_functions查看到当前endpoint视图函数的对应情况;
  • 可以通过url_map查看当前urlendpoint的绑定情况;
# view_functions
'static': <function Flask.__init__.<locals>.<lambda> at 0x00000230CC2A7DC0>, 'test': <function test at 0x00000230CC30FD30>, 'our_set': <function hello_world at 0x00000230CC30FDC0>
# url_map
Map([<Rule '/hello' (OPTIONS, HEAD, GET) -> our_set>,
 <Rule '/test' (OPTIONS, HEAD, GET) -> test>,
 <Rule '/static/<filename>' (OPTIONS, HEAD, GET) -> static>])
 * Serving Flask app 'endpoint_test' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
  • 值得注意的是,endpoint相当于给url起一个名字,view_functions内存储的就是url的名字到视图函数的映射,且endpoint在同一个蓝图下也不能重名:
from flask import Flask

app = Flask(__name__)

@app.route('/test',endpoint='Test')
def test():
    return 'None'
def Test():
    return 'World!'
    
if __name__ == '__main__':
    print(app.view_functions)
    print(app.url_map)
    app.run()
  • 通过view_functions可以看到,即使修改endpoint为其他视图函数名,依然是绑定其正下方的视图函数,说明endpoint作用于url:
'static': <function Flask.__init__.<locals>.<lambda> at 0x000002056C378CA0>, 'Test': <function test at 0x000002056C3E0C10>

3)request对象的使用

  • 什么是request对象
  • render_template():可以用于呈现一个我们编写的html文件模板
  • request.method用于获取url接收到的请求方式,以此返回不同的响应页面
#request:包含前端发送过来的所有请求数据

from flask import Flask,render_template,request

# 用当前脚本名称实例化Flask对象,方便flask从该脚本文件中获取需要的内容
app = Flask(__name__)

@app.route("/",methods=['GET','POST'])
#url映射的函数,要传参则在上述route(路由)中添加参数申明
def index():
    if request.method == 'GET':
        # 想要html文件被该函数访问到,首先要创建一个templates文件,将html文件放入其中
        # 该文件夹需要被标记为模板文件夹,且模板语言设置为jinja2
        return render_template('index.html')
    # 此处欲发送post请求,需要在对应html文件的form表单中设置method为post
    elif request.method == 'POST':
        name = request.form.get('name')
        password = request.form.get('password')
        return name+" "+password

if __name__=='__main__':
    app.run()

4)请求钩子before/after_request

想要在正常执行的代码的前、中、后时期,强行执行一段我们想要执行的功能代码,便要用到钩子函数——用特定装饰器装饰的函数


下面将介绍Flask内,四种常用的钩子:

  1. before_request:在每一次请求之前调用;
  • 该钩子函数表示每一次请求之前,可以执行某个特定功能的函数;
  • 执行顺序是先绑定的先执行
  • 并且先执行 flask app 的 before_request, 再执行 blueprint 的 before_request;
  • 一般用于检验用户权限、请求是否合法等场景;
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

@app.before_request
def before_request_a():
    print('I am in before_request_a')
 
@app.before_request
def before_request_b():
    print('I am in before_request_b')
 
 
if __name__ == '__main__':
    app.run()
 
# 打印结果 -=-=-=-=-=-=-=-=-=-=-=-=-=
I am in teardown_request_a
I am in teardown_request_b
  1. before_first_request:与before_request的区别是,只在第一次请求之前调用;
  • 该钩子函数表示第一次请求之前可以执行的函数;
  • 执行顺序同样也是先绑定的先执行
# 代码替换视图函数hello_world后,if main前
@app.before_first_request
def teardown_request_a():
    print('I am in teardown_request_a')

@app.before_first_request
def teardown_request_b():
    print('I am in teardown_request_b')

# 打印结果 -=-=-=-=-=-=-=-=-=-=-=-=-=
I am in teardown_request_a
I am in teardown_request_b
  1. after_request每一次请求之后都会调用;
  • 该钩子函数表示每一次请求之后,可以执行某个特定功能的函数,这个函数接收response对象,所以执行完后必须归还response对象
  • 执行的顺序是先绑定的后执行
  • 被触发的前提是没有异常抛出,或者异常被 errorhandler捕获并处理;
  • 一般可以用于产生csrf_token验证码等场景;
# 代码替换视图函数hello_world后,if main前
@app.after_request
def after_request_a(response):
    print('I am in after_request_a')
    # 该装饰器接收response参数,运行完必须归还response,不然程序报错
    return response
 
@app.after_request
def after_request_b(response):
    print('I am in after_request_b')
    return response
    
# 打印结果 -=-=-=-=-=-=-=-=-=-=-=-=-=
I am in teardown_request_b
I am in teardown_request_a
  1. teardown_request每一次请求之后都会调用;
  • 该钩子函数接收一个参数,该参数是服务器出现的错误信息;
  • 执行顺序也是先绑定的后执行
  • 只有在请求上下文被 pop 出请求栈的时候才会直接跳转到teardown_request;
  • 所以在被正常调用之前,即使某次请求有抛出错误,该请求也都会被继续执行, 并在执行完后返回 response;
# 代码替换视图函数hello_world后,if main前
@app.teardown_request
def teardown_request_a(exc):
    print('I am in teardown_request_a')
 
@app.teardown_request
def teardown_request_b(exc):
    print('I am in teardown_request_b')
 
# 打印结果 -=-=-=-=-=-=-=-=-=-=-=-=-=
I am in teardown_request_b
I am in teardown_request_a

5)redirect重定向

  • 什么是redirect重定向
  • 在flask 中,重定向是通过flask.redirect(location, code=302)这个函数来实现的,location表示需要重定向的url, 应该配合url_for函数来使用, code表示采用哪个重定向,默认是302,即临时性重定向, 可以修改为301来实现永性重定向;
from flask import Flask,redirect,url_for

app = Flask(__name__)

@app.route('/index')
def index():
    # redirect重定位(服务器向外部发起一个请求跳转)到一个url界面;
    # url_for给指定的函数构造 URL;
    # return redirect('/hello') 不建议这样做,将界面限死了
    return redirect(url_for('hello'))

@app.route('/hello')
def hello():
    return 'this is hello fun'

if __name__ == '__main__':
    app.run()

6)返回json数据给前端

  • 方法一:

使用:make_response方法和json库共同完成

from flask import Flask,make_response,json

app = Flask(__name__)

@app.route("/index")
def index():
   data = 
       'name':'张三'
   
   # json.dumps 将一个python数据结构转化为json
   # json.dumps 序列化时对中文默认使用的ascii编码.想输出真正的中文需要指定ensure_ascii=False
   # 生成一个response响应对象,而不是直接return来返回响应对象,便于执行更多的后续操作
   response = make_response(json.dumps(data,ensure_ascii=False))
   # 修改数据的MIME标准类型为json(在发送数据前会先发送该类型)
   response.mimetype = 'application/json'
   return response

if __name__=='__main__':
    app.run()
  • 方法二:

使用:jsonify库实现,减少代码行数

from flask import Flask,jsonify

app = Flask(__name__)
# 在Flask的config是一个存储了各项配置的字典
# 该操作是进行等效于ensure_ascii=False的配置
app.config['JSON_AS_ASCII'] = False

@app.route("/index")
def index():
   data = 
       'name':'张三'
   
   return jsonify(data)

if __name__=='__main__':
    app.run()
  • 运行结果:

7)abort函数的使用

  • 什么是abort()
  • 使用类似于python中的raise函数,可以在需要退出请求的地方抛出错误,并结束该请求;
  • 我们可以使用errorhandler()装饰器来进行异常的捕获与自定义:
from flask import Flask,render_template,request,abort

app = Flask(__name__)

@app.route("/",methods=['GET','POST'])
def index():
    if request.method == 'GET':
        return render_template('index.html')
    elif request.method == 'POST':
        name = request.form.get('name')
        password = request.form.get('password')
        if name == 'zhangsan' and password == '123456':
            return 'login sucess'
        else:
            # abort的用法类似于python中的raise,在网页中主动抛出错误
            abort(404)
            return None

# 自定义错误处理方法,将404这个error与Python函数绑定
# 当需要抛出404error时,将会访问下面的代码
@app.errorhandler(404)
def handle_404_error(err):
    # return "发生了错误,错误情况是:%s"%err
    # 自定义一个界面
    return render_template('404.html')

if __name__ == '__main__':
    app.run()
  • 自定义的404界面:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!-- 注意图片文件需要放在一个静态文件夹static里 -->
<img src="../static/error404.jpg" alt="" width="1428px" height="57px">
</body>
</html>
  • errorhandler()也可以传入异常类,用于捕获除flask异常列表内的其他异常:点我查看

8)url_for实现反转

  • url_for是实现url反转的工具,即视图函数 → \\rightarrow url
  • 静态文件引入:url_for('static', filename='文件路径')

静态文件需要存储在当前工程下的static目录内。

  • 定义路由:url_for('模块名.视图名',变量=参数)
  • 参数一中的视图名实质指的是endpoint,url_map中存储了url到endpoint的映射,只是默认情况下endpoint与视图函数名相同;
  • 如果在装饰器中修改了endpoint,则url_for只能使用endpoint设置的名字来反转url;
  • 在单模块程序下我们可以省略模块名,但当使用了蓝图(buleprint)后,参数一就必须使用"蓝图模块名.视图名",因为不同蓝图下的视图函数可以重名。

2. Flask高级视图

1)add_url_rule的初登场

  • 欲实现url与视图函数的绑定,除了使用路由装饰器@app.route,我们还可以通过add_url_rule(rule,endpoint=None,view_func=None)方法,其中:
  • rule:设置的url
  • endpoint:给url设置的名称
  • view_func:指定视图函数的名称
  • 因此,我们可以这样用:
def my_test():
    return '这是测试页面'
app.add_url_rule(rule='/test',endpoint='test',view_func=my_test)
  • 验证一下上述用法:
from flask import Flask,url_for

app = Flask(__name__)

@app.route('/',endpoint='index')
# 底层其实是使用add_url_rule实现的
def hello_world():
    return 'Hello World!'

def my_test():
    return '这是测试页面'
app.add_url_rule(rule='/test',endpoint='test',view_func=my_test)

# 请求上下文只有在发送request请求时才会被激活,激活后request对象被设置为全局可访问
# 其内部封装了客户端发出的请求数据报文
# 此处是主动生成一个临时的测试请求上下文
with app.test_request_context():
    print(url_for('test')) # 输出结果为/test

if __name__ == '__main__':
    app.run(debug=True)

2)类视图的引入

  • 之前我们所定义的视图都是通过函数来实现的,所以称之为视图函数,但其实视图还可以由类来实现,即类视图
  • 标准类视图
  • 定义时需要继承flask的views.View这一基类;
  • 每个类视图内必须包含一个dispatch_request方法,每当类视图接收到请求时都会执行该方法,返回值的设定和视图函数相同;
  • 视图函数可以通过@app.route和app.add_url_rule来进行注册(映射到url),但类视图只能通过app.add_url_rule来注册,注册时view_func不能直接使用类名,需要调用基类中的as_view方法来为自己取一个“视图函数名
  • 采用类视图的最大优势,就是可以把多个视图内相同的东西放在父类中,然后子类去继承父类;而类视图不方便的地方,就是每一个子类都要通过一个add_url_rule来进行注册。
  • 下面将创建一个网站包含三个页面,每个页面中都展示相同的对联广告,py文件如下:
from flask import Flask,render_template,views

app = Flask(__name__)

# 定义父视图类继承基类View
class Ads(views.View):
    def

从零开始搭建论坛:Web服务器与Web框架

之前用 Django 做过一个小的站点,感觉Django太过笨重,于是就准备换一个比较轻量级的 Web 框架来玩玩。Web.py 作者已经挂掉,项目好久没有更新,所以不准备用它。而 Flask 也是一个成熟的轻量级 Web 框架,在 github 上有众多的 Star 和 Fork,文档和扩展也很丰富,值得学习。

学习一个框架最好的方式就是用框架做一个项目,在实战中理解掌握框架。这里我用 Flask 框架,使用 Mysql 数据库做了一个论坛系统。麻雀虽小,五脏俱全,论坛效果图如下:

下面是论坛的基本功能:

  • 完整的用户模块(注册、登录,更改、找回密码、信息修改、站内消息通知);
  • 丰富的论坛模块(创建、回复话题,站内搜索,markdown支持,@user 提醒);
  • 强大的后台管理,支持屏蔽用户、话题、评论,支持各种条件搜索话题、评论;

本博客将会用一系列文章,记录论坛系统搭建的过程,希望对刚入门Web开发的同学有所帮助。

我们经常听说 Django, Flask 这些 python 语言的Web 框架,那么框架到底是什么,Web框架和Web服务器(Nginx, Apache等)有什么区别?离开框架还能用 Python 搭建Web站点吗?要解决这些疑问,我们有必要来理解下 Web 服务器的工作原理,以及 Web 框架的本质。

Web 服务器

当我们在浏览器输入URL后,浏览器会先请求DNS服务器,获得请求站点的 IP 地址。然后发送一个HTTP Request(请求)给拥有该 IP 的主机,接着就会接收到服务器给我们的 HTTP Response(响应),浏览器经过渲染后,以一种较好的效果呈现给我们。这个过程中,正是Web服务器在幕后默默做贡献。

简单来说,Web服务器是在运行在物理服务器上的一个程序,它永久地等待客户端(主要是浏览器,比如Chrome,Firefox等)发送请求。当收到请求之后,它会生成相应的响应并将其返回至客户端。Web服务器通过HTTP协议与客户端通信,因此也被称为HTTP服务器。


Web 服务器

Web服务器的工作原理并不复杂,一般可分成如下4个步骤:建立连接、请求过程、应答过程以及关闭连接

  1. 建立连接:客户机通过TCP/IP协议建立到服务器的TCP连接。
  2. 请求过程:客户端向服务器发送HTTP协议请求包,请求服务器里的资源文档。
  3. 应答过程:服务器向客户机发送HTTP协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理“动态内容”,并将处理得到的数据返回给客户端。由客户端解释HTML文档,在客户端屏幕上渲染图形结果。
  4. 关闭连接:客户机与服务器断开。

下面我们实现一个简单的 Web 服务器。运行示例程序后,会监听本地端口 8000,在浏览器访问 http://localhost:8000 就能看到响应内容。而我们的程序也能够打印出客户端发来的请求内容,如下图:


简单Web服务器

这里Request 和 Response 都需要遵守 HTTP 协议,关于 HTTP 协议的详细内容,可以读读《HTTP 权威指南》,或者看我整理的HTTP 部分内容

虽然说web服务器的主要工作是根据request返回response,但是实际中的 Web 服务器远远比上面示例的复杂的多,因为要考虑的因素实在是太多了,比如:

  • 缓存机制:讲一些经常被访问的页面缓存起来,提高响应速度;
  • 安全:防止黑客的各种攻击,比如 SYN Flood 攻击;
  • 并发处理:如何响应不同客户端同时发起的请求;
  • 日志:记录访问日至,方便做一些分析。

目前在UNIX和LINUX平台下使用最广泛的免费 Web 服务器有Apache和 Nginx 。

Web 应用程序

Web 服务器接受 Http Request,返回 Response,很多时候 Response 并不是静态文件,因此需要有一个应用程序根据 Request 生成相应的 Response。这里的应用程序主要用来处理相关业务逻辑,读取或者更新数据库,根据不同 Request 返回相应的 Response。注意这里并不是 Web 服务器本身来做这件事,它只负责 Http 协议层面和一些诸如并发处理,安全,日志等相关的事情。

应用程序可以用各种语言编写(Java, PHP, Python, Ruby等),这个应用程序会从Web服务器接收客户端的请求,处理完成后,再返回响应给Web服务器,最后由Web服务器返回给客户端。整个架构如下:


Web应用程序

以 Python 为例,使用Python开发Web,最原始和直接的办法是使用CGI标准,在1998年这种方式很流行。首先确保 Web 服务器支持CGI及已经配置了CGI的处理程序,然后设置好CGI目录,在目录里面添加相应的 python 文件,每一个 python 文件处理相应输入,生成一个 html 文件即可,如下例:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

print "Content-type:text/html"
print         # 空行,告诉服务器结束头部
print \'<html>\'
print \'<head>\'
print \'<meta charset="utf-8">\'
print \'</head>\'
print \'<body>\'
print \'<h2>Hello Word! 我是一个CGI程序</h2>\'
print \'</body>\'
print \'</html>\'

这样在浏览器访问该文件就可以得到一个简单的 Hello World 网页内容。直接通过 CGI 写 Web 应用程序看起来很简单,每一个文件处理输入,生成html。但是实际开发中,可能会遇到许多不方便的地方。比如:

  • 每个独立的CGI脚本可能会重复写数据库连接,关闭的代码;
  • 后端开发者会看到一堆 Content-Type 等和自己无关的 html 页面元素;

Web 框架

早期开发站点确做了许多重复性劳动,后来为了减少重复,避免写出庞杂,混乱的代码,人们将 Web 开发的关键性过程提取出来,开发出了各种 Web 框架。有了框架,就可以专注于编写清晰、易维护的代码,无需关心数据库连接之类的重复性工作。

其中一种比较经典的Web框架采用了 MVC 架构,如下图所示:


MVC 架构

用户输入 URL,客户端发送请求,控制器(Controller)首先会拿到请求,然后用模型(Models)从数据库取出所有需要的数据,进行必要的处理,将处理后的结果发送给 视图(View),视图利用获取到的数据,进行渲染生成 Html Response返回给客户端。

以 python web 框架 flask 为例,框架本身并不限定我们用哪种架构来组织我们的应用,不过 flask 可以很好地支持以 MVC 方式组织应用。

控制器:flask 可以用装饰器来添加路由项,如下:

@app.route(\'/\')
def main_page():
    pass

模型:主要用来取出需要的数据,如下面函数中操作:

@app.route(\'/\')
def main_page():
    """Searches the database for entries, then displays them."""
    db = get_db()
    cur = db.execute(\'select * from entries order by id desc\')
    entries = cur.fetchall()
    return render_template(\'index.html\', entries=entries)

视图:flask 利用 jinja2 来渲染页面,下面的模版文件指定了页面的样式:

{% for entry in entries %}
<li>
  <h2>{{ entry.title }}</h2>
  <div>{{ entry.text|safe }}</div>
</li>
{% else %}
<li><em>No entries yet. Add some!</em></li>
{% endfor %}

Web 服务器网关接口

我们知道Python有着许多的 Web 框架,而同时又有着许多的 Web 服务器(Apache, Nginx, Gunicorn等),框架和Web服务器之间需要进行通信,如果在设计时它们之间不可以相互匹配的,那么选择了一个框架就会限制对 Web 服务器的选择,这显然是不合理的。

那么,怎样确保可以在不修改Web服务器代码或网络框架代码的前提下,使用自己选择的服务器,并且匹配多个不同的网络框架呢?答案是接口,设计一套双方都遵守的接口就可以了。对python来说,就是WSGI(Web Server Gateway Interface,Web服务器网关接口)。其他编程语言也拥有类似的接口:例如Java的Servlet API和Ruby的Rack。

Python WSGI的出现,让开发者可以将 Web 框架与 Web 服务器的选择分隔开来,不再相互限制。现在,你可以真正地将不同的 Web 服务器与Web框架进行混合搭配,选择满足自己需求的组合。例如,可以使用 Gunicorn 或Nginx/uWSGI来运行Django、Flask或web.py应用。

以上是关于Flask框架学习整理——从零开始入门Flask的主要内容,如果未能解决你的问题,请参考以下文章

史上最全Mybatis框架入门教程,从零开始带你深入♂学习——mybatis入门

从零开始学Flask框架-第003天

从零开始学Flask框架-第001天

flask之ssti模版注入从零到入门

Flask快速入门,知识整理

送书 | 从零开始学习 PyTorch:多层全连接神经网络