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关键字判断一个字符串是否在另一份字符串内
- 多个逻辑判断排列原则:
- 有更大可能判断结果为假的条件应该放在前面
- 需要查询数据库的操作由于会消耗资源,应该尽量靠后
代码重构
@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的设计。
将视图函数拆分到单独的模块中
将试图函数都放在一个文件中有哪些不足:
- 代码太长,不利于维护
- 从业务模型抽象的角度,不应该把他们都放在一个文件中。关于书籍相关的API就应该放在书籍模型的视图函数文件中,跟用户相关的API就应该放在用户模型相关的文件中
- 入口文件的意义比较独特,会启动web服务器以及做很多初始化的操作,就算要放在一个文件也不应该业务的操作放在入口文件中来
深入了解Flask路由
Flask的工作就是捕捉这个URL地址,弄清用户想要做什么,并在众多的Python函数中匹配一个可以处理它的函数,请求流程如下图:
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的具体流程图
回答流程图中的两个问题:
问题1:因为都是由fisher引入book,Python不会把模块导入两次。所以只执行了一次book。
问题2:由于一次是主流程执行fisher文件;一次是由book模块导入 fisher。
可以看到,红线和蓝线分别注册了两个app,且发生了覆盖,最终导致注册路由和启动的不是同一个app。
其他相关资料已经同步到我的博客网站,欢迎访问我的个人博客了解更多内容。