Flask项目--鱼书学习笔记(一)

Flask项目–鱼书学习笔记(一)

Flask安装过程略。

判断查询参数是否为isbn号

@app.route("/search/<q>/<page>")
def search(q,page):
    """
    搜索书籍路由
    :param q: 关键字 OR isbn
    :param page: 页码
    """
    # isbn isbn13 由13个0-9在数字组成
    # isbn10 由10表0-9表数字组组成,中间可能包含' - '

    isbn_or_key = 'key'
    if len(q) == 13 and q.isdigit():
        isbn_or_key = 'isbn'
    short_q = q.replace('-', '')
    if '-' in q and len(short_q) == 10 and short_q.isdigit():
        isbn_or_key = 'isbn'
    pass

知识点:

  • 字符串中的**isdigit()**函数可以判断是否为数字
  • in关键字判断一个字符串是否在另一份字符串内
  • 多个逻辑判断排列原则:
    1. 有更大可能判断结果为假的条件应该放在前面
    2. 需要查询数据库的操作由于会消耗资源,应该尽量靠后

代码重构

@app.route("/search/<q>/<page>")
def search(q, page):
    """
    搜索书籍路由
    :param q: 关键字 OR isbn
    :param page: 页码
    """
    isbn_or_key = is_isbn_or_key(q)

helper.py

def is_isbn_or_key(word):
    """
        判断word是isbn号还是查询关键字key
        isbn isbn13 由13个0-9在数字组成
        isbn10 由10表0-9表数字组组成,中间可能包含' - '
    :param word:
    :return: key or isbn
    """
    isbn_or_key = 'key'
    if len(word) == 13 and word.isdigit():
        isbn_or_key = 'isbn'
    short_word = word.replace('-', '')
    if '-' in word and len(short_word) == 10 and short_word.isdigit():
        isbn_or_key = 'isbn'
    return isbn_or_key

如何判断代码是否需要封装

  • 视图函数中不要有太多数据处理、判断类型的代码,能封装的尽量封装
  • 是否有大量判断类型的代码
  • 代码能否重用
  • 是否可以一眼看出这段代码的作用

知识点:

  • 看源代码分层的看,第一遍首先看懂代码的作用,理清代码的结构线索,而不是一定要看代码的实现细节。
  • 过多的注释会让代码变的臃肿,尽量使用易懂的函数名来代替注释,保持代码的简洁性。

使用三元表达式优化

import requests
class HTTP:  #class HTTP(object):有无都无所谓,在Python3中都表示新式类

    @staticmethod
    def get(url,return_json=True):
    
        r = requests.get(url)

        if r.status_code == 200:
            if return_json():
                return r.json()
            else:
                return r.text
        else:
            if return_json:
                return {}
            else:
                return ''

优化后

import requests
class HTTP:  #class HTTP(object):有无都无所谓

   @staticmethod
   def get(url,return_json=True):
   
       r = requests.get(url)

       if r.status_code != 200:
           return {} if return_json else ''      #特例情况
       return r.json() if return_json else r.text   #正常返回

知识点:

  • @staticmethod注解表示这是一个静态方法,@classmethod也可达到相同效果,不过需要传一个cls参数
  • 最好将正常情况下的return放在最后

将具体调用HTTP请求,获取结果的业务代码封装

class YuShuBook:

    search_by_isbn_url = "http://t.yushu.im/v2/book/search/isbn/{}"

    search_by_key_url = "http://t.yushu.im/v2/book/search?q={}&count={}&start={}"

    @classmethod
    def search_by_isbn(cls, isbn):
        url = cls.search_by_isbn_url.format(isbn)
        return HTTP.get(url)

    @classmethod
    def search_by_key(cls, q, count=15, start=0):
        url = cls.search_by_key_url.format(q, count, start)
        return HTTP.get(url)

用jsonify代替json.dumps

使用前:

@app.route("/book/search/<q>/<page>")
def search(q, page):
    """
    搜索书籍路由
    :param q: 关键字 OR isbn
    :param page: 页码
    """
    isbn_or_key = is_isbn_or_key(q)
    if isbn_or_key == 'isbn':
        result = YuShuBook.search_by_isbn(q)
    else:
        result = YuShuBook.search_by_key(q)
    return json.dumps(result), 200, {'content-type': 'application/json'}

使用后:

return jsonify(result)

获取http请求,返回json格式的数据,其实就是API的开发,API开发的难点在于url的设计。

将视图函数拆分到单独的模块中

将试图函数都放在一个文件中有哪些不足:

  1. 代码太长,不利于维护
  2. 从业务模型抽象的角度,不应该把他们都放在一个文件中。关于书籍相关的API就应该放在书籍模型的视图函数文件中,跟用户相关的API就应该放在用户模型相关的文件中
  3. 入口文件的意义比较独特,会启动web服务器以及做很多初始化的操作,就算要放在一个文件也不应该业务的操作放在入口文件中来

深入了解Flask路由

Flask的工作就是捕捉这个URL地址,弄清用户想要做什么,并在众多的Python函数中匹配一个可以处理它的函数,请求流程如下图:

Flask路由

Endpoint有什么作用

端点通常用作反向查询URL地址(viewfunction**–>endpoint–>**URL)。例如,在flask中有个视图,你想把它关联到另一个视图上(或从站点的一处连接到另一处)。不用去千辛万苦的写它对应的URL地址,直接使用URL_for()就可以啦:

@app.route('/')
def index():
    print url_for('give_greeting', name='Mark') # 打印出 '/greeting/Mark'

@app.route('/greeting/<name>')
def give_greeting(name):
    return 'Hello, {0}!'.format(name)

这样做是大有裨益的:我们可以随意改变应用中的URL地址,却不用修改与之关联的资源的代码。

为何要多此一举

那么问题来了:为何要多此一举,为何要先把URL映射到端点上,再通过端点映射到视图函数上,为何不跳过中间的这个步骤?
原因就是采用这种方法能够使程序更高、更快、更强。例如蓝本。蓝本允许我们把应用分割为一个个小的部分,现在admin蓝本中含有超级管理员级的资源,user蓝本中则含有用户一级的资源。
蓝本允许咱们把应用分割为一个个以命名空间区分的小部分:
main.py:

from flask import Flask, Blueprint
from admin import admin
from user import user

app = Flask(__name__)
app.register_blueprint(admin, url_prefix='admin')
app.register_blueprint(user, url_prefix='user')

admin.py:

admin = Blueprint('admin', __name__)

@admin.route('/greeting')
def greeting():
    return 'Hello, administrative user!'

user.py:

user = Blueprint('user', __name__)
@user.route('/greeting')
def greeting():
    return 'Hello, lowly normal user!'

注意,在两个蓝本中路由地址*‘/greeting’的函数都叫"greeting"。如果我想调用admin对应的greeting函数,我不能说“我想要greeting*”,因为这里还有一个user对应的greeting函数。端点这时就发挥作用了:指定一个蓝本名称作为端点的一部分–通过这种方式端点实现了对命名空间的支持。所以,我们可以这样写:

print url_for('admin.greeting') # Prints '/admin/greeting'
print url_for('user.greeting') # Prints '/user/greeting'

实例

from flask import Flask, url_for

app = Flask(__name__)

# We can use url_for('foo_view') for reverse-lookups in templates or view functions
@app.route('/foo')
def foo_view():
    pass

# We now specify the custom endpoint named 'bufar'. url_for('bar_view') will fail!
@app.route('/bar', endpoint='bufar')
def bar_view():
    pass

with app.test_request_context('/'):
    print (url_for('foo_view'))  #/foo
    print (url_for('bufar'))  #/bar
    # url_for('bar_view') will raise werkzeug.routing.BuildError
    print (url_for('bar_view'))  #端点bar_view是没有定义的

循环引入流程分析

下面看下fisher.py和book.py的具体流程图

Flask执行流程

回答流程图中的两个问题:
问题1:因为都是由fisher引入book,Python不会把模块导入两次。所以只执行了一次book。
问题2:由于一次是主流程执行fisher文件;一次是由book模块导入 fisher。

可以看到,红线和蓝线分别注册了两个app,且发生了覆盖,最终导致注册路由和启动的不是同一个app。
其他相关资料已经同步到我的博客网站,欢迎访问我的个人博客了解更多内容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章