首页 > 技术文章 > FLASK框架基础知识一

jiangxianseng 2020-03-19 01:30 原文

1.什么是flask?

Flask 本是作者 Armin Ronacher在2010年4月1日的一个愚人节玩笑 ,不过后来大受欢迎,进而成为一个正式的python编写的web框架

Flask是一个Python编写的Web 微框架,让我们可以使用Python语言快速实现一个网站或Web服务,在介绍Flask之前首先来聊下它和Django的联系以及区别,django是大而全的web框架,它内置许多模块,flask是一个小而精的轻量级框架,Django功能大而全,Flask只包含基本的配置, Django的一站式解决的思路,能让开发者不用在开发之前就在选择应用的基础设施上花费大量时间。Django有模板,表单,路由,基本的数据库管理等等内建功能。与之相反,Flask只是一个内核,默认依赖于2个外部库: Jinja2 模板引擎和 WSGI工具集--Werkzeug , flask的使用特点是基本所有的工具使用都依赖于导入的形式去扩展,flask只保留了web开发的核心功能。

WSGI(web服务器网关接口)是python中用来规定web服务器如何与python Web服务器如何与Python Web程序进行沟通的标准,本质上就是一个socket服务端。而 Werkzeug模块 就是WSGI一个具体的实现

关键词:一个Python编写微web框架 一个核心两个库( Jinja2 模板引擎 和 WSGI工具集)

wsgiref

最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。

如果要动态生成HTML,就需要把上述步骤自己来实现。不过,接受HTTP请求、解析HTTP请求、发送HTTP响应都是苦力活,如果我们自己来写这些底层代码,还没开始写动态HTML呢,就得花个把月去读HTTP规范。

正确的做法是底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口协议来实现这样的服务器软件,让我们专心用Python编写Web业务。这个接口就是WSGI:Web Server Gateway Interface。而wsgiref模块就是python基于wsgi协议开发的服务模块

from wsgiref.simple_server import make_server

def mya(environ, start_response):
   print(environ)
   start_response('200 OK', [('Content-Type', 'text/html')])
   if environ.get('PATH_INFO') == '/index':
       with open('index.html','rb') as f:
           data=f.read()

   elif environ.get('PATH_INFO') == '/login':
       with open('login.html', 'rb') as f:
           data = f.read()
   else:
       data=b'<h1>Hello, web!</h1>'
   return [data]

if __name__ == '__main__':
   myserver = make_server('', 8011, mya)
   print('监听8010')
   myserver.serve_forever()

wsgiref简单应用
from werkzeug.wrappers import Request, Response

@Request.application
def hello(request):
   return Response('Hello World!')

if __name__ == '__main__':
   from werkzeug.serving import run_simple
   run_simple('localhost', 4000, hello)


# 1 django与flask都是实现了wsgi协议。
# 2 flask用的werkzeug来对wsgi进行了封装,werkzeug是用run_simple('localhost', 4000, hello)来启动flask项目
# 3 run_simple ('localhost', 4000, hello) 第一个是ip,第二个是项目启动端口,第三个是可执行对象,
# 也就是当请求wsgi,就会调用这个第三个参数。

 

2 为什么要有flask?

flask性能上基本满足一般web开发的需求, 并且灵活性以及可扩展性上要优于其他web框架, 对各种数据库的契合度都非常高

关键词

  1. 性能基本满足需求

  2. 灵活性可拓展性强

  3. 对各种数据库的契合度都比较高。

4.在真实的生产环境下,小项目开发快,大项目设计灵活

 

3.如何启动一个flask项目

# 安装flask
'''
pip install flask

'''


# 1 导入flask,我们要用flask,就必须导入Flask
from  flask import Flask
# 2 生成一个Flask对象,__name__表示当前文件的名字
app = Flask(__name__)


# 3 添加路由,flask用的是装饰器的模式
#注册路由,并写响应函数index
@app.route("/")
def index():
   return "Hello flask"

if __name__ == '__main__':
   #4 启动flask
   #run里面是执行了run_simple(host,port,self=app,也就是flask对象)
   app.run()
 # 点击app = Flask(__name__),可以看到源码中flask实例化时需要的参数
   
   def __init__(
       self,
       # 表示入口文件的名字,即__name__
       import_name,
       # 静态路由
       static_url_path=None,
# 静态文件夹
       static_folder="static",      
       static_host=None,
       host_matching=False,
       subdomain_matching=False,
       # HTML文件夹
       template_folder="templates",
       instance_path=None,
       instance_relative_config=False,
       root_path=None,
  ):

 

3.flask四剑客 (返回字符串,返回html,跳转路由,返回json)

# 1 如何响应一个字符串
# 2 如何响应一个html页面
# 3 如何跳转页面
# 4 如何返回一个json字符串

from  flask import Flask,render_template,redirect,jsonify
app = Flask(__name__)
@app.route("/index")
def index():
   #1 返回字符串
   return "你号,我是字符串"
   # 1 返回一个html,
           # 1 从flask里面导入render_template,2 在同级目录中添加templates文件夹,将html页面这个文件夹底下(默认从当前文件夹下找html文件)
   return render_template("index.html")
   # 3 跳转路由,1 从flask里面导入redirect
   return  redirect("/login")
   # 4 返回数据转json返回,要从flask中导入jsonify
   data = {'name':"jason","name1":"owen"}
   return  jsonify(data)

@app.route("/login")
def login():
   return "我是login页面"


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

4.flask的配置文件

flask中的配置文件是一个flask.config.Config对象(继承字典),默认配置为:
{
       'DEBUG':                                get_debug_flag(default=False),  是否开启Debug模式
       'TESTING':                              False,                          是否开启测试模式
       'PROPAGATE_EXCEPTIONS':                 None,                          
       'PRESERVE_CONTEXT_ON_EXCEPTION':        None,
       'SECRET_KEY':                           None,
       'PERMANENT_SESSION_LIFETIME':           timedelta(days=31),
       'USE_X_SENDFILE':                       False,
       'LOGGER_NAME':                          None,
       'LOGGER_HANDLER_POLICY':               'always',
       'SERVER_NAME':                          None,
       'APPLICATION_ROOT':                     None,
       'SESSION_COOKIE_NAME':                  'session',
       'SESSION_COOKIE_DOMAIN':                None,
       'SESSION_COOKIE_PATH':                  None,
       'SESSION_COOKIE_HTTPONLY':              True,
       'SESSION_COOKIE_SECURE':                False,
       'SESSION_REFRESH_EACH_REQUEST':         True,
       'MAX_CONTENT_LENGTH':                   None,
       'SEND_FILE_MAX_AGE_DEFAULT':            timedelta(hours=12),
       'TRAP_BAD_REQUEST_ERRORS':              False,
       'TRAP_HTTP_EXCEPTIONS':                 False,
       'EXPLAIN_TEMPLATE_LOADING':             False,
       'PREFERRED_URL_SCHEME':                 'http',
       'JSON_AS_ASCII':                        True,
       'JSON_SORT_KEYS':                       True,
       'JSONIFY_PRETTYPRINT_REGULAR':          True,
       'JSONIFY_MIMETYPE':                     'application/json',
       'TEMPLATES_AUTO_RELOAD':                None,
  }
# 4中方法给flask做配置

# 1直接给app对象赋值属性
# 2 以字典的形式,给flask做配置
# 3 以文件的形式,给flask做配置(django就是用这种)
# 4 以类的形式,给flask做配置(如果用flask,推荐是使用第4中)

from flask import Flask

app = Flask(__name__)
# 1方式1(不推荐),因为他只能配置两个配置项,一个是debug 一个是secret_key
# app.debug = True

# 2 方式2 字典的形式,这个里面就可以对所有的flask配置项做配置
#app.config["DEBUG"] = True

#3 方式3 以文件的形式,在form_pyfile(里面传递配文件的路径)
#app.config.from_pyfile("settings.py")

#4 方式4 以类的形式,那为什么推荐大家使用这个呢?因为他可以实现一个文件多个配置,而且减少测试与上线更改的配置项(通过开发和上线的配置类来继承基础配置类实现)
app.config.from_object("setobj.settings")

#5.通过环境变量配置
app.config.from_envvar("环境变量名称")
#app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS'])
环境变量的值为python文件名称名称,内部调用from_pyfile方法
# 6.通过json文件
app.config.from_json("json文件名称")
JSON文件名称,必须是json格式,因为内部会执行json.loads


@app.route("/")
def index():
   return "jason is dsb"

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

5.路由本质以及参数

from flask import Flask,url_for,redirect
app = Flask(__name__)
# @app.route("/")
def index(nid):
   print(nid,type(nid))

   return "ojbk"
# 根据下面源码的分析@app.route可以化成下面这句话
#add_url_rule(“/”, view_function = index )

'''
源码:
def decorator(f): # 这个f指的就是上面的index函数对象
          endpoint = options.pop("endpoint", None)
          self.add_url_rule(rule, endpoint, f, **options)
          return f
'''
'''
@setupmethod
  def add_url_rule(
      self,
      rule,
      endpoint=None,
      view_func=None,
      provide_automatic_options=None,
      **options
  ):
'''
#@app.route的本质就在执行add_url_rule这个中的rule是路由,endpoint是路由别名,view_func是响应函数
#如果endpoint不传就是响应的函数名
app.add_url_rule("/index/<int:nid>", endpoint="index1",view_func=index,methods=["POST","GET"])
# methods既可以在上面这句里面写也可以在下面这句里面写
@app.route("/login",methods=["POST"])
def login():
   #用endpoint取路由要用url_for 在flask中导入url_for,这样可以通过别名跳转路由,也就是反向解析
   print(url_for("index1"))
   # return redirect(url_for("index1"))

#路由参数;methods,可以控制该方法能有哪些请求方式可以访问(POST/GET)
#app.add_url_rule("/index", endpoint="index1",view_func=index,methods=["POST"])
# 路由参数:有名分组,app.add_url_rule("/index/<int:nid>"响应函数必须用nid来接收


if __name__ == '__main__':
   app.run()
   
'''
总结:
1 @app.route("/login") 的本质app.add_url_rule("/login",view_func=login),所以我们就可以用这两个方式来添加路由
2 路由的参数,
2.1 endpoint,做是反向解析,如果上面添加路由的时候,没有传递endpoint就是使用响应函数的函数名,反向解析用url_for(),即传入别名来跳转该路由,这个url_for必须在flask里面导入
2.2 methods=["POST","GET"],该参数控制路由允许哪些请求方法访问,如果不传,默认只能GET方法
2.3 路由以及路由路由转化器。"/index/<int:nid>",<参数的类型:用哪个变量来接收>,响应函数中的形参的名字必须转化器中一致。
默认转换器
DEFAULT_CONVERTERS = {
  'default':         UnicodeConverter,
  'string':           UnicodeConverter,
  'any':             AnyConverter,
  'path':             PathConverter,
  'int':             IntegerConverter,
  'float':           FloatConverter,
  'uuid':             UUIDConverter,
}
'''  
 

6.自定义转化器

# 非重点
# 1 写类,继承BaseConverter
# 2 注册:app.url_map.converters['regex'] = RegexConverter
# 3 使用:@app.route('/index/<regex("\d+"):nid>') 正则表达式会当作第二个参数传递到类中

from flask import Flask, url_for
from werkzeug.routing import BaseConverter

app = Flask(import_name=__name__)


# 1.自定义一个类,继承BaseConverter
class RegexConverter(BaseConverter):
   """
  自定义URL匹配正则表达式
  """

   # 2.继承父类的init和重写to_python和to_user方法
   def __init__(self, map, regex):
       super(RegexConverter, self).__init__(map)
       self.regex = regex

   def to_python(self, value):
       """
      路由匹配时,匹配成功后传递给视图函数中参数的值
      """
       print("to_python", value, type(value))
       return int(value) + 1

   def to_url(self, value):
       """
      使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数
      """
       # 因为父类的此方法做了处理,因此需要继承父类的方法
       val = super(RegexConverter, self).to_url(value)
       return val + "222"


# 3.注册添加到flask中,regex要和init中的regex名字一样,RegexConverter和类名一样
app.url_map.converters['regex'] = RegexConverter


# 4.regex这个名字也要与上面的一致,然后就可以写自定义正则了,正则匹配处理结果,要交给to_python,
# to_python函数可以对匹配处理结果做处理,返回的就是下面index接收的nid
@app.route('/index/<regex("\d+"):nid>')
def index(nid):
   print("index", nid, type(nid))
   # 5.反向解析的结果要传给to_utl处理
   print(url_for('index', nid='888'))
   return 'Index'


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

   
# BaseConverter部分源码
'''
class BaseConverter(object):
  """Base class for all converters."""

  regex = "[^/]+"
  weight = 100

  def __init__(self, map):
      self.map = map

  def to_python(self, value):
      return value

  def to_url(self, value):
      if isinstance(value, (bytes, bytearray)):
          return _fast_url_quote(value)
      return _fast_url_quote(text_type(value).encode(self.map.charset))


'''

总结:

1 导入from werkzeug.routing import BaseConverter
2 我写个继承BaseConverter。实现3个方法,def __init__ , def to_python , def to_url
3 将上面的类注册到app.url_map.converters['regex'] = RegexConverter中,注意regex必须上下名字一致,RegexConverter和自定义类名相同
4 然后就可以在路由转化器中使用3中的regex("传正则")
5 当路由被访问以后。regex("传正则")会匹配结果,把结果传递to_python,我们可以进行再次处理,to_python处理好的结果,会传递给响应函数的形参
6 当用url做反向解析的时候,传递给路由转化器的参数,会经过to_url,进行处理。处理以后,在拼接到路由。

 

7.flask的模板渲染

from flask import Flask,render_template,Markup
app = Flask(__name__)
app.debug = True


USERS = {
   1:{'name':'张三','age':18,'gender':'男','text':"道路千万条"},
   2:{'name':'李四','age':28,'gender':'男','text':"安全第一条"},
   3:{'name':'王五','age':18,'gender':'女','text':"行车不规范"},
}

# 传递html语句
def func1(arg,tank):
   # 需要导入Markup
   return Markup(f"<h1>饼哥正帅,{arg} is sb {tank} is same as {arg}</h1>")

@app.route("/")
def index():
   # data = {
   #     "user" :USERS,
   #     "name": "jason"
   # }
   # 可以传一个一个也可以传多个数据,也可以做成字典打散传过去,传html语句可以通过上面的Markup方式或者html页面用save的方式显示语句
   return render_template("index.html",user = USERS,name="jason",ht1 = func1,ht="<h1>饼哥正帅</h1>")
# 传一个大字典过去记得**打散
   #return render_template("index.html",**data)




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

 

html文件

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>Title</title>
</head>
<body>
<h1> 我是html</h1>
<table>
   <!--for 循环,注意后面要加个括号,django没有括号-->
{% for k,v in user.items() %}
  <tr>
      <td>{{ k }}</td>
     <!--三种取值方式-->
      <td>{{ v.name }}</td>
      <td>{{ v['name'] }}</td>
      <td>{{ v.get('name') }}</td>
      <td>{{url_for("index")}}</td>
  </tr>
{% endfor %}
</table>
<!--渲染单个变量-->
<div>{{name}}</div>
<!--if判断-->
{% if name == "jason" %}
   <h1>is sb</h1>
{% else %}
   <h1>水哥</h1>
{% endif %}
<!--用|save来显示传过来的html语句-->
{{ ht|safe}}
<!--用函数传过来的可以替换参数-->    
{{ht1("jaosn","tank")}}
</body>
</html>

8.flask的请求与响应

from flask import Flask,request,make_response,render_template,redirect

app = Flask(__name__)

@app.route("/",methods=["POST","GET"])
def index():
   # 请求相关的信息
   # print("请求方法",request.method)#请求方法
   # print("get请求的参数",request.args)# get请求的参数
   # print("post请求的参数",request.form) #post请求的参数
   # print("post,与get的所有参数",request.values)#post,与get的所有参数
   # print("请求的cookies",request.cookies)#请求的cookies
   # 请求相关信息
   # request.method 提交的方法
   # request.args get请求提及的数据
   # request.form   post请求提交的数据
   # request.values post和get提交的数据总和
   # request.cookies 客户端所带的cookie
   # request.headers 请求头
   # request.path     不带域名,请求路径
   # request.full_path 不带域名,带参数的请求路径
   # request.script_root
   # request.url           带域名带参数的请求路径
   # request.base_url     带域名请求路径
   # request.url_root     域名
   # request.host_url     域名
   # request.host         127.0.0.1:500

   #关于响应我们已经将了4剑客,如果添加响应头,已经cookie
   #要添加这些东西必须,导入make_response,

   response = make_response("ok")
   #response = make_response(render_template("login.html"))
   #response = make_response(redirect("/login"))
   #设置cookie
   #response.set_cookie("key","val")
   #如何删除cookie
   #response.delete_cookie("key")
   # 如何设置响应头
   response.headers["x-somexx"] = "A SB"
   return  response

9.flask的session

from flask import Flask,session

app = Flask(__name__)
# 要用session,必须app配置一个密钥
app.secret_key =  "asdasdihasdiuh"
app.config['SESSION_COOKIE_NAME']="python13session"

# app.session_interface

#app.session_interface实现了两个方法,一个叫save_session,一个open_session,

@app.route("/",)
def index():
   #如何设置sessoion
   # 1 导入session
   # 2 给sessoion设置值
   session['name'] = "egon"
   session["nam1"] ="sdsd"
   return "ok"

@app.route("/login")
def login():
   print(session["name"])
   return "login"

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

分析session的原理

class SecureCookieSessionInterface(SessionInterface):
 
   salt = "cookie-session"
 
   digest_method = staticmethod(hashlib.sha1)
 
   key_derivation = "hmac"
 
   serializer = session_json_serializer
   session_class = SecureCookieSession

   def get_signing_serializer(self, app):
       if not app.secret_key:
           return None
       signer_kwargs = dict(
           key_derivation=self.key_derivation, digest_method=self.digest_method
      )
       return URLSafeTimedSerializer(
           app.secret_key,
           salt=self.salt,
           serializer=self.serializer,
           signer_kwargs=signer_kwargs,
      )
   # 取session的时候执行的
   def open_session(self, app, request):
       s = self.get_signing_serializer(app)
       if s is None:
           return None
       ##cookie键是SESSION_COOKIE_NAME"=session
       val = request.cookies.get(app.session_cookie_name)

       print("open_session.session_cookie_name,", app.session_cookie_name, )
       if not val:
           return self.session_class()
       max_age = total_seconds(app.permanent_session_lifetime)
       try:
           data = s.loads(val, max_age=max_age)
           print("self.session_class(data)", self.session_class(data) )
           return self.session_class(data)
       except BadSignature:
           return self.session_class()

   #存session的时候执行的
   def save_session(self, app, session, response):
       domain = self.get_cookie_domain(app)
       path = self.get_cookie_path(app)

       # If the session is modified to be empty, remove the cookie.
       # If the session is empty, return without setting the cookie.
       if not session:
           if session.modified:
               response.delete_cookie(
                   app.session_cookie_name, domain=domain, path=path
              )

           return
       # Add a "Vary: Cookie" header if the session was accessed at all.
       if session.accessed:
           response.vary.add("Cookie")

       if not self.should_set_cookie(app, session):
           return
       httponly = self.get_cookie_httponly(app)
       secure = self.get_cookie_secure(app)
       samesite = self.get_cookie_samesite(app)
       expires = self.get_expiration_time(app, session)
       # 把session做了一个加密,把整个session的key--》val,全部加密,的到一个value值,
       #session是一个大字典,
       val = self.get_signing_serializer(app).dumps(dict(session))
       # 他把session加密后得到的val存到cookie里面了
       #cookie键是SESSION_COOKIE_NAME"=session
       print("源码中的session",dict(session))
       print("app.session_cookie_name,",app.session_cookie_name,)
       response.set_cookie(
           app.session_cookie_name,
           val,
           expires=expires,
           httponly=httponly,
           domain=domain,
           path=path,
           secure=secure,
           samesite=samesite,
      )

10.闪现

from flask import Flask,flash,get_flashed_messages

app = Flask(__name__)
#app.session_interface
app.secret_key ="sdasd"
# 什么闪现:就像session一样,也是一个页面设置,另一个页面使用,我不管你在哪个页面调用的
# 只要调用一次,就清空了,
# 闪现的作用,一般用信息处理。假设用户,a页面做操作,产生了信息。我希望在b页面内获取。
# 但是我不知道用户在什么时候,访问b页面,但是只要用户一旦访问页面就把信息显示出来。
# 同一页面,同次请求是可以拿多次的
@app.route("/")
def index():
   #产生信息,message设置消息的,category给消息分类,如果不传默写用”message“

   flash("你错过了我")
   
   flash(message="你再次错过我",category="渣男")
   return "index"

@app.route("/login")
def login():
   #(with_categories=True,消息是否要带上分类信息,category_filter=["渣男"]对消息进行过滤,取指定的分类消息
   print(get_flashed_messages(with_categories=True,category_filter=["渣男"]))
   print(get_flashed_messages())
   return "login"


@app.route("/test")
def test():
   print(get_flashed_messages())
   return "test"

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

 

 

 

推荐阅读