Flask 框架

时间:2023-03-09 05:40:18
Flask 框架

装饰器知识回顾

http://www.cnblogs.com/0bug/p/7978595.html

普通装饰器格式:

def wrapper(func):
def inner(*args, **kwargs):
return func(*args, **kwargs) return inner @wrapper # index = wrapper(index)
def index(request):
pass

带参数的装饰器

def d(a=None):  # 定义一个外层函数,给装饰器传参数a默认是None
def foo(func): # foo是我们原来的装饰器函数,func是被装饰的函数
def bar(*args, **kwargs): # args和kwargs是被装饰器函数的参数
# 根据装饰器的参数做一些逻辑判断
if a:
print("{}您好".format(a))
else:
print("您好")
# 调用被装饰的函数,接收参数args和kwargs
func(*args, **kwargs) return bar return foo @d() # 不给装饰器传参数,使用默认的'None'参数
def wish(name):
print("{}祝您前程似锦".format(name)) @d("亲爱的园友") # 给装饰器传一个参数
def greet_wish(name):
print("{}祝您前程似锦".format(name)) if __name__ == '__main__':
wish("lcg")
print('-' * 50)
greet_wish("lcg")

输出

您好
lcg祝您前程似锦
--------------------------------------------------
亲爱的园友您好
lcg祝您前程似锦

flask路由本质就是带参数的装饰器:

@web.route('/index')
def index():
return 'Index' def route(self, rule, **options):
def decorator(f):
endpoint = options.pop("endpoint", f.__name__)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator

Flask,Django,Tornado三大框架对比

Django:内部包含了非常多的组件比如:orm,form,modelForm,缓存,session等等。

Flask:短小精悍,内部没有包含多少组件,但是第三方的组件是非常丰富的。路由比较特殊:基于装饰器来实现,但是究其本质还是通过add_url_rule来实现

Tornado:是异步非阻塞框架,node.js也是异步非阻塞。

初识Flask

安装:pip install flask

模板引擎则使用Jinjia2

Flask使用BSD授权

WSGI工具箱采用Werkzeug

Werkzeug示例(Flask的本质):

from werkzeug.wrappers import Request, Response
from werkzeug.serving import run_simple @Request.application
def hello(request):
return Response('Hello World!') if __name__ == '__main__':
run_simple('localhost', 4000, hello)

Flask 框架

wsgiref示例:

from wsgiref.simple_server import make_server

def runserver(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [bytes('<h1>Hello, web!</h1>', encoding='utf-8'), ] if __name__ == '__main__':
httpd = make_server('', 8000, runserver)
httpd.serve_forever()

Flask 框架

上面的Werkzeug和wsgiref的本质也都是基于Socket实现的。

一个flask示例:

from flask import Flask

# 实例化Flask对象
app = Flask(__name__) # 将 '/' 和 函数index的对应关系添加到路由中。
"""
{
‘/’:index
}
"""
@app.route('/')
def index():
return 'Hello World!' if __name__ == '__main__':
# 监听用户请求
# 如果有用户请求到来,则执行app的__call__()方法
# app.__call__()
app.run()

上面的代码进行源码分析:

app=Flask(__name__),实例化Flask对象。

然后通过装饰器将 '/' 和 函数index的对应关系添加到路由中。

app.run()源码中 run_simple(host, port, self, **options),其中self就是Flask的实例化对象,而app就是Flask()的实例化对象,从上面的Werkzeug示例中我们看到run_simple的第三个参数就是一个方法(函数),那么这里就相当于app(),而对象加括号就相当于执行类对象的__call__()方法。即是执行了Flask类的实例化对象也就是app对象的__call__()方法。__call__()方法中的具体细节,后面在做探讨。

    def __call__(self, environ, start_response):
"""Shortcut for :attr:`wsgi_app`."""
return self.wsgi_app(environ, start_response)

Flask快速入门,实现登录认证

Flask 框架

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

app = Flask(__name__)
app.debug = True
app.secret_key = 'lcgasdasdadhahah' USERS = {
: {'name': '漩涡鸣人', 'age': , 'gender': '男', 'text': '我一直都是说道做到的,因为这就是我的忍道'},
: {'name': '藤原佐为', 'age': , 'gender': '男', 'text': '我只知道自己执黑子时从来没输过!'},
: {'name': '李舜生', 'age': , 'gender': '女', 'text': '总有一天我会撕去这满是虚假的星空'}} def wrapper(func):
def inner(*args, **kwargs):
if not session.get("user_info"):
return redirect(url_for('login'))
ret = func(*args, **kwargs)
return ret return inner @app.route('/login', methods=['GET', 'POST'], endpoint='login')
def login():
if request.method == "GET":
return render_template('login.html')
else:
user = request.form.get('user')
pwd = request.form.get('pwd')
if user == 'lcg' and pwd == '':
session['user_info'] = user
return redirect(url_for('index'))
return render_template('login.html', error='用户名或密码错误') @app.route('/detail/<int:nid>', methods=['GET', 'POST'], endpoint='detail')
@wrapper
def detail(nid):
info = USERS.get(nid)
return render_template('detail.html', info=info) @app.route('/index', methods=['GET'], endpoint='index')
@wrapper
def index():
return render_template('index.html', user_dict=USERS) if __name__ == '__main__':
app.run()

demo.py

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户登录</h1>
<form method="post">
<input type="text" name="user">
<input type="text" name="pwd">
<input type="submit" value="登录">{{error}}
</form>
</body>
</html>

templates/login.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户列表</h1>
<table>
{% for k,v in user_dict.items() %}
<tr>
<td>{{k}}</td>
<td>{{v.name}}</td>
<td>{{v['name']}}</td>
<td>{{v.get('name')}}</td>
<td><a href="/detail/{{k}}">查看详细</a></td>
</tr>
{% endfor %}
</table>
</body>
</html>

templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>详细信息 {{info.name}}</h1>
<div>
{{info.text}}
</div>
</body>
</html>

templates/index.html

配置文件

flask中的配置文件是一个flask.config.Config对象(继承字典),默认配置为:
{
'DEBUG': get_debug_flag(default=False), 是否开启Debug模式
'TESTING': False, 是否开启测试模式
'PROPAGATE_EXCEPTIONS': None,
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
'SECRET_KEY': None,
'PERMANENT_SESSION_LIFETIME': timedelta(days=31),
'USE_X_SENDFILE': False,
'LOGGER_NAME': None,
'LOGGER_HANDLER_POLICY': 'always',
'SERVER_NAME': None,
'APPLICATION_ROOT': None,
'SESSION_COOKIE_NAME': 'session',
'SESSION_COOKIE_DOMAIN': None,
'SESSION_COOKIE_PATH': None,
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SECURE': False,
'SESSION_REFRESH_EACH_REQUEST': True,
'MAX_CONTENT_LENGTH': None,
'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12),
'TRAP_BAD_REQUEST_ERRORS': False,
'TRAP_HTTP_EXCEPTIONS': False,
'EXPLAIN_TEMPLATE_LOADING': False,
'PREFERRED_URL_SCHEME': 'http',
'JSON_AS_ASCII': True,
'JSON_SORT_KEYS': True,
'JSONIFY_PRETTYPRINT_REGULAR': True,
'JSONIFY_MIMETYPE': 'application/json',
'TEMPLATES_AUTO_RELOAD': None,
}

方式一:

app.config['DEBUG'] = True

PS: 由于Config对象本质上是字典,所以还可以使用app.config.update(...)

方式二:

app.config.from_pyfile("python文件名称")

如:
settings.py
DEBUG = True app.config.from_pyfile("settings.py") app.config.from_envvar("环境变量名称")
环境变量的值为python文件名称名称,内部调用from_pyfile方法 app.config.from_json("json文件名称")
JSON文件名称,必须是json格式,因为内部会执行json.loads app.config.from_mapping({'DEBUG': True}) # 字典格式

方式三(最常用的):

app.config.from_object("python类或类的路径")

如:
app.config.from_object('pro_flask.settings.TestingConfig') settings.py中: class Config(object):
DEBUG = False
TESTING = False
DATABASE_URI = 'sqlite://:memory:' class ProductionConfig(Config):
DATABASE_URI = 'mysql://user@localhost/foo' class DevelopmentConfig(Config):
DEBUG = True class TestingConfig(Config):
TESTING = True PS: 从sys.path中已经存在路径开始写 PS: settings.py文件默认路径要放在程序root_path目录,
如果instance_relative_config为True,则就是instance_path目录

路由系统

路由比较特殊,:是基于装饰器实现的,但是究其本质还是有add_url_rule实现的。

@app.route('/user/<username>')

@app.route('/post/<int:post_id>')

@app.route('/post/<float:post_id>')

@app.route('/post/<path:path>')

@app.route('/login', methods=['GET', 'POST'], endpoint='login')

常用路由系统有以上五种,所有的路由系统都是基于以下对应关系来处理(可以扩展以正则表达式,需继承BaseConverter并实现相应方法):

DEFAULT_CONVERTERS = {
'default': UnicodeConverter,
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid': UUIDConverter,
}
from flask import Flask, url_for
from werkzeug.routing import BaseConverter app = Flask(import_name=__name__) class RegexConverter(BaseConverter):
"""
自定义URL匹配正则表达式
""" def __init__(self, map, regex):
super(RegexConverter, self).__init__(map)
self.regex = regex def to_python(self, value):
"""
路由匹配时,匹配成功后传递给视图函数中参数的值
"""
return int(value) # 将字符串转换为int类型 def to_url(self, value):
"""
使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数
"""
val = super(RegexConverter, self).to_url(value)
return val # 添加到flask中
app.url_map.converters['regex'] = RegexConverter @app.route('/index/<regex("\d+"):nid>')
def index(nid):
print(url_for('index', nid=nid))
return 'Index' if __name__ == '__main__':
app.run()

自定制路由扩展,对正则表达式的支持

上面的扩展正则的类要继承BaseConverter,并且需要实现to_python方法,to_python方法在路由配合成功传递给函数之前,在此中间执行,进行了一次封装,to_url是在使用url_for反向生成URL时,经过该函数处理,返回的值用于生成URL的参数。

路由示例:

from flask import Flask

app = Flask(__name__)

def auth(func):
def inner(*args, **kwargs):
print('before')
result = func(*args, **kwargs)
print('after')
return result return inner @app.route('/index', methods=['GET', 'POST'], endpoint='index')
@auth
def index():
print('index')
return 'Index' if __name__ == '__main__':
app.run()

装饰器的位置,放在router后面

路由的本质

def route(self, rule, **options):
def decorator(f):
endpoint = options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f return decorator @setupmethod
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
options['endpoint'] = endpoint
methods = options.pop('methods', None) if methods is None:
methods = getattr(view_func, 'methods', None) or ('GET',)
if isinstance(methods, string_types):
raise TypeError('Allowed methods have to be iterables of strings, '
'for example: @app.route(..., methods=["POST"])')
methods = set(item.upper() for item in methods) required_methods = set(getattr(view_func, 'required_methods', ())) provide_automatic_options = getattr(view_func,
'provide_automatic_options', None) if provide_automatic_options is None:
if 'OPTIONS' not in methods:
provide_automatic_options = True
required_methods.add('OPTIONS')
else:
provide_automatic_options = False methods |= required_methods rule = self.url_rule_class(rule, methods=methods, **options)
rule.provide_automatic_options = provide_automatic_options self.url_map.add(rule)
if view_func is not None:
old_func = self.view_functions.get(endpoint)
if old_func is not None and old_func != view_func:
raise AssertionError('View function mapping is overwriting an '
'existing endpoint function: %s' % endpoint)
self.view_functions[endpoint] = view_func

部分源码

@app.route('/index', methods=["GET", "POST"], endpoint='index')
def index():
return "Hello Index"

endpoint是别名,不写别名的话。别名默认就是被装饰的函数名。根据源码分析,也可以写成下面的形式(这样就跟Django的路由很像了):

def index():
return "Hello Index" app.add_url_rule('/index', 'index', index, methods=["GET", "POST"]) # url,别名,函数名,方法

Flask的CBV写法

Flask中的CBV原理也是反射,跟Django的CBV类似。Flask里面继承views.MethodView或者继承views.View然后自定义dispatch_request方法。

from flask import Flask, views

app = Flask(__name__)

def auth(func):
def inner(*args, **kwargs):
print('before')
result = func(*args, **kwargs)
print('after')
return result return inner def auth2(func):
def inner(*args, **kwargs):
print('before2')
result = func(*args, **kwargs)
print('after2')
return result return inner class IndexView(views.MethodView):
methods = ['GET']
decorators = [auth, auth2] # 装饰器的用法 decorators=[装饰器名,] def get(self):
return 'Index.GET' def post(self):
return 'Index.POST' app.add_url_rule('/index', view_func=IndexView.as_view(name='index')) # name是别名 if __name__ == '__main__':
app.run()

CBV+两个装饰器修饰

from flask import Flask, views

app = Flask(__name__)

class IndexView(views.View):
methods = ['GET'] def dispatch_request(self):
return 'Index!' app.add_url_rule('/index', view_func=IndexView.as_view(name='index')) # name是别名 if __name__ == '__main__':
app.run()

CBV继承View里面自定义dispatch_request

@app.route和app.add_url_rule参数:

def route(self, rule, **options):pass

def add_url_rule(self, rule, endpoint=None, view_func=None, **options):pass

rule, URL规则

view_func, 视图函数名称

defaults = None, 默认值, 当URL中无参数,函数需要参数时,使用defaults = {'k': 'v'}为函数提供参数

endpoint = None, 别名,用于反向生成URL,即: url_for('别名')

methods = None, 允许的请求方式,如:["GET", "POST"]

strict_slashes = None, 对URL最后的 / 符号是否严格要求,
如:
@app.route('/index', strict_slashes=False)
访问 http://www.xx.com/ndex/ 或 http://www.xx.com/index 均可 @app.route('/index', strict_slashes=True)
仅可访问 http://www.xx.com/index redirect_to = None, 重定向到指定地址(一般公司换网址了,旧网站也可能被顾客访问)
如:
@app.route('/index/<int:nid>', redirect_to='/home/<nid>')

def func(adapter, nid):
return "/home/888"
@app.route('/index/<int:nid>', redirect_to=func) subdomain = None, 子域名访问
from flask import Flask, views, url_for app = Flask(import_name=__name__)
app.config['SERVER_NAME'] = 'lcg.com:5000' # SERVER_NAME是固定的写法 @app.route("/", subdomain="admin")
def static_index():
"""Flask supports static subdomains
This is available at static.your-domain.tld"""
return "static.your-domain.tld" @app.route("/dynamic", subdomain="<name>")
def username_index(name):
"""Dynamic subdomains are also supported
Try going to user1.your-domain.tld/dynamic"""
return name + ".your-domain.tld"

模板语言

1.Flask使用的是Jinja2模板,所以其语法大部分和Django类似,Django的模板语法:http://www.cnblogs.com/0bug/p/7993588.html

2、自定义模板方法

创建一个函数并通过参数的形式传入render_template,渲染HTML标签,如:

from flask import Flask, render_template

app = Flask(__name__)

def hello():
return '<h1>hello lcg</h1>' @app.route('/test', methods=['GET', 'POST'])
def test():
return render_template('test.html', ww=hello) if __name__ == '__main__':
app.run() templates/test.html {{ww()|safe}}

方式一, |safe

from flask import Flask, render_template, Markup

app = Flask(__name__)

def hello():
return Markup('<h1>hello lcg</h1>') @app.route('/test', methods=['GET', 'POST'])
def test():
return render_template('test.html', ww=hello) if __name__ == '__main__':
app.run() templates/test.html
{{ww()}}

方式二,Markup(类似Django中的mark_safe)

3.宏定义

<body>

{% macro func(name, type='text', value='') %}
<input type="{{ type }}" name="{{ name }}1" value="{{ value }}">
<input type="{{ type }}" name="{{ name }}2" value="{{ value }}">
<input type="{{ type }}" name="{{ name }}3" value="{{ value }}">
<input type="{{ type }}" name="{{ name }}4" value="{{ value }}">
{% endmacro %} {{ func('lcg') }} </body>

Flask 框架

请求和响应相关

from flask import Flask
from flask import request
from flask import render_template
from flask import redirect
from flask import make_response
from flask import jsonify app = Flask(__name__) @app.route('/test', methods=['GET', "POST"])
def login():
# 请求相关信息
# request.method
# request.args
# request.form
# request.values
# request.cookies
# request.headers
# request.path
# request.full_path
# request.script_root
# request.url
# request.base_url
# request.url_root
# request.host_url
# request.host
# request.files
# obj = request.files['the_file_name']
# obj.save('/var/www/uploads/' + secure_filename(obj.filename)) # 响应相关信息
# return "字符串"
# return render_template('html模板路径',**{})
# return redirect('/index.html') # response = make_response('hello lcg')
# response = make_response(render_template('index.html'))
# response是flask.wrappers.Response类型
# response.set_cookie('key', 'value') # 加cookie
# response.delete_cookie('key')
# response.headers['X-Something'] = 'A value'
# return response return "内容" if __name__ == '__main__':
app.run()

--

Session

除请求对象之外,还有一个 session 对象。它允许你在不同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名要使用会话,你需要设置一个密钥。

  • 设置:session['username'] = 'xxx'

  • 删除:session.pop('username', None)
from flask import Flask, session, redirect, url_for, escape, request

app = Flask(__name__)
# set the secret key. keep this really secret:
app.secret_key = 'dsaba sca6789pinkNa:xL C[N' @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 action="" 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'))
if __name__ == '__main__':
app.run()

Session的基本用法

闪现,flash&get_flashed_messages

用途:对临时数据操作,如显示错误信息。

from flask import Flask, flash, get_flashed_messages

app = Flask(__name__)

app.secret_key = 'wertyuhgfdrtyujnbvghuij'  # flash操作需要添加 secret_key

@app.route('/set')
def setflash():
flash('世界感受痛苦吧') # 向某个地方设置(flash)一个值
return 'Hello World!' @app.route('/get')
def getflash():
data = get_flashed_messages() # 从某个地方获取(flash)设置过的值,并清除
print(data)
return 'Hello World!' if __name__ == '__main__':
app.run()

请求的扩展(类似Django中间件)

@app.before_request         每次请求进来之前执行
@app.after_request 每次请求进来之后执行
@app.errorhandler(404) 引发404错误时候执行
@app.before_first_request 第一次请求进来之前执行(如创建数据库连接池这样的业务)
@app.template_global() 调用方法{{函数名(参数1,参数2)}}
@app.template_filter() 调用方法{{参数1|函数名(参数2,参数3))}}

示例

from flask import Flask, Request, render_template

app = Flask(__name__, template_folder='templates')
app.debug = True @app.before_first_request
def before_first_request1():
print('before_first_request1') @app.before_first_request
def before_first_request2():
print('before_first_request2') @app.before_request
def process_request1():
print('process_request1') @app.before_request
def process_request2():
print('process_request2') @app.after_request
def process_response1(response):
print('process_response1', response)
return response @app.after_request
def process_response2(response):
print('process_response2', response)
return response @app.errorhandler(404)
def page_not_found(error):
return 'This page does not exist', 404 @app.template_global()
def global_func(a1, a2):
return a1 + a2 @app.template_filter()
def filter_func(a1, a2, a3):
return a1 + a2 + a3 @app.route('/')
def hello_world():
return render_template('hello.html') if __name__ == '__main__':
app.run() '''
templates/hello.html <body> {{global_func(1,2)}} {{1|filter_func(2,3)}} </body>
'''

Flask 框架
Flask 框架

在process_request1处用return拦截:

@app.before_request
def process_request1():
print('process_request1')
return '被process_request1拦截'

Flask 框架

Flask 框架

访问错误,引发异常的时候执行相关的@app.errorhandler(错误码)修饰的函数:

Flask 框架

Flask 框架

用户认证用请求扩展的方式替代之前的装饰器(类似Django的中间件的用法):

@app.before_request
def process_request(*args, **kwargs):
if request.path == url_for('login'):
return None
if session.get("user_info"):
return None
return redirect(url_for('login'))

Flask的中间件

前面的初识flask那里已经介绍了,请求入口在app.run(), 实质上是执行run_simple(host, port, self, **options),这个run_simple就是Flask的WSGI工具箱Werkzeug中的run_simple的,它的第三个参数是一个加括号的也就是self(),也就是app()。而app又是Flask类的实例化对象,对象加扩至,执行的是对象的__call__()方法。我们在源码中就可以找到def __call__(self, environ, start_response):return self.wsgi_app(environ, start_response),中间件的原理就在这,用面向对象的思想重写__call__()方法:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
return 'hello lcg' class MiddleWare:
def __init__(self, wsgi_app):
self.wsgi_app = wsgi_app def __call__(self, environ, start_response):
print('请求开始之前')
ret = self.wsgi_app(environ, start_response)
print('请求结束之后')
return ret if __name__ == "__main__":
app.wsgi_app = MiddleWare(app.wsgi_app)
app.run()

flask中间件写法,重写__call__()方法

蓝图(Blueprint)

蓝图实现了应用的模块化,使用蓝图让应用层次清晰,开发者可以很容易的开发和维护项目。

简单的蓝图:

Flask 框架

from flask import Blueprint
from flask import render_template # 创建一个blueprint对象。第一个参数可看做该blueprint对象的姓名
# 在一个app里,姓名不能与其余的Blueprint对象姓名重复,也不能与函数名重复
# 第二个参数__name__用作初始化
account = Blueprint('account', __name__) @account.route('/login', methods=['GET', "POST"])
def login():
return render_template('login.html')

account.py

from flask import Blueprint

blog_bp = Blueprint('blog_bp', __name__)

@blog_bp.route('/blog', methods=['GET', "POST"])
def blog():
return 'blog'

blog.py

from flask import Blueprint

user_bp = Blueprint('user_bp', __name__)

@user_bp.route('/user', methods=['GET', "POST"])
def user():
return 'user'

user.py

from flask import Flask

app = Flask(__name__, template_folder='templates',
static_folder='statics',
static_url_path='/static') from .views.account import account
from .views.blog import blog_bp
from .views.user import user_bp app.register_blueprint(account, url_prefix='/simple') # 注册蓝图,simple/login
app.register_blueprint(blog_bp, url_prefix='/simple')
app.register_blueprint(user_bp, url_prefix='/simple')

simple_bp/__init__.py

from simple_bp import app

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

run.py

点击下载此演示

复杂的项目(多个应用),蓝图写法:

Flask 框架

from flask import Blueprint

admin = Blueprint(
'admin',
__name__,
template_folder='templates',
static_folder='static'
) from . import views

admin/__init__.py

from . import admin

@admin.route('/index')
def index():
return 'Admin.Index'

admin/views.py

from flask import Blueprint

web = Blueprint(
'web',
__name__,
template_folder='templates',
static_folder='static'
) from . import views

Web/__init__.py

from . import web

@web.route('/index')
def index():
return 'Web.Index'

Web/views.py

from flask import Flask
from .admin import admin
from .web import web app = Flask(__name__)
app.debug = True app.register_blueprint(admin, url_prefix='/admin')
app.register_blueprint(web)

complex_bp/__init__.py

from complex_bp import app

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

complex_bp/run.py

点击下载此演示

上下文管理(Threadlocal)

import threading

class Foo(object):
def __init__(self):
self.name = 0 local_values = Foo() def func(num):
local_values.name = num
import time
time.sleep(1)
print(local_values.name, threading.current_thread().name) for i in range(20):
th = threading.Thread(target=func, args=(i,), name='线程%s' % i)
th.start() 结果:
19 线程0
19 线程2
19 线程1
19 线程3
19 线程7
19 线程6
19 线程5
19 线程4
19 线程11
19 线程10
19 线程9
19 线程8
19 线程14
19 线程13
19 线程12
19 线程16
19 线程15
19 线程19
19 线程18
19 线程17

当多个线程改同一个变量的时候,引发的问题。

import threading

local_values = threading.local()

# threading.local对象,用于为每个线程开辟一块空间来保存它独有的值。

def func(num):
local_values.name = num
import time
time.sleep(1)
print(local_values.name, threading.current_thread().name) for i in range(20):
th = threading.Thread(target=func, args=(i,), name='线程%s' % i)
th.start() 结果:
19 线程0
19 线程2
19 线程1
19 线程3
19 线程7
19 线程6
19 线程5
19 线程4
19 线程11
19 线程10
19 线程9
19 线程8
19 线程14
19 线程13
19 线程12
19 线程16
19 线程15
19 线程19
19 线程18
19 线程17

threading.local对象,用于为每个线程开辟一块空间来保存它独有的值

通过get_ident自定制一个类似threading.local的功能:

import threading
from _thread import get_ident class Local(object):
def __init__(self):
self.storage = {}
self.get_ident = get_ident def set(self, k, v):
ident = self.get_ident() # 获得线程的唯一标识
origin = self.storage.get(ident)
if not origin:
origin = {k: v}
else:
origin[k] = v
self.storage[ident] = origin def get(self, k):
ident = self.get_ident()
origin = self.storage.get(ident)
if not origin:
return None
return origin.get(k, None) local_values = Local() def task(num):
local_values.set('name', num)
import time
time.sleep(1)
print(local_values.get('name'), threading.current_thread().name) for i in range(20):
th = threading.Thread(target=task, args=(i,), name='线程%s' % i)
th.start() 结果:
0 线程0
1 线程1
3 线程3
2 线程2
5 线程5
4 线程4
6 线程6
9 线程9
10 线程10
7 线程7
8 线程8
13 线程13
14 线程14
11 线程11
15 线程15
12 线程12
19 线程19
16 线程16
18 线程18
17 线程17

get_ident()可获取线程唯一标识

获取协程唯一标识,需要先按照gevent :pip install gevent

import threading

try:
from greenlet import getcurrent as get_ident # 协程
except ImportError:
try:
from thread import get_ident
except ImportError:
from _thread import get_ident # 线程 class Local(object):
def __init__(self):
self.storage = {}
self.get_ident = get_ident def set(self, k, v):
ident = self.get_ident() # 获得线程的唯一标识
origin = self.storage.get(ident)
if not origin:
origin = {k: v}
else:
origin[k] = v
self.storage[ident] = origin def get(self, k):
ident = self.get_ident()
origin = self.storage.get(ident)
if not origin:
return None
return origin.get(k, None) local_values = Local() def task(num):
local_values.set('name', num)
import time
time.sleep(1)
print(local_values.get('name'), threading.current_thread().name) for i in range(20):
th = threading.Thread(target=task, args=(i,), name='线程%s' % i)
th.start() 结果:
0 线程0
6 线程6
5 线程5
4 线程4
3 线程3
2 线程2
1 线程1
7 线程7
10 线程10
9 线程9
8 线程8
13 线程13
12 线程12
11 线程11
15 线程15
17 线程17
19 线程19
18 线程18
16 线程16
14 线程14

自定制支持线程+支持协程

import threading

try:
from greenlet import getcurrent as get_ident # 协程
except ImportError:
try:
from thread import get_ident
except ImportError:
from _thread import get_ident # 线程 class Local(object): def __init__(self):
object.__setattr__(self, '__storage__', {})
object.__setattr__(self, '__ident_func__', get_ident) def __getattr__(self, name):
try:
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name) def __setattr__(self, name, value):
ident = self.__ident_func__()
storage = self.__storage__
try:
storage[ident][name] = value
except KeyError:
storage[ident] = {name: value} def __delattr__(self, name):
try:
del self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name) local_values = Local() def task(num):
local_values.name = num
import time
time.sleep(1)
print(local_values.name, threading.current_thread().name) for i in range(20):
th = threading.Thread(target=task, args=(i,), name='线程%s' % i)
th.start() 结果:
0 线程0
8 线程8
6 线程6
9 线程9
5 线程5
4 线程4
2 线程2
7 线程7
3 线程3
1 线程1
12 线程12
11 线程11
14 线程14
10 线程10
13 线程13
19 线程19
17 线程17
16 线程16
15 线程15
18 线程18

按照面向对象思想__getattr__,__setattr__方式优化实现

关于__setattr__与__getattr__的补充:

当使用点号获取实例属性时,如果属性不存在就自动调用__getattr__方法__setattr__当设置类实例属性时自动调用,如j.name=5 就会调用__setattr__方法 self.[name]=5因为这个类是从dict继承来的,,是dict的超类所以 self[attr]=value 相当于调用dict的下标方法,与 a={}   ; a[attr]=value意思一样。

__setattr__

class Foo(object):

    def __setattr__(self, *args, **kwargs):
print('__setattr__ in runing') def __getattr__(self, *args, **kwargs):
print('__getattr__ is runing') obj = Foo()
obj.name = 'lcg'
obj.age = '20' 结果==》》
__setattr__ in runing
__setattr__ in runing

__getattr__

class Foo(object):

    def __setattr__(self, *args, **kwargs):
print('__setattr__ in runing') def __getattr__(self, *args, **kwargs):
print('__getattr__ is runing') obj = Foo()
print(obj.name)
print(obj.age) 结果==》》
__getattr__ is runing
None
__getattr__ is runing
None

这里有一个坑:

class Foo(object):
def __init__(self):
self.name = 'lcg' def __setattr__(self, *args, **kwargs):
self.name = '0bug' # 此处无限递归
print('__setattr__ in runing') def __getattr__(self, *args, **kwargs):
print('__getattr__ is runing') obj = Foo() obj.name = 'lcg' # 引发异常:
# RecursionError: maximum recursion depth exceeded

解决方法:object.__setattr__(self, 'name', 'lcg')代替self.name = 'lcg'

class Foo(object):
def __init__(self):
object.__setattr__(self, 'name', 'lcg') def __setattr__(self, *args, **kwargs):
object.__setattr__(self, 'name', '0bug')
print('__setattr__ in runing') def __getattr__(self, *args, **kwargs):
print('__getattr__ is runing') obj = Foo() obj.name = 'lcg'
print(obj.name) # 结果==>
# __setattr__ in runing
# 0bug