有一个类似的示例:
有两个文件app.py、test1.py.
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 省略import
# jwt
app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = "super-secret" # Change this!
jwt = JWTManager(app)
@app.route('/')
def index():
return 'hello flask app'
@app.route('/protected', methods=['GET'])
@jwt_required()
def protected():
return 'hello protected'
import test1
from werkzeug.serving import run_simple
if __name__ == '__main__':
# 启动参数
run_simple('0.0.0.0', 7070, app, threaded=True)
test1.py
1
2
3
4
5
6
7
8
9
from app import app
from flask_jwt_extended import jwt_required, verify_jwt_in_request
@app.route('/test1')
@jwt_required()
def test1():
print("enter test1")
return "test1"
执行python app.py,”/test1”的url能注册进去吗,答案是不能。
为什么呢。
我们加上调试信息:
app.py
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
31
32
33
34
35
36
37
38
39
from flask import Flask
from flask_jwt_extended import JWTManager
from flask_jwt_extended import jwt_required
# jwt
print("create app")
app = Flask(__name__)
app.config["JWT_SECRET_KEY"] = "super-secret" # Change this!
jwt = JWTManager(app)
@app.route('/')
def index():
return 'hello flask app'
@app.route('/protected', methods=['GET'])
@jwt_required()
def protected():
return 'hello protected'
print("before import test1")
import sys
if "app" in sys.modules:
print("app get app")
if hasattr(sys.modules["app"], "app"):
print("app get app")
print(sys.modules["app"].app)
import test1
print("after import test1")
from werkzeug.serving import run_simple
if __name__ == '__main__':
# 启动参数
run_simple('0.0.0.0', 7070, app, threaded=True)
test1.py
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
print("start init test1")
import sys
if "app" in sys.modules:
print("test1 get app")
if hasattr(sys.modules["app"], "app"):
print("test1 get app")
print(sys.modules["app"].app)
from app import app
from flask_jwt_extended import jwt_required, verify_jwt_in_request
@app.route('/test1')
@jwt_required()
def test1():
print("enter test1")
return "test1"
@app.route('/test2')
def protected1():
print("add url protected1")
verify_jwt_in_request()
return "protected1"
print("end init test1")
同时修改app.route的源码:
1
2
3
4
5
6
7
8
9
def route(self, rule: str, **options: t.Any) -> t.Callable:
def decorator(f: t.Callable) -> t.Callable:
endpoint = options.pop("endpoint", None)
print(f"add url into app { f.__name__}")
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
调试信息如下:(add url)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PS C:\Users\nfuser\workspace\test_flask> python .\app.py
**create app**
add url into app index
add url into app protected
before import test1
start init test1
**create app**
add url into app index
add url into app protected
before import test1
app get app
app get app
<Flask 'app'>
after import test1
add url into app test1
add url into app protected1
end init test1
after import test1
分析调用过程:
- 首先进入app.py,初始化app,可以看到第一条语句”create app”,然后是注册app中url,然后导入test1,这个时候在app中是不包含app的module的,这是当然的,app.py里也没有import app。
- 进入test1,这个时候碰到第一句
from app import app,按照Python执行流程和Module理解 总结的module导入规则,就是在sys.modules里面去找这个模块,如果找不到,就实例化这个module模块的对象,然后从新的这个对象中,取出import进来的对象。这里可以看到,test1中判断是没有app的module的。因此,在start init test1以后,继续进入了app.py文件,又输出了一个create app!相当于是把app.py又执行了一次。可以看到后面的add url又出现了一次,import test1也是。但这里不会循环引用,因为app中也没有引用test1的内容,只是import,发现已经import过了(import会先添加到sys.module里面,然后再执行,可以看到导入app里面,已经出现了app get app)就会跳过。这个时候算是完成了from app import app这句话。 - 前面重点是,test1中取得的app,是test1中重新初始化一次的app!和app.py里的app不是一个。那之后的add url,也是加到这个新的app中。而在main里面,我们启动的是app.py里的py,因此,显然我们无法从浏览器访问到test1这个url。(但是如果在test1中,app.run,这个是能访问到所有url的)
怎么办呢?
从app.py中拆分一个run.py出来。
1
2
3
4
5
6
7
8
from app import app
# run web server
from werkzeug.serving import run_simple
if __name__ == '__main__':
# 启动参数
run_simple('0.0.0.0', 7070, app, threaded=True)
这是因为为什么不对,就是test1中重新实例化一个app,而为什么重新实例化一个app,是因为app不存在sys.module里面,因此只需要在调用test1时,app已经被人导入过就好办了。因此,应该将main拆分到一个单独的文件中。
可以看看新的调试信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PS C:\Users\nfuser\workspace\test_flask> python .\run.py
**create app**
add url into app index
add url into app protected
before import test1
app get app
app get app
<Flask 'app'>
start init test1
test1 get app
test1 get app
<Flask 'app'>
add url into app test1
add url into app protected1
end init test1
after import test1
这个时候只有一个create app 了,同时这次app里直接就输出了app get app .
启示:
这个示例有好几个重要的作用:
- 一是深入理解python的执行过程,以及module导入的本质概念。
- 不要导入作为main的包,会重复执行。作为替代,分解到单独的、简单的一个py中。
- 所有module都导入到sys.module中,重复被后续所使用。即一个包只有被运行一次。
- 根据上一点,这就能够借助monkey patch,先导入包的地方,对包进行动态变更,则后续再导入这个包的地方都会使用到变更后的函数。这就是动态语言的魔性。
扩展:
- 虽然用的是同一份包的内容,但要注意的是,导入到当前module时,会包含在当前module的命名空间中
- 因此如果使用monkey patch在这里直接修改导入后的对象是没用的,该对象仅作用于当前空间,需要使用导入包.对象去访问唯一的对象。
1
2
3
4
5
6
7
8
9
oidc_auth = False
def optional_jwt_required(fresh=False, refresh=False, locations=None):
return original_jwt_required(optional=True, fresh=fresh, refresh=refresh, locations=locations)
if not oidc_auth:
logger.warning("unauth now")
jwt_required = optional_jwt_required
flask_jwt_extended.jwt_required = optional_jwt_required
这里对于jwt_required这个对象,只有当前文件使用,修改这个对外界是没有影响的,除非其他地方import该文件的jwt_required,相反,后面对于flask_jwt_extended.jwt_required,是全局通用的。后续的所有地方去import flask_jwt_extended.jwt_required都会使用变化后的函数。