信号、flask-script、自定义命令的使用

文章介绍了PythonFlask和Django框架中的信号机制,用于模块间解耦合。在Flask中,详细展示了如何使用内置信号如request_started和template_rendered,并解释了如何自定义信号。同样,文中也提及了Django的Model信号,如pre_save和post_delete。此外,文章讨论了Flask请求上下文的工作原理,以及全局变量如request和session的管理。最后,文章提到了Flask-Script的使用,包括创建自定义命令来实现数据库操作等功能。

一、信号的使用

信号(Signal)就是两个独立的模块用来传递消息的方式,它有一个消息的发送者Sender,还有一个消息的订阅者Subscriber。信号的存在使得模块之间可以摆脱互相调用的模式,也就是解耦合。发送者无需知道谁会接收消息,接收者也可自由选择订阅何种消息。

简单来说,flask请求进来,某个点关联上一个函数,只要到了这个点这个函数就会执行。

flask有一些内置信号:

request_started = _signals.signal('request-started')                # 请求到来前执行
request_finished = _signals.signal('request-finished')              # 请求结束后执行
 
before_render_template = _signals.signal('before-render-template')  # 模板渲染前执行
template_rendered = _signals.signal('template-rendered')            # 模板渲染后执行
 
got_request_exception = _signals.signal('got-request-exception')    # 请求执行出现异常时执行
 
request_tearing_down = _signals.signal('request-tearing-down')      # 请求执行完毕后自动执行(无论成功与否)
appcontext_tearing_down = _signals.signal('appcontext-tearing-down')# 应用上下文执行完毕后自动执行(无论成功与否)
 
appcontext_pushed = _signals.signal('appcontext-pushed')            # 应用上下文push时执行
appcontext_popped = _signals.signal('appcontext-popped')            # 应用上下文pop时执行
message_flashed = _signals.signal('message-flashed')                # 调用flask在其中添加数据时,自动触发

内置信号执行使用步骤:

# 模板渲染后信号执行
# 功能:home页面被渲染,记录一条日志
# 第一步:写一个函数
def template_test(*args,**kwargs):
    print(args)
    print(kwargs)
    print('模板渲染完了')
# # 第二步:绑定上内置信号:template_rendered
template_rendered.connect(template_test)
# 第三步:触发信号执行(不需要咱们操作)


@app.route('/home')
def home():
    return render_template('home.html')


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

自定义信号步骤:

# 第一步:自定义一个信号
xxxxx = _signals.signal('xxxxx')

# 第二步:写一个函数
def xxx_test(*args, **kwargs):
    print(args)
    print(kwargs)
    print('xxx信号触发了')


# 第三步:绑定上自定义的信号信号:xxxxx
xxxxx.connect(xxx_test)


# 第四步:触发信号执行(手动触发,在某个位置触发,比如视图函数中)


@app.route('/')
def index():
    xxxxx.send()  # 触发信号
    return 'hello'


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

django的内置信号:

Model signals
    pre_init                    # django的modal执行其构造方法前,自动触发
    post_init                   # django的modal执行其构造方法后,自动触发
    pre_save                    # django的modal对象保存前,自动触发
    post_save                   # django的modal对象保存后,自动触发
    pre_delete                  # django的modal对象删除前,自动触发
    post_delete                 # django的modal对象删除后,自动触发
    m2m_changed                 # django的modal中使用m2m字段操作第三张表(add,remove,clear)前后,自动触发
    class_prepared              # 程序启动时,检测已注册的app中modal类,对于每一个类,自动触发
Management signals
    pre_migrate                 # 执行migrate命令前,自动触发
    post_migrate                # 执行migrate命令后,自动触发
Request/response signals
    request_started             # 请求到来前,自动触发
    request_finished            # 请求结束后,自动触发
    got_request_exception       # 请求异常后,自动触发
Test signals
    setting_changed             # 使用test测试修改配置文件时,自动触发
    template_rendered           # 使用test测试渲染模板时,自动触发
Database Wrappers
    connection_created          # 创建数据库连接时,自动触发

方式一(放到__init__里):

from django.db.models.signals import pre_save
import logging
def callBack(sender, **kwargs):
    print(sender)
    print(kwargs)
    # 创建对象写日志
    logging.basicConfig(level=logging.DEBUG)
    # logging.error('%s创建了一个%s对象'%(sender._meta.db_table,kwargs.get('instance').title))
    logging.debug('%s创建了一个%s对象'%(sender._meta.model_name,kwargs.get('instance').title))

pre_save.connect(callBack)

方式二(装饰器方式):

from django.db.model.signals import pre_save
from django.dispatch import receiver
@receiver(pre_save)
def my_callback(sender, **kwargs):
	print("创建对象成功")
	print(sender)
	print(kwargs)

django自定义信号:
在这里插入图片描述

了解
多线程并发安全的问题:多个线程同时操作同一个数据,出现错乱。
使用同步锁---->修改数据之前先拿到---->开始修改---->释放锁---->别人再拿

几个锁的概念:
线程锁(线程级)---->分布式锁(应用级别):即分布式系统中的锁。为了解决分布式系统中控制共享资源访问的问题。

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。

乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。

同步锁(互斥锁),递归锁(可重入锁,递归锁可以acquire多次)

线程死锁:多个线程使用同一资源,资源还没有被释放,那么多个线程会阻塞在那里。
触发场景:多把(互斥/递归)锁并且在多个线程中交叉使用。
解决:整合成一把互斥锁或所有互斥锁改成一把递归锁

Event事件(Event锁)(一些线程需要等到其他线程执行完成之后才能执行,类似于发射信号)
Semaphore(信号量)可以理解为多把锁,同时允许多个线程来更改数据。

二、flask-script的使用

实现类似于这样的命令:python manage.py runserver
django自定义命令,比如python manage.py initdb xx.xsl article,只要执行这个命令,就向数据库的article表中写入xx.xls的数据。

安装:pip3 install flask-script

基本使用:

from flask import Flask

app = Flask(__name__)
# 第一步导入
from flask_script import Manager
# 第二步包装
manager = Manager(app)


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


if __name__ == '__main__':
    # app.run()
    # 第三步使用
    manager.run()

自定义命令使用:

from flask import Flask

app = Flask(__name__)
# 第一步导入
from flask_script import Manager
# 第二步包装
manager = Manager(app)


# 自定义命令
@manager.command
def custom(arg):
    """
    自定义命令
    python manage.py custom 123
    :param arg:
    :return:
    """
    print(arg)


@manager.option('-n', '--name', dest='name')
@manager.option('-u', '--url', dest='url')
def cmd(name, url):
    """
    自定义命令(-n也可以写成--name)
    执行: python manage.py  cmd -n lqz -u http://www.baidu.com
    执行: python manage.py  cmd --name lqz --url http://www.baidu.com
    :param name:
    :param url:
    :return:
    """
    print(name, url)
# 有什么用?
# 把excel的数据导入数据库,定制个命令,去执行


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


if __name__ == '__main__':
    # 第三步使用
    manager.run()

三、flask请求上下文源码分析

from flask import Flask

app = Flask(__name__)


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


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

请求来了,会干什么事?
第一:会执行app(environ, start_response)---->对象(),触发类的 __call__方法
第二:app.__call__---->Flask类的方法.wsgi_app(environ, start_response)---->整个的请求生命周期

核心代码如下:

ctx = self.request_context(environ)
error = None
try:
    try:
        ctx.push()
        response = self.full_dispatch_request()
    except Exception as e:
        error = e
        response = self.handle_exception(e)
    except:  # noqa: B001
        error = sys.exc_info()[1]
        raise
    return response(environ, start_response)
finally:
    if self.should_ignore_error(error):
        error = None
    ctx.auto_pop(error)

第一行的ctx叫请求上下文,ctx = self.request_context(environ)---->RequestContext(self, environ)实例化得到了一个RequestContext类的对象---->__init__方法中

def __init__(self, app, environ, request=None, session=None):
    self.app = app
    if request is None:#创造出一个请求对象
        request = app.request_class(environ)
    self.request = request
    self.flashes = None
	self.session = session

总结:ctx对象中放了哪些东西?当次请求的request对象、app对象、flashes和session。

重点:flask的request、session对象是全局的,那它是怎么区分不同的请求的?
使用了python的Local对象(java threadlocal),多个线程可以同时读写这个变量,并且不会有并发安全的问题---->不同线程操作的是自己的数据---->大字典---->key值为线程id号

l=local()
# 线程1 
l.name='cb'

# 线程2
l.name='blee'

总结
1 flask-session

  • redis、文件
  • 两种方式

2 数据库连接池

3 wtforms

  • 表单验证
  • 页面渲染

4 信号

  • 在源码中安插了很多切面,只要绑定了函数,就会执行函数,
  • 内置信号
  • 自定义信号

5 flask-script

  • 可以使用命令,启动项目

6 自定义命令

  • flask可以自定义
  • django也可以

补充
Web项目的全局变量

  • 进程是资源分配的最小单位,线程是cpu调度(执行)的最小单位
  • uwsgi启动---->操作系统启动4个进程运行(flask,django)
  • uwsgi启动Python的Web项目中不要动态修改全局变量

uwsgi多进程启动Python的Web项目
在这里插入图片描述

示例:
写一个flask程序x1.py

from flask import Flask
import time

app = Flask(__name__)
count = 0
@app.route('/')
def hello_world():
    global count
    count+=1
    return 'Hello World!'+str(count)


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

编写uwsgi的配置文件uwsgi.ini

[uwsgi]
http = 0.0.0.0:5000
chdir = /root/
wsgi-file = /root/x1.py
processes = 1
threads = 2
buffer-size = 32768
master = true
pidfile = uwsgi.pid
daemonize = uwsgi.log
callable = app

使用一个进程启动uwsgi:uwsgi uwsgi.ini

压测:

import requests
from threading import Thread
import time
def task():
    res=requests.get('http://127.0.0.1:5000/')
    print(res.text)

if __name__ == '__main__':
    for i in range(1000):
        t=Thread(target=task)
        t.start()
        # time.sleep(0.9)

如果使用python x1.py(一个进程运行),不会有重复的数据
如果使用1个进程启动uwsgi,返回的数据中也不会有重复的数字;
但如果使用多个进程启动uwsgi,返回的数据中会有重复的数据,由于不同的进程中同一个变量不共享。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

妍婧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值