Leetao's Blog

Talk is cheap, show me the code

0%

前言

说到钩子函数大家可能会觉得有点陌生,但是不着急,接着看下去,大家只要用过 flask 对我接下来说的这些一定不会陌生。

钩子函数

钩子函数可以分为两层说明,第一层是 app 层,第二层则是 blueprint

app 层

app 层的钩子函数有 before_request,before_first_request,after_request,teardown_request,下面我们一一分析。

before_request

Registers a function to run before each request.

在每次请求都会执行的函数,比如:用于连接数据库连接,或者从会话中加载登录用户

注意:该函数不需要任何参数,如果其返回了一个非空的值,则其将会作为当前视图的返回值,看下面的例子。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from flask import Flask,

app = Flask(__name__)

@app.before_request
def before_request():
print('before request started')
return "example01"

@app.route('/')
def index():
return 'Hello world''

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

这个例子下当访问 localhost:5000/的时候,前端渲染的值为 “example01” 而不是 “Hello world”

before_first_request

Registers a function to be run before the first request to this instance of the application.

仅在第一次请求的时候去调用这个函数,比如初始化加载一次性的数据。

注意:和 before_request 不同的是, 它的非空返回值会被忽略。

问题来了:before_first_request 和 before_request 加载顺序是什么样子呢?

让我们通过下面的代码看一下:

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from flask import Flask, g, request

app = Flask(__name__)

@app.before_request
def before_request():
print('before request started')

@app.before_first_request
def before_request():
print('before first request started')

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

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

结果如下图:

这次 first_request 胜出了

after_request

Register a function to be run after each request

在每次请求结束后运行

除了运行的时间和上面不同之外,after_request 这个函数带有一个参数,用来接收response_class,一个响应对象,一般用来统一修改响应的内容,比如修改响应头。

teardown_request

Register a function to be run at the end of each request, regardless of whether there was an exception or not.

在每次请求结束调用,不管是否出现异常,同样 teardown_request 也需要一个参数,这个参数用来接收异常,当然没有异常的情况下这个参数的值为 None,一般它用来释放程序所占用的资源,比如释放数据库连接

after_request 和 teardown_request 的区别

两者最大的区别是在于,从Flask 0.7开始,如果出现未处理的异常,after_request 将不会被执行,而后者将正常运行并接收异常,其次还有两者的执行顺序,让我们通过代码去了解一下.

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from flask import Flask, g, request

app = Flask(__name__)

@app.before_request
def before_request():
print('before request started, %s' % request.url)

@app.before_first_request
def before_request():
print('before first request started, %s' % request.url)

@app.after_request
def after_request(reponse):
print("after request started, %s" % request.url)
return reponse

@app.teardown_request
def teardown_request(exception):
print("teardown request,%s,%s" % (exception,request.url))

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

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

结果如下:

结果很清晰,after_request 先执行(注意这个图和代码)

说完了,app 层的关于 request 的钩子函数,接下来说一说 blueprint 层的

blueprint 层

blueprint 层关于request 的钩子函数其实和app层基本一致,有两点区别:

  1. 与 app 层相比,bluepint 层少了一个 before_first_request 的钩子函数

  2. 多了一些,可以在蓝图调用 app 层的钩子函数:before_app_first_request,before_app_request,after_app_request,after_app_request,teardown_app_request

请求上下文(request context)

为什么这里会突然出现这个标题呢?我们在哪里用到了这个吗?还记得上个图吗,我们在所有的请求中都访问了request对象,并且成功了输出了 url,

但是我们并没有传入它,它是一个全局对象吗?让我们测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from flask import Flask, g, request

app = Flask(__name__)

def test_request():
print("test request,%s" % request.url)

test_request()

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

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

看一下截图:

由此可见 request 对象仅可以在请求上下文的生命周期可以访问,由此不难得出,我们上面说到几个钩子函数也是挂载到请求上下文的生命周期的不同阶段从而发挥作用的。

g 对象

flask 中有两种上下文,一是:请求上下文(request context),上面刚刚说到的,另外一种则是应用上下文(application context),

g 对象则是在后者期间存储数据的命名空间对象。在一个请求之间用它来存储数据是再好不过了。

注意我加粗的地方:一个请求,为什么说是在一个请求之间呢,上面说了 g 是在应用上下文中发挥作用的,这就意味着一旦它所处的应用上下文结束了,它也随之失效了,那么一个应用上下文的生命周期是多久呢?

让我们看一下官网给的说明:

The application context is created and destroyed as necessary. When a Flask application begins handling a request, it pushes an application context and a request context. When the request ends it pops the request context then the application context. Typically, an application context will have the same lifetime as a request.

看不懂上面英文没有关系,我来总结一些,上面一段官网文字说道,一个应用上下文的生命周期和一个请求相同,这意味什么?也就以为着当一个请求结束了,当前的 g 也就失效了。

给定一个未排序的数组,判断这个数组中是否存在长度为 3 的递增子序列。

数学表达式如下:

如果存在这样的 i, j, k, 且满足 0 ≤ i < j < k ≤ n-1,


使得 arr[i] < arr[j] < arr[k] ,返回 true ; 否则返回 false 。

说明: 要求算法的时间复杂度为 O(n),空间复杂度为 O(1) 。

示例 1:

1
2
输入: [1,2,3,4,5]
输出: true

示例 2:

1
2
输入: [5,4,3,2,1]
输出: false

Solution

维护一个最大值,最小值的二元组,然后记录下前 i 个元素前的最小递增二元组,当存在元素大于最大值后即为 true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution:
def increasingTriplet(self, nums):
"""
:type nums: List[int]
:rtype: bool
"""
max_value = float('inf')
min_value = float('inf')
for num in nums:
if num > max_value:
return True
elif num <= max_value and num > min_value:
max_value = num
else:
min_value = num
return False

给定一个字符串,找出不含有重复字符的最长子串的长度。

示例 1:

1
2
3
输入: "abcabcbb"
输出: 3
解释: 无重复字符的最长子串是 "abc",其长度为 3。

示例 2:

1
2
3
输入: "bbbbb"
输出: 1
解释: 无重复字符的最长子串是 "b",其长度为 1。

示例 3:

1
2
3
4
输入: "pwwkew"
输出: 3
解释: 无重复字符的最长子串是 "wke",其长度为 3。
请注意,答案必须是一个子串,"pwke" 是一个子序列 而不是子串。

Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution:
def lengthOfLongestSubstring(self, s):
"""
:type s: str
:rtype: int
"""
if len(s) == 0:
return 0

max_len = 1
store_str = ""

for strs in s:
if len(store_str) == 0:
store_str += strs
elif strs not in store_str:
store_str += strs
max_len = max(max_len,len(store_str))
else:
repeat_index = store_str.index(strs)
store_str = store_str[repeat_index+1:] + strs
return max_len

给定一个 m x n 的矩阵,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请使用原地算法。

示例 1:

输入:


[


    [1,1,1],


    [1,0,1],


    [1,1,1]


]


输出:


[


    [1,0,1],


    [0,0,0],


    [1,0,1]


]

示例 2:

输入:


[


    [0,1,2,0],


    [3,4,5,2],


    [1,3,1,5]


]


输出:


[


    [0,0,0,0],


    [0,4,5,0],


    [0,3,1,0]


]

进阶:

一个直接的解决方案是使用 O(mn) 的额外空间,但这并不是一个好的解决方案。
一个简单的改进方案是使用 O(m + n) 的额外空间,但这仍然不是最好的解决方案。
你能想出一个常数空间的解决方案吗?

Solution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution:
def setZeroes(self, matrix):
"""
:type matrix: List[List[int]]
:rtype: void Do not return anything, modify matrix in-place instead.
"""
zero_pos_list = []

matrix_row_len = len(matrix)
matrix_col_len = len(matrix[0])

for row in range(matrix_row_len):
for col in range(matrix_col_len):
if matrix[row][col] == 0:
zero_pos_list.append([row,col])

for row,col in zero_pos_list:
for index in range(matrix_col_len):
matrix[row][index] = 0
for index in range(matrix_row_len):
matrix[index][col] = 0

O(1) 的解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import itertools

class Solution:
def setZeroes(self, matrix):
"""
:type matrix: List[List[int]]
:rtype: void Do not return anything, modify matrix in-place instead.
"""
if not matrix:
return

R, C = len(matrix), len(matrix[0])

# First pass: Set rows/cols to infinity when you encounter 0
for r, c in itertools.product(range(R), range(C)):
if matrix[r][c] == 0:
for i in range(R):
if i == r or matrix[i][c] == 0:
continue
matrix[i][c] = float('inf')

for j in range(C):
if j == c or matrix[r][j] == 0:
continue
matrix[r][j] = float('inf')

# Second pass: Replace all infinity with 0
for r, c in itertools.product(range(R), range(C)):
if matrix[r][c] == float('inf'):
matrix[r][c] = 0

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]

Solution

思路很简单先计算两数之和,然后再找第三个数是否存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution:
def threeSum(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
nums_hash = {}
result = list()
for num in nums:
nums_hash[num] = nums_hash.get(num, 0) + 1
if 0 in nums_hash and nums_hash[0] >= 3:
result.append([0, 0, 0])

neg = list(filter(lambda x: x < 0, nums_hash))
pos = list(filter(lambda x: x>= 0, nums_hash))

for i in neg:
for j in pos:
dif = 0 - i - j
if dif in nums_hash:
if dif in (i, j) and nums_hash[dif] >= 2:
result.append([i, j, dif])
if dif < i or dif > j:
result.append([i, j, dif])
return result

前言

用过 flask 的人肯定对 jinjia2 不会陌生,自然对 Jinjia2 自带的 filter 也有印象,但是其自带的 filter 实在有限,有些时候就不得不需要我们自己定义 filter 了,接下来的例子将介绍如何自定义 filter

自定义 filter

举个例子

自定义 filter 其实很简单,写一个函数实现你需要的功能,然后添加到 jinjia_env 中,这里以一个返回列表长度到自定义函数为例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# test.py
from flask import Flask
from flask import render_template

app = Flask(__name__)

def my_len(arg):
return len(arg)

env = app.jinja_env
env.filters['my_len'] = my_len

@app.route("/")
def index():
test_str = "hello"
return render_template("index.html", test_str=test_str)

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

ok 函数定义完了,接下来就是在模版中去使用了.

1
2
3
4
5
6
7
8
9
10
11
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>jinjia2模板测试</title>
</head>
<body>
<h1>len: {{test_str|my_len}}</h1>
</body>
</html>

简单吧?到这里似乎就可以结束了?没问题,但是实际开发中大家很多人的目录结构都不是这样吧,在文件里有着为了优雅,有着一些
xxx.init_app(app) 的代码吧?所以接下来让我们试着把这些 filter 集成一下。

让 filter 优雅起来

尝试将这些 filter 封装到一个类中并且也实现 init_app 方法

1
2
3
4
5
6
7
# examples.py
class Example:
@staticmethod
def init_app(app):
@app.template_filter('my_len')
def my_len(arg):
return len(arg)

然后就可以在其他文件中像这样子使用了

1
2
3
4
5
6
from examples import Example
example = Example()

# ... 省略部分代码
def create_app(app):
example.init_app(app)

题外话 —— 自定义 function

有时候我们需要一个函数处理数据返回结果使得我们可以进行下一步操作,这个时候就需要用到 jinjia2 的自定义函数。

自定义 function

自定义函数的方法很简单,通过 add_template_global 即可

1
2
3
4
5
6
7
8
9
10
11
12
# 第一种写法
def my_len(arg):
return len(arg)

app.add_template_global(my_len,'my_len')

# 第二种写法
@app.template_global('my_len')
def my_len(arg):
return len(arg)

# 同样可以参照 filter 中封装到类中去

在模板中调用方法也与 filter 类似。

1
2
3
4
5
6
7
8
9
10
11
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>jinjia2模板测试</title>
</head>
<body>
<h1>len: {{my_len(test_str)}}</h1>
</body>
</html>

用法很简单,那么问题来了,这两者有什么区别吗?

Jinjia2 的 function 和 filter 的区别

区别很简单,相较于 function 来说, filter 拥有访问上下文以及环境的能力,当然为了解决这个问题,jinjia2 有 contextfilter 等相关函数。

参考链接

Jinjia2

Difference between jinja2 functions and filters?

前言

年纪大了就容易忘事,一转眼就把自己的服务器上 MySQL 的 root 密码给忘记了,几经周折最后把密码找回来了,准确的说是把密码重新设置了一下。

解决办法

Mysql 版本 5.7.23

修改 mysql 的配置文件

在命令下输入如下命令:

1
sudo nano /etc/mysql/mysql.cnf

然后添加如下配置,网上都说在 [mysqld] 下面添加,我的配置文件什么都没有,我只好手动加了:

1
2
[mysqld]
skip-grant-tables

上面那个参数的意思是:在启动 mysql 时不启动 grant-tables (授权表),不启用权限表会发生什么呢?接着看下去

重启 Mysql

修改完配置文件,为了让配置文件生效,自然需要我们重启一下 mysql 服务了

进入 Mysql

what? 不是忘记密码了吗?怎么进入 mysql ? 不要急,在命令行上输入下述命令:

1
mysql

然后回车,你会发现你竟然进入到熟悉的 mysql 界面了,这就是上面那个参数的作用,成功进入界面,接下来就该重设密码了。

重设 root 密码

按照下述命令,重设 root 密码

1
2
3
>use mysql;
>update user set authentication_string=password('new_password') WHERE User='root';
>exit;

最后不要忘记把配置文件的配置注释掉,然后重启 Mysql。

前言

最近用 pyqt5 开发了一款图形化界面工具,完成之后用 pyinstaller 将其打包成 exe 后出现了问题:

ModuleNotFoundError: No module named ‘PyQt5.sip’

解决方法

通过在网上查找一些资料,找到三种办法,网上都有人说成功了.

方法一

安装旧版本 pyqt5,原因是: pyinstaller 内置的 sip 版本与最新的 pyqt5 不兼容

1
pip install pyqt5=5.10.1

方法二

安装新的 sip

1
pip install -U sip 

方法三

在程序中引入 sip

1
2
# your code
from PyQt5 import sip

第一种方法,成功的解决了我的问题,剩下的两种我尝试了都没有成功

前言

最近用 pyqt5 开发了一款图形化界面工具,完成之后用 pyinstaller 将其打包成 exe 后出现了问题:

ModuleNotFoundError: No module named ‘PyQt5.sip’

解决方法

通过在网上查找一些资料,找到三种办法,网上都有人说成功了.

方法一

安装旧版本 pyqt5,原因是: pyinstaller 内置的 sip 版本与最新的 pyqt5 不兼容

1
pip install pyqt5=5.10.1

方法二

安装新的 sip

1
pip install -U sip 

方法三

在程序中引入 sip

1
2
# your code
from PyQt5 import sip

第一种方法,成功的解决了我的问题,剩下的两种我尝试了都没有成功

关于 Ubuntu 下 FTP 的常用配置这里就直接跳过,不了解的可以点击 Ubuntu 下安装和配置 FTP 详解 链接了解一下。这里重点说一下单用户多目录。

正常情况下一个 FTP 用户只能访问一个目录以及该目录下的所有文件,也就是单用户单目录。如何做到单用户多目录呢,先说一下解决办法:

解决办法 – mount

使用 mount 命令,具体使用方法,让我们通过一个例子说明。

我有一个项目目录,

1
2
3
4
5
6
7
8
|———api
├── assets
├── components
├── router
├── store
├── styles
├── utils
└── views

这是项目的目录结构,由于某种需求需要,我希望创建一个 FTP 用户只访问 api views目录。

如何实现这个效果呢?

  1. 在其他目录下创建另外的文件夹,这里我们创建一个 example 文件夹
  2. 在该文件夹下创建 apiviews 文件夹
  3. 将目标文件夹的挂载到对应的文件夹下
    

整个操作下涉及到的所有命令行:

1
2
3
4
5
6
mkdir example
cd example
mkdir api
mkdir views
mount --bind /pathto/source/api/ /pathto/example/api/
mount --bind /pathto/source/views/ /pathto/example/views/

然后指定相应的 FTP 用户的根目录为 example就大功告成了。