flask的基本使用
顺着视频学习
视频地址:https://ke.qq.com/course/228864#term_id=100270059
开启debug模式
为什么开启debug模式
原因1:看下面这段代码,很明显除数不能为0,会抛出异常。
@app.route('/') def hello_world(): a = 1 b = 0 c = a / b return '你好,世界'
如果不开启debug,则在网页中显示:(表示是程序内部出错)
Internal Server Error
The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.
网页中不显示是哪里出错,但在代码终端可以看到,但这样很不直观。虽然终端可以直接跳到错误行。
原因2:修改python代码后直接Ctrl + S保存后,对应的页面也会更改,不用重启服务器,更快更方便。
开启debug的方法
方法一
app.run(debug=True) # 或者 app.debug = True app.run()
方法二:配置参数
新建一个config.py,然后写DEBUG = True。然后在app.py导入import config并且app.config.from_object(config)
PS:如果开启不了,请检查editconfigurations中是否开启了flask debug,另外最好single instance
url传参
# 路由里的参数用<>括起来!
@app.route('/article/<id>')
def article(id):
return '您请求的参数是:' + id
- 作用:可以在相同的url,但是指定不同的参数,来加载不同的数据
- 注意:
- 路由里的参数用<>括起来!
- 视图函数中的参数需要和路由url里的参数名称相同。
url反转
导入:from flask import Flask, url_for
@app.route('/')
def hello_world():
print(url_for('article', id='1'))
print(url_for('my_list'))
return '你好,世界'
# 路由里的参数用<>括起来!
@app.route('/article/<id>')
def article(id):
return '您请求的参数是:' + id
@app.route('/list/')
def my_list():
return 'list'
- 正转:由url得到视图函数
- 反转:由视图函数得到url
- 使用到的地方:页面重定向,模板中
重定向redirect
导入:from flask import Flask, url_for, redirect
@app.route('/')
def hello_world():
print(url_for('article', id='1'))
print(url_for('my_list'))
return redirect('/list/')
return '你好,世界'
# 路由里的参数用<>括起来!
@app.route('/article/<id>')
def article(id):
return '您请求的参数是:' + id
@app.route('/list/')
def my_list():
return 'list'
# 更优雅的做法如下:
list_url = url_for('my_list')
return redirect(list_url)
# 这样参数不管怎么变,只要视图函数名不变就可以了
模板
导入:from flask import Flask, render_template
基本使用:
在模板中新建index.html文件
在视图函数中返回
@app.route('/') def index(): return render_template('index.html')
模板传参:
@app.route('/') def index(): return render_template('index.html', username='User1', gender='男')
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>主页</title> </head> <body> 我是主页 <p>用户:{{ username }}</p> <p>性别:{{ gender }}</p> </body> </html>
注意:模板中参数用{{ 参数名 }}包括起来,参数名必须和视图函数中传递的参数名一致
数据结构传参:
字典:
@app.route('/') def index(): context = { 'username': 'User1', 'gender': '男' } return render_template('index.html', context=context)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>主页</title> </head> <body> 我是主页 <p>用户:{{ context['username'] }}</p> <p>性别:{{ context['gender'] }}</p> </body> </html>
或者将字典打散
@app.route('/') def index(): context = { 'username': 'User1', 'gender': '男' } return render_template('index.html', **context)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>主页</title> </head> <body> 我是主页 <p>用户:{{ username }}</p> <p>性别:{{ gender }}</p> </body> </html>
PS:注意字典打散加**号
实例
@app.route('/') def index(): class Person: name = 'Jay' age = '20' p = Person() context = { 'username': 'User1', 'gender': '男', 'person': p } return render_template('index.html', **context)
<body> 我是主页 <p>用户:{{ username }}</p> <p>性别:{{ gender }}</p> <p>姓名:{{ person.name }}</p> <p>年龄:{{ person.age }}</p> </body>
字典打散后的字典
@app.route('/') def index(): class Person: name = 'Jay' age = '20' p = Person() context = { 'username': 'User1', 'gender': '男', 'person': p, 'website': { 'baidu': 'www.baidu.com', 'google': 'www.google.com' } } return render_template('index.html', **context)
<body> 我是主页 <p>用户:{{ username }}</p> <p>性别:{{ gender }}</p> <hr/> <p>姓名:{{ person.name }}</p> <p>年龄:{{ person.age }}</p> <hr/> <p>百度:{{ website['baidu'] }}</p> <p>谷歌:{{ website['google'] }}</p> </body>
PS:访问字典可以通过params.key的形式或者params[‘key’],推荐使用后者,和字典样式一致
if语句
@app.route('/<is_login>/') def index(is_login): if is_login == '1': user = { 'username': 'Jay', 'age': 8 } return render_template('index.html', user=user) else: return render_template('index.html')
<body> {% if user %} <a href="#">{{ user['username'] }}</a> <a href="#">注销</a> {% else %} <a href="#">登录</a> <a href="#">注册</a> {% endif %} </body>
PS:注意,return render_template(‘index.html’, user=user)传参给模板不能直接打散,否则得不到该变量
for语句
字典的遍历
@app.route('/') def index(): user = { 'username': 'Jay', 'age': 20 } for k, v in user.items(): print(k) print(v) return render_template('index.html', user=user)
<body> {% for k, v in user.items() %} <p>{{ k }}:{{ v }}</p> {% endfor %} </body>
列表的遍历
@app.route('/') def index(): context = { 'user': { 'username': 'Jay', 'age': 20, }, 'website': [ 'www.baidu.com', 'www.google.com' ] } for website in context['website']: print(website) return render_template('index.html', **context)
<body> {% for k, v in user.items() %} <p>{{ k }}:{{ v }}</p> {% endfor %} <hr> {% for website in website %} <p>{{ website }}</p> {% endfor %} </body>
过滤器
default过滤器:
def index(): context = { 'user': { 'username': 'Jay', 'age': 20, }, 'website': [ 'www.baidu.com', 'www.google.com', 'www.souhu.com' ], 'avatar': 'https://avatar.csdn.net/B/A/9/3_towtotow.jpg' } for website in context['website']: print(website) return render_template('index.html', **context)
<body> {% for k, v in user.items() %} <p>{{ k }}:{{ v }}</p> {% endfor %} <hr> {% for website in website %} <p>{{ website }}</p> {% endfor %} <hr> <img src="{{ avatar|default('https://avatar.csdn.net/D/B/C/3_core___java.jpg') }}"> </body>
PS:default过滤器只是其中一个过滤器而已,作用是后台传的变量不存在,就用default展示。但是如果变量存在,值为空,那么过滤器不会用默认值来展示,这样展示的是后台传过来的空值
length过滤器:求字符串、列表、元祖、字典的长度
@app.route('/') def index(): website = [ 'www.baidu.com', 'www.google.com', 'www.souhu.com' ] return render_template('index.html', website=website)
<body> 网站数:{{ website|length }} </body>
PS:如果后台没传数据,则过滤为0,传了就是数据的长度,这个不用担心数据为空,为空就是0。
继承和block
首先创建一个base.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .nav { background: #3a3a3a; height: 65px; } ul { overflow: hidden; } ul li { float: left; list-style: none; padding: 0 10px; line-height: 65px; } ul li a { color: #fff; } </style> </head> <body> <div class="nav"> <ul> <li> <a href="#">首页</a> </li> <li> <a href="#">发布评论</a> </li> </ul> </div> {% block main %}{% endblock %} </body> </html>
- 定义了一个导航条,所有子页面均可以继承使用
- 通过{% block main %}{% endblock %} block语句,提供给子页面main块的接口来实现自己的内容
python
@app.route('/') def index(): return render_template('index.html') @app.route('/login/') def login(): return render_template('login.html')
index.html
<body> {% extends 'base.html' %} {% block main %} <h1>这是主页面</h1> {% endblock %} </body>
login.html
<body> {% extends 'base.html' %} {% block main %} <h1>这是登录页面</h1> {% endblock %} </body>
url_for链接和静态文件使用,重构上述代码:
在static下新建 css 文件夹,在css文件夹下新建base.css文件,将base.html的head中设置的style复制到base.css中去,让base.html的head标签中引入base.css属性。
base.css
.nav { background: #3a3a3a; height: 65px; } ul { overflow: hidden; } ul li { float: left; list-style: none; padding: 0 10px; line-height: 65px; } ul li a { color: #fff; }
base.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="{{ url_for('static', filename='css/base.css') }}"/> </head> <body> <div class="nav"> <ul> <li> <a href="{{ url_for('index') }}">首页</a> </li> <li> <a href="{{ url_for('login') }}">登录</a> </li> </ul> </div> {% block main %}{% endblock %} </body> </html>
python代码和index.html以及login.html不变
PS:加载图片,请求项目URI都可以用url_for来加载
数据库
python连接数据库的安装与配置
- 下载安装mysql
- 下载mysql_python驱动(直接百度),Windows系统下不能直接pip安装,下载后点击安装包安装,貌似不起作用
- 在虚拟环境中安装sqlalchemy
- 代码中配置mysql数据库,然后报错:ModuleNotFoundError: No module named ‘MySQLdb’
- 解决办法在虚拟环境中安装:pip install mysqlclient,然后一切OK
代码中连接mysql
配置文件
# 配置参数 DEBUG = True DIALECT = 'mysql' DRIVER = 'mysqldb' USERNAME = 'root' PASSWORD = '1234' HOST = '127.0.0.1' PORT = '3306' DATABASE = 'test' # SQLALCHEMY标志URI SQLALCHEMY_DATABASE_URI = "{}+{}://{}:{}@{}:{}/{}?charset=utf8".format(DIALECT, DRIVER, USERNAME, PASSWORD, HOST, PORT, DATABASE) # 忽略警告 SQLALCHEMY_TRACK_MODIFICATIONS = False
主代码:
from flask import Flask from flask_sqlalchemy import SQLAlchemy import config app = Flask(__name__) app.config.from_object(config) db = SQLAlchemy(app) class Student(db.Model): __tablename__ = 'student' number = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.String(100), nullable=False) db.create_all() @app.route('/') def index(): return 'index' if __name__ == '__main__': app.run()
注意:
- 模型类需要继承自db.Model,这个db变量时SQLAlchemy的实例。然后类的每一个成员属性都需要和表一一对应来映射,必须初始化成db.Column的类型
- 数据类型:
- db.Integer表示整型
- db.String(length)表示varchar类型
- db.Text表示text类型
- 其他参数
- ’primary_key’:表示主键
- ‘autoincrement’:表示整型自增
- ‘nullable’:设置True或False表示是否为空
增:
# 创建一个映射实例 student1 = Student(name='Jay') # 增加 db.session.add(student1) # 提交 db.session.commit()
查:
返回数组:
# 按条件返回一个数组 students = Student.query.filter(Student.name == 'Jay').all() # 取到数组的第一个元素 student = students[0] #复杂查询 # 查询或 search_questions = Question.query.filter( or_(Question.title.contains(q), Question.content.contains(q))).order_by( '-create_time').all() # 查询与 Question.query.filter(Question.title.contains(q), Question.content.contains(q))
返回查到的第一个元素:
# 只查找匹配的第一条 student = Student.query.filter(Student.name == 'Jay').first()
改
# 修改 # 1.先把要修改的数据查出来 student = Student.query.filter(Student.name == 'Jay').first() # 2.修改 student.name = 'new name' # 3.提交 db.session.commit()
删
# 删除 # 1.把需要删除的数据查出来 student = Student.query.filter(Student.name == 'Jay').first() # 2.删除 db.session.delete(student) # 3.提交 db.session.commit()
高级用法之外键,反向引用
from datetime import datetime from exts import db class User(db.Model): __table_name__ = 'user' id = db.Column(db.Integer, primary_key=True, autoincrement=True) phone = db.Column(db.String(11), nullable=False) username = db.Column(db.String(50), nullable=False) password = db.Column(db.String(100), nullable=False) class Question(db.Model): __table_name__ = 'question' id = db.Column(db.Integer, primary_key=True, autoincrement=True) title = db.Column(db.String(100), nullable=False) content = db.Column(db.Text, nullable=False) # now()表示服务器第一次运行的时间 # now表示当前时间 create_time = db.Column(db.DateTime, default=datetime.now) # 外键为user表中的id字段 author_id = db.Column(db.Integer, db.ForeignKey('user.id')) # 按外键反转查找author信息,关系到User模型。然后反向引用得到该author的所有questions author = db.relationship('User', backref=db.backref('questions'))
一对多关系,引入中间表
from datetime import datetime from werkzeug.security import generate_password_hash, check_password_hash from exts import db # 用户 class User(db.Model): __table_name__ = 'user' id = db.Column(db.Integer, primary_key=True, autoincrement=True) username = db.Column(db.String(50), nullable=False) email = db.Column(db.String(50)) # 文章tag的中间表 article_tag_table = db.Table('article_tag', db.Column('article_id', db.Integer, db.ForeignKey('article.id'), primary_key=True), db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'), primary_key=True)) # 文章 class Article(db.Model): __table_name__ = 'article' id = db.Column(db.Integer, primary_key=True, autoincrement=True) title = db.Column(db.String(100)) content = db.Column(db.Text) author_id = db.Column(db.Integer, db.ForeignKey('user.id')) author = db.relationship('User', backref='articles') tags = db.relationship('Tag', secondary=article_tag_table, backref='tags') # 文章标签 class Tag(db.Model): __table_name__ = 'tag' id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.String(50)) article_id = db.Column(db.Integer, db.ForeignKey('article.id'))
@app.route('/') def hello_world(): user = User(username='lzj', email='123@qq.com') article = Article(title='title123', content='content123') article.author = user tag1 = Tag(name='前端') tag2 = Tag(name='python') article.tags.append(tag1) article.tags.append(tag2) # 这里数据库操作增加一个article,那么与该article相关的表会自动映射更新 db.session.add(article) db.session.commit() return 'Hello World!'
flask-script
在虚拟环境中安装:pip install flask-script
用法:请看视频
分开modules解决循环引用
新建一个exts.py文件
from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy()
新建model.py文件
from exts import db class Student(db.Model): __tablename__ = 'student' number = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.String(100), nullable=False)
app.py引用如下
from flask import Flask from exts import db from models import Student import config app = Flask(__name__) app.config.from_object(config) # 数据库迁移后,不要忘记初始化 db.init_app(app) # 否则只能在视图函数中执行,不能在全局执行 with app.app_context(): db.create_all() @app.route('/') def index(): return 'index' if __name__ == '__main__': app.run()
flask-migrate
在虚拟环境中安装:pip install flask-migrate
作用:数据库的迁移,假如对数据库的字段属性进行了更改,数据库是不会映射的。比如增加一个字段,这时可以直接将表drop掉,但是用户的数据就没了,显然很不好。于是可以采用migrate。
用法:
- 写好manage.py后,进入虚拟环境并activate,然后在虚拟环境中用cd..回退,进入到项目目录中命令:
python manage.py db init
初始化migrate
- 然后命令:
python manage.py db migrate
生成一个迁移文件
- 然后实时迁移更新数据库
python manage.py db upgrade
数据库迁移后不要忘记在app.py中初始化:db.init_app(app)
session
flask中session机制:
- flask中session机制是:把敏感数据经过加密后放入‘session’中,再把’session’存放到’cookie’中,下次请求的时候,再从浏览器发送过来的’cookie’中读取’session’,然后再从’session’中读取敏感数据,并进行解密,获取最终的用户数据。
- flask的这种’session’机制,可以节省服务器的开销,因为把所有的信息都存储到了客户端(浏览器)。
- 但安全是相对的,把’session’放到’cookie’中,经过加密,也是比较安全的。
session的使用:
在config.py中设置SECRET_KEY:24位的随机字符码
# session SECRET_KEY = os.urandom(24)
导入session:from flask import Flask, session
-
- 设置session:
# session设置一个key-value session['username'] = 'jay' session['password'] = '1399'
- 访问session:
# 访问字典元素可以用session['key']也可以session.get('key') # 区别是前者没有查找到会出异常KeyError,后者返回None print(session['username']) # 推荐使用第二种方式,即使不存在也不会抛出异常 print(session.get('password')) if not session.get('user'): print('user为None')
- 删除session:
# 如果key不存在会抛出异常KeyError # del session['user_id'] # session.pop('user') # 设置第二个参数后,即使user_id不存在也不会出现Key_Error session.pop('username', None) # 清空本项目所有session数据 session.clear()
设置session的过期时间:
默认的session的过期时间为:关闭会话(浏览器关闭)
设置permanent后过期时间为一个月(31天)
session.permanent = True
- 在配置文件中修改permanent时间
PERMANENT_SESSION_LIFETIME = timedelta(days=25)
get请求和post请求
使用场景和传参方式
get请求
使用场景:如果只对服务器获取数据,并没有对服务器产生影响,那么这个时候用get请求。
传参方式:get请求的参数放在url中,通过’?’形式来指定key-value
post请求
使用场景:如果对服务器产生影响,如上传数据,那么用post请求。
传参方式:post请求参数不是放在url中,是通过’form data’的形式发送给服务器的
get请求基本用法
index.html中定义一个链接
<body> <a href="{{ url_for('search', q='hello') }}">跳转到搜索页面</a> </body>
搜索页面的视图函数
@app.route('/search/') def search(): # 得到一个key-value传参字典 arguments = request.args print(arguments) print(arguments.get('q')) return 'search'
post请求基本用法
login.html中定义一个表单
<body> {#如果不写method="post"的话,表单默认使用get请求#} {#action表示跳转到指定页面(不写就是本页面)如果指定的页面没有定义post请求方法会报Method Not Allowed#} <form action="{{ url_for('login') }}" method="post"> <table> <tbody> <tr> <td>用户名:</td> {#name可理解为post请求的key,输入框输入的内容未value#} <td><input type="text" placeholder="请输入用户名" name="username"></td> </tr> <tr> <td>密码:</td> <td><input type="text" placeholder="请输入密码" name="password"></td> </tr> <tr> <td></td> <td><input type="submit" value="登录"></td> </tr> </tbody> </table> </form> </body>
login视图函数:
# 默认的视图函数只能使用get请求 # 如果用post请求需要定义 @app.route('/login/', methods=['GET', 'POST']) def login(): # 判断用的是get方法还是post方法 if request.method == 'GET': return render_template('login.html') else: form = request.form print('账号:' + form.get('username')) print('密码:' + form.get('password')) return '得到参数:' + str(request.form)
注意:这里login在浏览器中回车访问是get请求,返回一个视图。而表单提交后是post请求,返回一个字符串视图
保存全局变量的g对象
- g:global
- g对象可以存储任何数据,也可以用来保存用户数据
- g对象在一次请求中所执行的代码的地方都是可以用的,但是这一次请求过后就销毁了。
g对象的使用:
login.html
<body>
{#如果不写method="post"的话,表单默认使用get请求#}
{#action表示跳转到指定页面,不写就是本页面如果指定页面没有定义post请求方法会报Method Not Allowed#}
<form action="{{ url_for('login') }}" method="post">
<table>
<tbody>
<tr>
<td>用户名:</td>
{#name可理解为post请求的key,输入框输入的内容未value#}
<td><input type="text" placeholder="请输入用户名" name="username"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="text" placeholder="请输入密码" name="password"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="登录"></td>
</tr>
</tbody>
</table>
</form>
</body>
login视图函数:
# 默认的视图函数只能使用get请求
# 如果用post请求需要定义
@app.route('/login/', methods=['GET', 'POST'])
def login():
# 判断用的是get方法还是post方法
if request.method == 'GET':
return render_template('login.html')
else:
form = request.form
username = form.get('username')
password = form.get('password')
if '1' == username and '1' == password:
# 记录登录信息
g.username = username
login_log()
return '登录成功'
else:
return '登陆失败'
钩子函数
before_request
在请求之前执行(所有请求),即在视图函数之前执行
这个函数用一个装饰器修饰 @app.before_request
应用场景:
可以和g对象一起使用,即每一次请求都调用一次这个before_request钩子函数,然后将session的数据保存到g对象中,由于每一次请求之前调用,所以g对象包含的session信息在每次请求前都存在,不会在下一次请求后就销毁。比如:
用户登录后一般在session中存一个user_id,然后用这个user_id来在数据库中查找User信息,如果每一次都查找数据库会很慢,这样可以在before_request钩子函数中将session中的user_id取出来,然后在数据库中查找User信息,然后将这个user信息保存到g对象中去,这样每次请求都有这个user信息了,不用每次都访问数据库。
(但是有个疑问,before_request是在每次请求之前都调用,那么每次都访问数据库将user信息存到g对象中不是比需要user信息的时候在视图函数中访问数据库的次数不是多多了?这个疑问后来懂了再补上)
视图函数:虽然g对象在一次请求过后就会被销毁,但是因为每次请求前(before_request)都访问了session然后将需要的信息存到g对象中,所以g对象被销毁了又在钩子函数中创建,所以每个视图函数都可以使用g对象。
@app.route('/') def index(): print('index, g.username == ' + g.username) return render_template('index.html') # 默认的视图函数只能使用get请求 # 如果用post请求需要定义 @app.route('/login/', methods=['GET', 'POST']) def login(): # 判断用的是get方法还是post方法 if request.method == 'GET': return render_template('login.html') else: form = request.form username = form.get('username') password = form.get('password') if '1' == username and '1' == password: # 记录登录信息 session['username'] = username return redirect(url_for('index')) else: return '登陆失败' @app.before_request def my_before_request(): print('before_request钩子函数执行了') if session.get('username'): g.username = session.get('username') @app.route('/edit/') def edit(): if hasattr(g, 'username'): return 'edit user information' else: return redirect(url_for('login'))
login.html和上面的一样就不复制了
context_processor
使用场景:渲染的模板的多个页面需要相同的数据
注意:必须有返回值,即使返回空字典{}也要返回,否则报错
用法如下:
# 钩子函数返回一个字典,这个字典在所有模板可用 # 从打印结果来看,这个钩子函数应该是在视图函数末尾执行 # 不能没有返回值,任何一种情况都必须返回一个字典,否则报错 @app.context_processor def my_context_processor(): print('my_context_processor钩子函数执行了') username = session.get('username') if username: return {'username': '111'} else: return {}
一个少处理返回情况的异常:TypeError: ‘NoneType’ object is not iterable
@app.context_processor def my_context_processor(): user_id = session.get('user_id') # 这是两种情况,其中一种没处理返回就报:TypeError: 'NoneType' object is not iterable if user_id: user = User.query.filter(User.id == user_id).first() if user: return {'user': user} return {}
创建一个项目必须注意的点
配置文件
debug调试,数据库,session等等
import os from datetime import timedelta # debug模式 DEBUG = True # 配置数据库 DIALECT = 'mysql' DRIVER = 'mysqldb' USERNAME = 'root' PASSWORD = '1234' HOST = '127.0.0.1' PORT = '3306' DATABASE = 'test' # SQLALCHEMY标志URI SQLALCHEMY_DATABASE_URI = "{}+{}://{}:{}@{}:{}/{}?charset=utf8" \ .format(DIALECT, DRIVER, USERNAME, PASSWORD, HOST, PORT, DATABASE) # 忽略SQLALCHEMY警告 SQLALCHEMY_TRACK_MODIFICATIONS = False # session # 设置session'盐':SECRET_KEY SECRET_KEY = os.urandom(24) # 设置session过期时间,25天,设置permanent默认一个月,没设置关闭浏览器就销毁 PERMANENT_SESSION_LIFETIME = timedelta(days=25)
导包,各种包
from flask import Flask, url_for, render_template, request, session, g, redirect
文件夹管理等等
具体看问答平台小案例
坑,各种坑
flask debug模式怎么开都开不了的坑
点run左边的project->EditConfiguration,勾选FLASK_DEBUG,另外如果只想启动一个服务,不用在控制台弹出多个,点击右上角的Single instance only
配置虚拟环境
需要的各种包都在虚拟环境中安装,创建项目的时候注意选择虚拟环境即可