01.初识FLASK框架

Flask是使用Python编写的Web微框架。Web框架可以不用关心底层的请求响应处理,更加方便、高效的编写Web程序。因为Flask框架简单且易于扩展,所以被称之为微框架。

Flask有两个主要依赖:

  1. WSGI(Web服务器 网关接口)工具集

    WSGI(Web Server Gateway Interface)是Python中用来规定Web服 务器如何与Python Web程序进行沟通的标准

  2. Jinjia2(模板引擎)

Flask只保留了Web开发的核心功能,其他的功能都是由外部扩展来实现的,比如数据库集成、表单验证、文件上传等。

如果没有合适的扩展,甚至还可以自己开发。

Flask不会替自己做决定,但绝不会限制开发。

以下所有的案例都是基于CentOS 7 下测试。

一、搭建开发环境

这里将会详细的介绍Flask的搭建方式。后续所有的示例均为CentOS 7 上演示。

1.Python环境搭建

详情请跳转,点击跳转

2.创建虚拟环境

在python中,虚拟环境就是隔离Python解释器环境,通过创建虚拟环境,可以拥有一个独立的Python解释器环境。这样做的好处就是可以为每一个项目创建独立的Python解释器环境,因为不同的项目常常会依赖于不同版本的第三方库或者Python版本。使用虚拟环境可以保持全局Python解释器环境的干净,避免包和版本的混乱,并且可以方便的区分和记录每个项目的依赖,以便在新的环境中复用现有的依赖环境。

mkvirtualenv py3env -p /usr/bin/python3

-p 参数,用于指定解释器的版本创建虚拟环境。例如指向Python2.7则创建的虚拟环境的Python版本为2.7

此命令用于创建虚拟环境,默认解释器路径为用户主目录/.virtualenvs

相关命令:

  • 列出当前所有的虚拟环境workon
  • 进入某一个虚拟环境workon py3env
  • 退出当前虚拟环境deactivate

3.安装Flask

使用上述所创建的虚拟解释器安装Flask

workon py3env
pip install flask

如果安装Python解释器为上述中的安装方式,此处安装Flask会非常快,因为已经更换了国内源

安装的过程中会发现,除了Flask包外,同时被安装的还有5个依赖包,如下:

名称与版本 说明
Jinjia2(3.0.1) 模板渲染引擎
MarkupSafe(2.0.1) HTML字符转义(escape)工具
Werkzeug(2..0.1) WSGI工具集,处理请求与响应,内置WSGI开发服务器、调试器、重载器
click(8.0.1) 命令行工具
itsdangerous(2.0.1) 提供各种加密签名功能

4.集成开发环境

Python编辑器有很多种,例如notepad++、Sublime Text等,甚至可以用记事本编辑。

这里推荐使用Jet家族的PyCharm集成开发环境。

PyCharm的具体操作如下:

①下载并安装PyCharm

点击跳转到下载页面

必须要清楚,PyCharm的专业版是收费的,而社区版的是免费的。这里选择试用的专业版

image-20210622232005811

安装过程比较比较简单,详细的操作步骤请参考

②创建项目

安装成功后,初始界面提供了多种方式创建新项目。这里可以单 击“Open”,选择一个任意的文件夹。打开项目后的界面如图所示,左边是项目目录树,右边是代码编辑区域。单击左下角的方形图标可以隐藏和显示工具栏,显示工具栏后,可以看到常用的Python交互控制台(Python Console)和终端(Terminal,即命令行工具)。

image-20210623014930755

③设置Python解释器

因为是远程解释器,所以这里需要配置SSH远程虚拟环境,具体步骤如下

  • 单击“File”、“Settings”、“Project”、“Python Interpreter”

image-20210623015156917

image-20210623015245174

  • 右上角锯齿状按钮点击“Add”添加解释器

    image-20210623015421720

  • 切换到“SSH Interpreter”,填入PythonServer的服务器信息,点击“Next”即可跳转到下一步

    image-20210623015537534

  • 输入服务器密码之后即可连接到服务器

    image-20210623015604825

  • 选择解释器路径

    image-20210623015705913

  • 确定好解释器之后,PyCharm会自动同步第三方库信息

    image-20210623015900267

二、Hello Flask

创建一个app.py文件

from flask import Flask

app = Flask(__name__)


@app.route("/")
def index():
    return "<h1>Hello Flask !!!</h1>"

这个py文件中包含一个最小的Flask程序。

对于简单的程序来说,程序的主模块一般命令为app.py。也可以使用其他名称,比如hello.py,但是要避免使用flask.py,这样回和本身的Flask程序相冲突。

1.创建程序实例

安装Flask时,会在Python解释器中创建一个flask包,可以通过flask包的构造文件导入所有开放的类和函数。先从flask包导 入Flask类,这个类表示一个Flask程序。实例化这个类,就得到程序实例APP

from flask import Flask

app = Flask(__name__)

传入Flask类构造方法的第一个参数是模块或包的名称,应该使用特殊变量__name__。Python会根据所处的模块来赋予__name__变量相 应的值,对于程序来说(app.py),这个值为app。除此之外,这也会帮助Flask在相应的文件夹里找到需要的资源,比如模板和静态文 件。

Flask类是Flask的核心类,它提供了很多与程序相关的属性和方法。在后面,经常会直接在程序实例app上调用这些属性和方法来实现相关功能。在第一次提及Flask类中的某个方法或属性时,会直接以实例方法或者属性的形式写出,比如存储程序名称的属性为app.name。

2.注册路由

在一个Web应用里,客户端和服务器上的Flask程序的交互可以简单概括为以下几步:

  1. 用户在浏览器输入URL访问某个资源。
  2. Flask接收用户请求并分析请求的URL。
  3. 为这个URL找到对应的处理函数。
  4. 执行函数并生成响应,返回给浏览器。
  5. 浏览器接收并解析响应,将信息显示在页面中。

在上面这些步骤中,大部分都由Flask完成,需要做的只是建立处理请求的函数,并为其定义对应的URL规则。只需为函数附加 app.route()装饰器,并传入URL规则作为参数,就可以让URL与函数建立关联。这个过程称为注册路由(route),路由负责管理 URL和函数之间的映射,而这个函数则被称为视图函数(view function)。

在这个程序里,app.route()装饰器把根地址/和index()函数绑定起来,当用户访问这个URL时就会触发index()函数。这个视图函数可以像其他普通函数一样执行任意操作,比如从数据库中获取信息, 获取请求信息,对用户输入的数据进行计算和处理等。最后,视图函数返回的值将作为响应的主体,一般来说,响应的主体就是呈现在浏览器 窗口的HTML页面。在最小程序中,视图函数index()返回一行问候:

@app.route("/")
def index():
    return "<h1>Hello World !!!</h1>"

虽然这个程序相当简单,但它却是大部分Flask程序的基本模式。在复杂的程序中,会有许多个视图函数分别处理不同URL的请求,在视图函数中会完成更多的工作,并且返回包含各种链接、表单、图片的 HTML文件,而不仅仅是一行字符串。返回的页面中的链接又会指向其他URL,被单击后触发对应的视图函数,获得不同的返回值,从而显示不同的页面,这就是浏览网页时的体验。

route()装饰器的第一个参数是URL规则,用字符串表示,必须以斜杠/开始。这里的URL是相对URL(又称为内部URL),即不包含域名的URL。以域名www.helloflask.com为例,“/”对应的是根地址 (即www.helloflask.com),如果把URL规则改为“/hello”,则实际的绝 对地址(外部地址)是www.helloflask.com/hello。 假如这个程序部署在域名为www.helloflask.com的服务器上,当启 动服务器后,只要在浏览器里访问www.helloflask.com,就会看到浏览器上显示一行“Hello,Flask!”问候。

①为视图绑定多个URL

@app.router("/hi")
@app.router("/hello")
def say_hello():
    return "Hello World!!!"

一个视图函数可以绑定多个URL,比如上边的代码中,把hi和hello都绑定到say_hello函数上,这就会为say_hello视图注册两个路由。

用户访问这两个url均会触发say_hello函数,获得相同的响应。

②动态URL

@app.route('/greet/<name>')
def greet(name):
	return '<h1>Hello, %s!</h1>' % name

FLask不仅可以为视图函数绑定多个URL,还可以在URL规则中添加变量部分,使用“<变量名>”的形式表示。

Flask处理请求时会把变量传入 视图函数,所以可以添加参数获取这个变量值。

当URL规则中包含变量时,如果用户访问的URL中没有添加变量, 比如/greet,那么Flask在匹配失败后会返回一个404错误响应。一个很常见的行为是在app.route()装饰器里使用defaults参数设置URL变量的默认值,这个参数接收字典作为输入,存储URL变量和默认值的映射。在 下面的代码中,greet视图新添加了一个app.route()装饰器, 为/greet设置了默认的name值:

@app.route('/greet', defaults={'name': 'ChanceySolo'})
@app.route('/greet/<name>')
def greet(name):
    return '<h1>Hello, %s!</h1>' % name

三、启动开发服务器

Flask内置了一个简单的开发服务器(由依赖包Werkzeug提供), 足够在开发和测试阶段使用。

1.命令行启动

Flask通过依赖包Click内置了一个CLI系统。在安装了Flask后,会自动添加一个flask命令脚本,通过flask命令执行内置命令、扩展提供的命令或者是自己定义的命令。

其中,flask run命令用来启动内置的开发服务器。

image-20210624203837466

注意:

  1. 确保执行命令前激活了虚拟服务器,即先执行命令workon py3env,再执行flask run

  2. 如果执行flask run命令后显示命令未找到提示(command not found)或者其他错误,可以尝试使用python -m fkask run来启动

flask run命令的开发服务器默认会监听http://127.0.0.1:5000/地址,并开启多线程支持。

打开浏览器访问该地址

image-20210624204338799

老版的启动开发服务器的方式是使用app.run()方法,目前已不推荐

  1. 自动发现程序实例

    一般来说,在执行flask run命令运行之前,需要提供程序实例所在模块的位置。以上可以直接运行,是因为FLask会自动探测程序实例,自动探测存在如下的规则:

    • 从当前目录寻找app.pywsgi.py模块,并从中寻找名为appapplication的程序实例
    • 从环境变量FLASK_APP对应的值寻找名为appapplication的程序实例
    • 第三条规则后边介绍:管理环境变量

    因为程序主模块命名为app.py,所以flask run命令会自动在 其中寻找程序实例。如果程序主模块是其他名称,比如hello.py, 那么需要设置环境变量FLASK_APP,将包含程序实例的模块名赋值给这个变量。Linux或macOS系统使用export命令:

    export FLASK_APP=hello
    

    在Windows系统中使用set命令:

    set FLASK_APP=hello
    
  2. 管理环境变量

    Flask的自动发现程序实例机制还有第三条规则:如果安装python-dotenv,那么在使用flask run或其他命令的时候会自动从.flaskenv文件和.env文件中加载环境变量

    当安装了python-dotenv时,Flask在加载环境变量的优先级是:

    手动设置的环境变量>.env中设置的环境变量>.flaskenv设置的环境变量

    除了FLASK_APP,在后面还会用到其他环境变量。

    环境变量在新创建命令行窗口或重启电脑后就清除了,每次都要重设变量有些麻烦。而且如果同时开发多个Flask程序,这个FLASK_APP就需要在不同的值之间切换。为了避免频繁设置环境变量,使用python-dotenv管理项目的环境变量,首先使用Pipenv将它安装到虚拟环境:

    pip install python-dotenv
    

    在项目根目录下分别创建两个文件:.env.flaskenv.flaskenv用来存储和Flask相关的公开环境变量,比如FLASK_APP;而.env用来存储包含敏感信息的环境变量,比如用来配置Email服务器的账户名与密码。在.flaskenv.env文件中,环境变量使用键值对的形式定义,每行一个,以#开头的为注释,如下所示:

    SOME_VAR=1
    #这是注释
    FOO="BAR"
    

2.使用PyCharm启动

在PyCharm中,虽然可以使用内置的命令行窗口执行命令以启动开发服务器,但是在开发时使用PyCharm内置的运行功能更加方便。 专业版添加了Flask命令行支持,在旧版本或社区版中,如果要使用PyCharm运行程序,还需要进行一些设置。

  1. 首先,在PyCharm中,单击菜单栏中的RunEdit Configurations打开运行配置窗口。

    image-20210624213339778

    image-20210624213824047

    • 步骤1 单击左侧的“+”符号打开下拉列表
    • 步骤2 新建一个Python类型的运行配置(如果是专业版,则可以直接选择Flask server),并在右侧的Name字段输入一个合适的名称,比如“Run hello”
    • 步骤3 勾选“Store as project file”
    • 步骤4 将第一项配置字段通过下列选项选为“Module Name”
    • 步骤5 填入模块名称flask
    • 步骤6 第二栏的“Parameters”填入要执行的命令run,可以附加其他启动选项
    • 步骤7 在“Working directory”字段中选择程序所在的目录作为工作目录

    更多的启动选项

    1. 使服务器外部可见

      上述的启动方式是外部设备访问不到的,可以在run命令后添加--host选项将主机地址设置为0.0.0.0使其对外可见。

      image-20210624214434647

      或者使用命令

      flask run --host="0.0.0.0"
      
    2. 改变默认的端口

      Flask提供的Web服务器默认监听5000端口,可以通过启动的命令或者编辑启动PyCharm来更改

      image-20210624214712724

      或者使用命令

      flask run --port=8088
      

      执行flask run命令时的host和port选项也可以通过环境变量 FLASK_RUN_HOSTFLASK_RUN_PORT设置。

      Flask内置的命令都可以使用这种模式定义默认选项值, 即“FLASK__”,使用flask --help命令查看所有可用的命令。

  2. 设置运行环境

    开发环境(development enviroment)和生产环境(production enviroment)会频繁接触到。

    开发环境是指在本地编写和测试程序时的计算机环境,而生产环境与开发环境相对,它指的是网站部署上线供用户访问时的服务器环境。

    根据运行环境的不同,Flask程序、扩展以及其他程序会改变相应的行为和设置。为了区分程序运行环境,Flask提供了一个FLASK_ENV环境变量用来设置环境,默认为production(生产)。

    在开发时,将其设为development(开发),这会开启所有支持开发的特性。为了方便管理,把环境变量FLASK_ENV的值写入.flaskenv文件中:

    FLASK_ENV=development
    

    image-20210624220252748

    在开发环境下,调试模式将被打开,这是执行flask run启动程序会自动激活Werkzeug内置的调试器和重载器。

    如果需要单独控制调试开关,可以通过FLASK_DEBUG环境变量设置,设置为1则开启,设置为0则关闭。不过不推荐此项来设置调试模式

    1. 调试器

      Werkzeug提供的调试器非常强大,当程序出错时,可以在网页上看到详细的错误追踪信息,这在调试错误时非常有用。调试器允许在错误页面上执行Python代码。单击错误信息右侧的命令行图标,会弹出窗口要求输入PIN码,也就是在启动服务器时命令行窗口打印出的调试器PIN码(Debugger PIN)。输入PIN码后,可以单击错误堆栈的某个节点右侧的命令行界面图标,这会打开一个包含代码执行上下文信息的Python Shell,可以利用它来进行调试。

      image-20210624221320924

      image-20210624221348449

      image-20210624221422588

    2. 重载器

      在修改代码之后,期望的行为是这些改动立刻作用到程序上。重载器的作用就是监测文件变动,然后重新启动开发服务器。修改了脚本内容并保存后,会在命令行看到重新启动的服务器。

      默认会使用Werkzeug内置的stat重载器,它的缺点是耗电较严重, 而且准确性一般。为了获得更优秀的体验,可以安装另一个用于监测文件变动的Python库Watchdog,安装后Werkzeug会自动使用它来监测文件变动:

      pip install watchdog --dev
      #因为这个包只在开发时才会用到,所以在安装命令后添加了一个--dev选项,这用来把这个包声明为开发依赖。在Pipfile文件中,这个包会被添加到dev-packages部分。不过,如果项目中使用了单独的CSS或JavaScript文件时,那么浏览器可能会缓存这些文件,从而导致对文件做出的修改不能立刻生效。在浏览器中,可以按下Crtl+F5或Shift+F5执行硬重载(hardreload),即忽略缓存并重载(刷新)页面。
      

四、PythonShell

在开发过程中,有许多的命令需要在Flask Shell中执行。

image-20210624223502315

执行这个命令之前,需要确保程序实例可以被正常找到。

Python Shell可以执行exit()quit()退出,在Windows系统上可 以使用Crtl+Z并按Enter退出;在Linux和macOS则可以使用Crtl+D退出。 使用flask shell命令打开的Python Shell自动包含程序上下文,并且已经导入了app实例:

image-20210624223756012

上下文(context)可以理解为环境。为了正常运行程序,一些操作相关的状态和数据需要被临时保存下来,这些状态和数据被统称为上下文。在Flask中,上下文有两种,分别为程序上下文和请求上下文。

五、Flask扩展

扩展(extension)即使用 Flask提供的API接口编写的Python库,可以为Flask程序添加各种各样的功能。大部分Flask扩展用来集成其他库,作为Flask和其他库之间的薄薄一层胶水。因为Flask扩展的编写有一些约定,所以初始化的过程大致相似。大部分扩展都会提供一个扩展类,实例化这个类,并传入创建的程序实例app作为参数,即可完成初始化过程。通常,扩展会在传入的程序实例上注册一些处理函数,并加载一些配置。

以某扩展实现了Foo功能为例,这个扩展的名称将是Flask-Foo或 Foo-Flask;程序包或模块的命名使用小写加下划线,即flask_foo(即导入时的名称);用于初始化的类一般为Foo,实例化的类实例一般使用 小写,即foo。初始化这个假想中的Flask-Foo扩展的示例如下所示:

from flask import Flask
from flask_foo import Foo

app = Flask(__name__)
foo = Foo(app)

在日常开发中,大多数情况下,没有必要重复制造轮子,所以选用扩展可以避免让项目变得臃肿和复杂。尽管使用扩展可以简化操作,快速集成某个功能,但同时也会降低灵活性。如果过度使用扩展, 在不需要的地方引入,那么相应也会导致代码不容易维护。更糟糕的是,质量差的扩展可能还会带来潜在的Bug,而不同扩展之间也可能会出现冲突。因此,在编写程序时,应该尽量从实际需求出发,只在需要的时候使用扩展,并把扩展的质量和兼容性作为考虑因素,尽量在效率和灵活性之间达到平衡。

早期版本的Flask扩展使用flaskext.foo或flask.ext.something的形式导 入,在实际使用中带来了许多问题,因此Flask官方推荐以 flask_something形式导入扩展。在1.0版本以后的Flask中,旧的扩展导入方式已被移除。

六、项目配置

在很多情况下,需要设置程序的某些行为,这时就需要使用配置变量。

在Flask中,配置变量就是一些大写形式的Python变量,也可称之为配置参数或配置键。使用统一的配置变量可以避免在程序中以硬编码(hard coded)的形式设置程序。

在一个项目中,会用到许多配置:

  • Flask提供的配置
  • 扩展提供的配置
  • 程序特定的配置

和平时使用变量不同,这些配置变量都通过Flask对象的app.config属性作为统一的接口来设置和获取,它指向的 Config类实际上是字典的子类,所以可以像操作其他字典一样操作它。

Flask提供了很多种方式来加载配置。比如,像在字典中添加一个键值对一样来设置一个配置:

app.config["ADMIN_NAME"] = "ChanceySolo"

配置的名称必须是全大写形式,小写的变量将不会被读取。

使用update()可以一次性加载多个值

app.config.update(
	TESTING = True,
    SECRET_KEY = "ChacneySolo1314"
)

除此之外,还可以把配置变量存储在单独的Python脚本、JSON 格式的文件或是Python类中,config对象提供了相应的方法来导入配置。和操作字典一样,读取一个配置就是从config字典里通过将配置变量的名称作为键读取对应的值:

value = app.config["ADMIN_NAME"]

某些扩展需要读取配置值来完成初始化操作,比如Flask-Mail,因此尽量将加载配置的操作提前,最好在程序实例app创建后就加载配置。

七、URL与端点

在Web程序中,URL无处不在。如果程序中的URL都是以硬编码的方式写出,那么将会大大降低代码的易用性。比如,修改了某个路由的URL规则,那么程序里对应的URL都要一个一个进行修改。更好的解决办法是使用Flask提供的url_for()函数获取URL,当路由中定义的URL规则被修改时,这个函数总会返回正确的URL。

调用url_for()函数时,第一个参数为端点(endpoint)值。在 Flask中,端点用来标记一个视图函数以及对应的URL规则。端点的默认值为视图函数的名称。

@app.router("/")
def idnex():
    return "Hello World"

这个路由的端点即视图函数的名称index,调用url_for("index")即可获取对应的URL,即“/”。

app.route()装饰器中使用endpoint参数可以自定义端点值,不过通常不需要这样做。 如果URL含有动态部分,那么需要在url_for()函数里传入相应的参数,以下面的视图函数为例:

@app.route('/hello/<name>')
def greet(name):
	return 'Hello %s!' % name

这时使用url_for("say_hello", name="ChanceySolo")得到的URL为/hello/ChanceySolo

使用url_for()函数生成的URL是相对URL(即内部URL), 即URL中的path部分,比如“/hello”,不包含根URL。相对URL只能在程序内部使用。如果要生成供外部使用的绝对URL,可以在使用 url_for()函数时,将_external参数设为True,这会生成完整的URL, 比如http://helloflask.com/hello,在本地运行程序时则会获得 http://localhost:5000/hello。

八、Flask命令

除了Flask内置的flask run等命令,还可以自己定义命令。在虚拟环境中安装了Flask之后,包含许多内置命令可以使用。

通过创建任意一个函数,并为其添加app.cli.command()装饰器,就可以注册一个Flask命令。

@app.cli.command("say_hello")
def hello():
    click.echo("Hello Flask!!!")

image-20210625172839054

image-20210625172744483

函数的名称即为命令名称,这里注册的命令即hello,使用 flask hello命令来触发函数。作为替代,也可以在app.cli.command() 装饰器中传入参数来设置命令名称,比如app.cli.command('say-hello')会把命令名称设置为say-hello,完整的命令即flask say-hello

九、模板与静态文件

一个完整的网站当然不能只返回用户一句“Hello,World!”,这时就需要模板(template)和静态文件(static file)来生成更加丰富的网页。

模板即包含程序页面的HTML文件,静态文件则是需要在HTML文件中加载的CSS和JavaScript文件,以及图片、字体文件等资源文件。

默认情况下,模板文件存放在项目根目录中的templates文件夹中,静态文件存放在static文件夹下,这两个文件夹需要和包含程序实例的模块处于同一个目录下,对应的项目结构示例如下所示:

HelloFlask/
	- templates/
	- static/
	- app.py

在开发Flask程序时,使用CSS框架和JavaScript库是很常见的需求, 而且有很多扩展都提供了对CSS框架和JavaScript库的集成功能。使用这些扩展时都需要加载对应的CSS和JavaScript文件,通常这些扩展都会提供一些可以在HTML模板中使用的加载方法或者函数,使用这些方法即可渲染出对应的link标签和script标签。这些方法一般会直接从CDN加载资源,有些提供了手动传入资源URL的功能,有些提供了内置的本地资源。

在开发环境下建议使用本地资源,这样可以提高加载速度。最好自己下载到static目录下,统一管理,出于方便的考虑也可以使用扩展内置的本地资源。在过渡到生产环境时,自己手动管理所有本地资源或自己设置CDN,避免使用扩展内置的资源。这个建议主要基于下面这些考虑因素:

  • 鉴于国内的网络状况,扩展默认使用的国外CDN可能会无法访问,或访问过慢。
  • 不同扩展内置的加载方法可能会加载重复的依赖资源,比如 jQuery。
  • 在生产环境下,将静态文件集中在一起更方便管理。
  • 扩展内置的资源可能会出现版本过旧的情况。

CDN指分布式服务器系统。服务商把需要的资源存储在分布于不同地理位置的多个服务器,它会根据用户的地理位置来就近分配服务器 提供服务(服务器越近,资源传送就越快)。

使用CDN服务可以加快网 页资源的加载速度,从而优化用户体验。

对于开源的CSS和JavaScript 库,CDN提供商通常会免费提供服务。

十、Flask与MVC架构

MVC架构最初是用来设计桌面程序的,后来也被用于Web程序,应用了这种架构的Web框架有Django、Ruby on Rails等。在MVC架构中, 程序被分为三个组件:数据处理(Model)、用户界面(View)、交互逻辑(Controller)。如果套用MVC架构的内容,那么Flask中视图函数的名称其实并不严谨,使用控制器函数(Controller Function)似乎更合适些,虽然它也附带处理用户界面。严格来说,Flask并不是MVC架构 的框架,因为它没有内置数据模型支持。

粗略归类,如果想要使用Flask来编写一个MVC架构的程序,那么 视图函数可以作为控制器(Controller),视图(View)就是使用Jinja2渲染的HTML模板,而模型(Model)可以使用其他库来实现。

posted @ 2023-07-21 00:01  ChanceySolo  阅读(17)  评论(0编辑  收藏  举报