[373]python Flask入门

54 阅读14分钟

@[toc]

Flask入门(一)

Flask是一个Python编写的Web 微框架,让我们可以使用Python语言快速实现一个网站或Web服务。本文参考自Flask官方文档,大部分代码引用自官方文档。

安装Flask

首先我们来安装Flask。最简单的办法就是使用pip。

pip install flask

然后打开一个Python文件,输入下面的内容并运行该文件。默认访问localhost:5000,我们应当可以看到浏览器上输出了Hello Flask!

from flask import Flask

app = Flask(__name__)

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

if __name__ == '__main__':
    app.run(host='127.0.0.1',port=8800,debug=True)

调试模式

我们修改代码中的输出,然后查看浏览器上是否有变化。如果你照做的话,可以看到什么变化都没有。其实Flask内置了调试模式,可以自动重载代码并显示调试信息。这需要我们开启调试模式,方法很简单,设置debug=True

然后再次运行程序,会看到有这样的输出。这时候如果再次修改代码,会发现这次Flask会自动重启。

 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 157-063-180
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

路由

在上面的例子里可以看到路由的使用。如果了解Spring Web MVC的话,应该对路由很熟悉。路由通过使用Flask的app.route装饰器来设置,这类似Java的注解。

from flask import Flask,url_for,request,render_template,jsonify

app = Flask(__name__)

@app.route('/')
def index():
    return 'Index Page'

@app.route('/hello')
def hello():
    return 'Hello, World'

api_list = {
    'count': 'get the count of proxy',
    'get_one': 'get a random proxy',
    'get_all': 'get all proxy from proxy pool',
}
@app.route('/index')
def index():
    # return 'Index Page'
    return jsonify(api_list)

路径变量

如果希望获取/article/1这样的路径参数,就需要使用路径变量。路径变量的语法是/path/<converter:varname>。在路径变量前还可以使用可选的转换器,有以下几种转换器。

转换器作用
string默认选项,接受除了斜杠之外的字符串
int接受整数
float接受浮点数
path和string类似,不过可以接受带斜杠的字符串
any匹配任何一种转换器
uuid接受UUID字符串

下面是Flask官方的例子。

@app.route('/user/<username>')
def show_user_profile(username):
    # show the user profile for that user
    return 'User %s' % username

@app.route('/post/<int:post_id>')
def show_post(post_id):
    # show the post with the given id, the id is an integer
    return 'Post %d' % post_id

构造URL

在Web程序中常常需要获取某个页面的URL,在Flask中需要使用url_for('方法名')来构造对应方法的URL。下面是Flask官方的例子。

>>> from flask import Flask, url_for
>>> app = Flask(__name__)
>>> @app.route('/')
... def index(): pass
...
>>> @app.route('/login')
... def login(): pass
...
>>> @app.route('/user/<username>')
... def profile(username): pass
...
>>> with app.test_request_context():
...  print(url_for('index'))
...  print(url_for('login'))
...  print(url_for('login', next='/'))
...  print(url_for('profile', username='John Doe'))
...
/
/login
/login?next=/
/user/John%20Doe

HTTP方法

如果需要处理具体的HTTP方法,在Flask中也很容易,使用route装饰器的methods参数设置即可。

from flask import request

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        do_the_login()
    else:
        show_the_login_form()

静态文件

Web程序中常常需要处理静态文件,在Flask中需要使用url_for函数并指定static端点名和文件名。在下面的例子中,实际的文件应放在static/文件夹下。

url_for('static', filename='style.css')

模板生成

Flask默认使用Jinja2作为模板,Flask会自动配置Jinja 模板,所以我们不需要其他配置了。默认情况下,模板文件需要放在templates文件夹下。

使用 Jinja 模板,只需要使用render_template函数并传入模板文件名和参数名即可。

from flask import render_template

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
    return render_template('hello.html', name=name)

相应的模板文件如下

<!doctype html>
<title>Hello from Flask</title>
{% if name %}
  <h1>Hello {{ name }}!</h1>
{% else %}
  <h1>Hello, World!</h1>
{% endif %}

日志输出

Flask 为我们预配置了一个 Logger,我们可以直接在程序中使用。这个Logger是一个标准的Python Logger,所以我们可以向标准Logger那样配置它,详情可以参考官方文档

app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')

处理请求

在 Flask 中获取请求参数需要使用request等几个全局对象,但是这几个全局对象比较特殊,它们是 Context Locals ,其实就是 Web 上下文中局部变量的代理。虽然我们在程序中使用的是全局变量,但是对于每个请求作用域,它们都是互不相同的变量。理解了这一点,后面就非常简单了。

Request 对象

Request 对象是一个全局对象,利用它的属性和方法,我们可以方便的获取从页面传递过来的参数。

method属性会返回HTTP方法的类似,例如postgetform属性是一个字典,如果数据是POST类型的表单,就可以从form属性中获取。下面是 Flask 官方的例子,演示了 Request 对象的methodform属性。

from flask import request

@app.route('/login', methods=['POST', 'GET'])
def login():
    error = None
    if request.method == 'POST':
        if valid_login(request.form['username'],request.form['password']):
            return log_the_user_in(request.form['username'])
        else:
            error = 'Invalid username/password'
    # the code below is executed if the request method
    # was GET or the credentials were invalid
    return render_template('login.html', error=error)

如果数据是由GET方法传送过来的,可以使用args属性获取,这个属性也是一个字典。

searchword = request.args.get('key', '')

文件上传

利用Flask也可以方便的获取表单中上传的文件,只需要利用 request 的files属性即可,这也是一个字典,包含了被上传的文件。如果想获取上传的文件名,可以使用filename属性,不过需要注意这个属性可以被客户端更改,所以并不可靠。更好的办法是利用werkzeug提供的secure_filename方法来获取安全的文件名。

from flask import request
from werkzeug.utils import secure_filename

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['the_file']
        f.save('/var/www/uploads/' + secure_filename(f.filename))

Cookies

Flask也可以方便的处理Cookie。使用方法很简单,直接看官方的例子就行了。下面的例子是如何获取cookie。

from flask import request

@app.route('/')
def index():
    username = request.cookies.get('username')
    # 使用 cookies.get(key) 代替 cookies[key] 避免
    # 得到 KeyError 如果cookie不存在

如果需要发送cookie给客户端,参考下面的例子。

from flask import make_response

@app.route('/')
def index():
    resp = make_response(render_template(...))
    resp.set_cookie('username', 'the username')
    return resp

重定向和错误

redirectabort函数用于重定向和返回错误页面。

from flask import abort, redirect, url_for

@app.route('/')
def index():
    return redirect(url_for('login'))

@app.route('/login')
def login():
    abort(401)
    this_is_never_executed()

默认的错误页面是一个空页面,如果需要自定义错误页面,可以使用errorhandler装饰器。

from flask import render_template

@app.errorhandler(404)
def page_not_found(error):
    return render_template('page_not_found.html'), 404

响应处理

默认情况下,Flask会根据函数的返回值自动决定如何处理响应:如果返回值是响应对象,则直接传递给客户端;如果返回值是字符串,那么就会将字符串转换为合适的响应对象。我们也可以自己决定如何设置响应对象,方法也很简单,使用make_response函数即可。

@app.errorhandler(404)
def not_found(error):
    resp = make_response(render_template('error.html'), 404)
    resp.headers['X-Something'] = 'A value'
    return resp

Sessions

我们可以使用全局对象session来管理用户会话。Sesison 是建立在 Cookie 技术上的,不过在 Flask 中,我们还可以为 Session 指定密钥,这样存储在 Cookie 中的信息就会被加密,从而更加安全。直接看 Flask 官方的例子吧。

from flask import Flask, session, redirect, url_for, escape, request

app = Flask(__name__)

@app.route('/')
def index():
    if 'username' in session:
        return 'Logged in as %s' % escape(session['username'])
    return 'You are not logged in'

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        session['username'] = request.form['username']
        return redirect(url_for('index'))
    return '''
        <form method="post">
            <p><input type=text name=username>
            <p><input type=submit value=Login>
        </form>
    '''

@app.route('/logout')
def logout():
    # remove the username from the session if it's there
    session.pop('username', None)
    return redirect(url_for('index'))

# set the secret key.  keep this really secret:
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'

模板简介

这里简单的介绍一下Jinja 模板的使用方法,详细资料直接看原文档吧。

模板标签

其实Jinja 模板和其他语言和框架的模板类似,反正都是通过某种语法将HTML文件中的特定元素替换为实际的值。如果使用过JSP、Thymeleaf 等模板,应该可以非常容易的学会使用 Jinja模板。

其实从上面的例子中我们应该可以看到Jinja 模板的基本语法了。代码块需要包含在{% %}块中,例如下面的代码。

{% extends 'layout.html' %}
{% block title %}主页{% endblock %}
{% block body %}

    <div class="jumbotron">
        <h1>主页</h1>
    </div>

{% endblock %}

双大括号中的内容不会被转义,所有内容都会原样输出,它常常和其他辅助函数一起使用。下面是一个例子。

<a class="navbar-brand" href={{ url_for('index') }}>Flask小例子</a>

继承

模板可以继承其他模板,我们可以将布局设置为父模板,让其他模板继承,这样可以非常方便的控制整个程序的外观。

例如这里有一个layout.html模板,它是整个程序的布局文件。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{% block title %}{% endblock %}</title>
    <link rel="stylesheet" href="{{ url_for('static',filename='css/bootstrap.css') }}"/>
    <link rel="stylesheet" href="{{ url_for('static',filename='css/bootstrap-theme.css') }}"/>

</head>
<body>

<div class="container body-content">
    {% block body %}{% endblock %}
</div>

<div class="container footer">
    <hr>
    <p>这是页脚</p>
</div>

<script src="{{ url_for('static',filename='js/jquery.js') }}"></script>
<script src="{{ url_for('static',filename='js/bootstrap.js') }}"></script>

</body>
</html>

其他模板可以这么写。对比一下面向对象编程的继承概念,我们可以很容易的理解。

{% extends 'layout.html' %}
{% block title %}主页{% endblock %}
{% block body %}

    <div class="jumbotron">
        <h1>主页</h1>
        <p>本项目演示了Flask的简单使用方法,点击导航栏上的菜单条查看具体功能。</p>
    </div>

{% endblock %}

控制流

条件判断可以这么写,类似于JSP标签中的Java 代码,{% %}中也可以写Python代码。下面是Flask官方文档的例子。

  <div class=metanav>
  {% if not session.logged_in %}
    <a href="{{ url_for('login') }}">log in</a>
  {% else %}
    <a href="{{ url_for('logout') }}">log out</a>
  {% endif %}
  </div>

循环的话可以这么写,和在Python中遍历差不多。

        <tbody>
        {% for key,value in data.items() %}
            <tr>
                <td>{{ key }}</td>
                <td>{{ value }}</td>
            </tr>
        {% endfor %}
        <tr>
            <td>文件</td>
            <td></td>
        </tr>
        </tbody>

需要注意不是所有的Python代码都可以写在模板里,如果希望从模板中引用其他文件的函数,需要显式将函数注册到模板中。可以参考这个爆栈提问


Flask入门(二)

WEB工作原理

1、C/S和B/S架构:c:client s:server b:browser s:server 2、B/S工作原理

客户端(浏览器) <=> WEB服务器(nginx/apache) <=> Python(Flask) <=> 数据库(MySQL)

Flask框架

1.简介 是一个非常小的python web框架,被称为微型框架,只提供了一个强健的核心,其它的功能都要使用扩展来实现。意味着可以根据自己的项目需求量身打造。遵循BSD证书。

官网http://flask.pocoo.org/

附各种证书说明:

2. 组成

  • 调试、路由、WSGI系统
  • 模板引擎(Jinja2,Flask的核心人员开发)

3. 安装

pip install flask -i https://pypi.douban.com/simple/

Flask 依赖两个外部库: Jinja2 模板引擎和 Werkzeug WSGI 套件

4. 第一个程序

# 导入Flask类库
from flask import Flask

# 创建应用实例
app = Flask(__name__)
# 创建视图函数
@app.route('/')
def index():
    return 'Hello Flask !'

# 启动应用实例
if __name__ == '__main__':
    app.run()

默认访问地址:http://127.0.0.1:5000

5. 启动参数

参数说明
debug是否开启调试模式,开启后有错误提示,代码修改后可以重新启动
threaded开启多线程,默认是不开启的
port指定端口号
host指定主机,设置为‘0.0.0.0’后可以通过ip地址进行访问

6. 请求与响应 1.变量或对象

变量/对象上下文说明
current_app程序上下文当前运行的程序实例
request请求上下文请求对象,保存了客户端的所有的HTTP请求信息
session请求上下文用户会话,用于保存需要’记住’的会话信息
g(global的简写)程序上下文处理请求时用作临时存储的对象,专门用来保存用户数据,每次请求都会重置,g对象在一次请求中的所有代码中都是可以使用的。
  1. 请求钩子函数
from flask import Flask, g

app = Flask(__name__)

@app.route('/test/')
def test():
    return g.string

@app.before_first_request
def bf_first_request():
    g.string = 'before_first_request'

@app.before_request
def bf_request():
    g.string = 'before_request'

ps:

  # 注册一个函数,没有异常情况下,在每次请求之后运行.
  # 注册的函数至少需要含有一个参数
  # 这个参数实际上为服务器的响应,且函数中需要返回这个响应参数.
  @app.after_request
  def af_request(param):
      param.set_cookie('username', 'xiaoming')
      return param

7.视图函数

  • 不带参数的视图函数,见《完整程序》
  • 带参数的视图函数,如下:
# 带参数的路由,不指定参数类型,默认是字符串
@app.route('/hello/<username>/')
def hello(username):
    return 'Hello %s !' % username

# 参数可以指定类型:int、float、path(/不再是分隔符)
@app.route('/user/<int:uid>/')
def user(uid):
    return '%d号用户详细信息' % uid

@app.route('/path/<path:p>/')
def path(p):
    return p
  • 总结说明
  1. 若指定参数,需要将参数外添加<>,且路由参数名称要与视图函数参数一致
  2. 参数可以指定类型,如:int、float、path,不指定默认为字符串
  3. 路由中的最后的/最好还是加上

8. 请求(request)

@app.route('/request/')
def req():
    # 完整的请求URL
    # return request.url
    # 基本路由信息,不包含get参数
    #return request.base_url
    # 只包含主机和端口
    #return request.host_url
    # 只包含装饰器中的路由地址
    #return request.path
    # 请求方法类型
    #return request.method
    # 客户端IP地址
    #return request.remote_addr
    # 所有的请求参数(GET)
    #return request.args['page']
    # 请求头信息
    return request.headers['User-Agent']

Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36

  • Mozilla/5.0 :以前用于Netscape浏览器,目前大多数浏览器UA都会带有。
  • Windows NT 6.1:代表windows7系统。
  • WOW64:Windows-on-Windows 64-bit,32位的应用程序运行于此64位处理器上。
  • AppleWebKit/537.36:浏览器内核。
  • KHTML:一个HTML排版引擎。
  • like Gecko:这不是Geckeo 浏览器,但是运行起来像Geckeo浏览器。
  • Chrome/55.0.2883.87:Chrome版本号。
  • Safari/537.36:宣称自己是Safari?

9. 响应(response)

@app.route('/response/')
def response():
    # 只要返回字符串就可以,默认状态码为200
    #return 'ok'
    # 可以指定状态码
    #return 'page not found', 404
    # 先使用专门的函数构造一个响应,然后返回,可以在构造时指定状态码
    resp = make_response('我是提前构造好的响应', 404)
    return resp

10. 重定向(redirect)

@app.route('/redirect/')
def new():
    #return 'abc'
    #return redirect('/')
    # 根据视图函数,反向构造对应的路由,传递的参数是视图函数名
    #return redirect(url_for('response'))
    # 可以拼接路由参数,多余的参数会以get方式拼接在url后面
    #return redirect(url_for('hello', username='xiaoming', page=2))
    # 当需要构造外部跳转的链接时,需要将_external设置为True以构造完整路由
    return url_for('hello', username='xx', _external=True)
    url_for('hello')  ==> /hello/
    url_for('hello', _external=True) ==> http://127.0.0.0:5000/hello/

11. 终止(abort)

@app.route('/abort/')
def err():
    # 使用abort不是说控制权归调用的函数所有,
    # 而是向系统抛出一个异常,按照系统原有的异常处理方式进行处理
    abort(404)
    return 'error'

# 定制错误显示页面
@app.errorhandler(404)
def page_not_found(e):
    return '老铁,有木有搞错'

12. 会话控制(cookie/session)

# 设置cookie
@app.route('/set_cookie/')
def set_cookie():
    resp = make_response('cookie已设置')
    expires = time.time() + 10
    # 设置cookie,顺便可以指定过期时间
    resp.set_cookie('name', 'xiaoming', expires=expires)
    return resp

# 获取cookie
@app.route('/get_cookie/')
def get_cookie():
    return request.cookies.get('name') or '你是哪个二哥'

# 设置秘钥,此处的秘钥不只是用于session的加密
app.config['SECRET_KEY'] = '123456'  

# 设置session
@app.route('/set_session/')
def set_session():
    session['username'] = 'xiaoma'
    return 'session已设置'
# 获取session
@app.route('/get_session/')
def get_session():
    return session.get('username', 'who are you?')

flask_script扩展

1. 安装pip install flask-script 2. 说明: 在项目测试完成后,上线时最好不要改动任何代码。只能通过终端的方式进行启动,通过传递不同的参数,完成特定的启动方式。很遗憾flask默认不支持命令行启动,然而幸运(^_^)的是有一个第三方库flask-script帮我们实现了这个功能。简单来说,它就是一个flask终端启动的命令行解析器。 3. 使用

# 导入类库
from flask_script import Manager
# 创建对象
manager = Manager(app)
# 启动应用实例
if __name__ == '__main__':
    #app.run(debug=True, threaded=True, port=8000, host='0.0.0.0')
    manager.run()
  • 启动参数
 -?,--help          # 查看帮助信息
--threaded          # 开启多线程
-d                  # 开启调试模式
-r                  # 自动加载
-h,--host           # 指定主机
-p,--port           # 指定端口 

在命令行使用指令python manage.py runserver -d -r -p 8000 -h 0.0.0.0

蓝本(blueprint)

1. 说明: 代码越来越复杂时,将所有的视图函数放在一个文件中是很不合理的,如果能够根据功能不同将特定的视图函数分类存放,蓝本就是为了解决这个问题而出现的 2. 使用

# 导入类库
from flask import Blueprint
# 创建对象
user = Blueprint('user', __name__)

@user.route('/register/')
def register():
    return '欢迎注册'

#在manage.py中注册蓝本。
# 蓝本注册,不注册时蓝本处于休眠状态  
app.register_blueprint(user, url_prefix='/user')