Python--补充:pipreqs模块、函数与方法、偏函数、local对象、web项目全局变量问题
1 pipreqs的使用
# 项目依赖文件:
requirements.txt
# 多种方式实现
1.手写
2.虚拟环境: # 只有该项目时
pip freeze > requirements.txt
3.使用第三方插件自动生成: # 只生成当前项目所依赖的模块
- 安装
pip3 install pipreqs==0.4.0
- 项目根路径下执行,生成依赖文件:
pipreqs ./ --encoding=utf-8 # Windows下(gbk) 需指定编码
- 安装依赖文件
pip install -r requirements.txt
2 函数和方法
# 函数就是普通函数,不会自动传值
# 方法是函数,但是它会自动传值
# 面向对象中概念
-类方法
-对象方法
-静态方法
# 方法有可能是函数(对象的绑定方法,如果类来调用,就是函数)
# 案例
MethodType # 判断是不是方法
FunctionType # 判断是不是函数
from types import MethodType, FunctionType
class Foo(object):
def fetch(self): # 绑定给对象的方法,正常对象来掉用,就是方法
pass
# 1.类 调用 对象方法 此时为普通函数
print(isinstance(Foo.fetch, MethodType)) # False 不是方法
print(isinstance(Foo.fetch, FunctionType)) # True 是函数
# 2.对象 调用 对象方法 此时为方法
obj = Foo()
print(isinstance(obj.fetch, MethodType)) # True 是方法
print(isinstance(obj.fetch, FunctionType)) # False 不是函数
3 偏函数
# 偏函数
提前传值 延迟计算 # 返回一个新的函数
# 案例:
from functools import partial
def add(a, b, c):
return a + b + c
# 函数正常调用,即刻获取结果
res=add(3,4,5)
print(res)
# 偏函数
res = partial(add, 3) # res: 带有参数的 函数内存地址
# 提前先给add函数传了一个参数3,还没执行add
# 以后执行 传后续参数就可以了 使用res
r=res(4,5)
print(r)
# 应用场景:
-若某个函数,可以提前知道某个参数,就可以使用偏函数
-flask源码中使用
4 local对象
# 多线程并发操作一个变量,会导致数据错乱
# 解决:
1.可以使用 互斥锁加锁 处理 数据不安全的情况
# 也是临界区的概念
这段进行加锁处理的共用资源 也叫临界区
2.使用local对象处理
多个线程操作的变量存放在local对象中 # 处理了并发安全的问题
类似:大字典 {'key值是线程id号':'value值是个字典'}
# Local对象本质 {123:{'name':lqz},222:{'name':egon}}
l=local()
# 线程1
l.name='lqz'
# 线程2
l.name='egon'
4.1 基本使用
##### 1 不使用local
多线程并发操作同一个进程的某个数据,数据容易错乱
import time
from threading import Thread
class Local():
pass
l = Local()
def task(name):
l.name = name
time.sleep(1) # 模拟修改数据的时间延迟
print('在线程内的名字是:', name, 'l对象中的名字大概率不一样', l.name)
# 正常来讲:同一个线程(函数中) name 和 l.name 应该一样
# 但多线程造成数据错乱,可能不一致
if __name__ == '__main__':
for i in range(10):
t = Thread(target=task, args=['egon' + str(i) + '号', ])
t.start()
# 等待所有线程都执行完成再执行下面代码
time.sleep(3)
##### 2 使用local
import time
from threading import Thread
from threading import local
# 实例化 并发安全的local
多个线程操作,不会错乱,因为每个线程用的都是自己的数据
l = local()
def task(name):
l.name = name
time.sleep(1)
print('在线程内的名字是:', name, 'l对象中的名字也是', l.name)
if __name__ == '__main__':
for i in range(10):
t = Thread(target=task, args=['egon' + str(i) + '号', ])
t.start()
# 等待所有线程都执行完成再执行下面代码
time.sleep(3)
print(l)
4.2 自定义local类
# 前提:
threading类下的local 能处理线程安全,但不能处理协程安全
# 故:
自定义一个local类,实现线程和协程并发安全
##### 1 第一个版本 通过字典自定义
from threading import get_ident,Thread
import time
storage = {}
def set(k,v):
ident = get_ident() # 线程id号
if ident in storage:
storage[ident][k] = v
else:
storage[ident] = {k:v}
def get(k):
ident = get_ident()
return storage[ident][k]
def task(arg):
set('val',arg) #
v = get('val')
print(v)
# 10个线程跑完,最终storage={123:{val:0},222:{val:1},333:{val:2},444:{val:3}.....}
for i in range(10):
t = Thread(target=task,args=(i,))
t.start()
##### 2 使用面向对象
from threading import get_ident,Thread
import time
class Local(object):
storage = {}
def set(self, k, v):
ident = get_ident()
if ident in Local.storage:
Local.storage[ident][k] = v
else:
Local.storage[ident] = {k: v}
def get(self, k):
ident = get_ident()
return Local.storage[ident][k]
obj = Local()
def task(arg):
obj.set('val',arg)
v = obj.get('val')
print(v)
for i in range(10):
t = Thread(target=task,args=(i,))
t.start()
##### 3 __setattr__和__getattr__方法版
from threading import get_ident,Thread
import time
class Local(object):
storage = {} # 存储空间都是使用的同一个类下的storage
def __setattr__(self, k, v):
ident = get_ident()
if ident in Local.storage:
Local.storage[ident][k] = v
else:
Local.storage[ident] = {k: v}
def __getattr__(self, k):
ident = get_ident()
return Local.storage[ident][k]
obj = Local()
def task(arg):
obj.val = arg
print(obj.val)
for i in range(10):
t = Thread(target=task,args=(i,))
t.start()
##### 4 每个local对象用自己的存储空间
from threading import get_ident, Thread
import time
class Local(object):
def __init__(self):
# self.storage={} # 不能直接 '.' 变量赋值,会触发__setattr__() 导致死递归了
object.__setattr__(self, 'storage', {}) # 使用父类的方法,来初始一个存储空间
def __setattr__(self, k, v):
ident = get_ident() # 获取线程id号
if ident in self.storage:
self.storage[ident][k] = v
else:
self.storage[ident] = {k: v} #
def __getattr__(self, k):
ident = get_ident()
return self.storage[ident][k]
obj = Local()
def task(arg):
obj.val = arg
print(obj.val)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
##### 3.5 兼容协程 最终版
存储空间的 key 要嘛是线程号 要嘛是协程号
try:
from greenlet import getcurrent as get_ident # 从协程模块 获取协程号
except Exception as e:
from threading import get_ident
from threading import Thread
import time
class Local(object):
def __init__(self):
object.__setattr__(self,'storage',{})
def __setattr__(self, k, v):
ident = get_ident()
if ident in self.storage:
self.storage[ident][k] = v
else:
self.storage[ident] = {k: v}
def __getattr__(self, k):
ident = get_ident()
return self.storage[ident][k]
obj = Local()
def task(arg):
obj.val = arg
obj.xxx = arg
print(obj.val)
for i in range(10):
t = Thread(target=task,args=(i,))
t.start()
5 django/flask全局变量的坑
详见:https://zhuanlan.zhihu.com/p/428228031
# django 或 flask项目启动
django: python manage.py runserver
flask : 右键执行
# 开发调试中:
在用自身的web框架(wsgiref 或 Werkzeug) 启动时,
都是由一个进程启动后,然后不同的线程去处理不同的业务请求。
此时:不同线程修改全局变量,都是修改的同一个进程的变量资源,不会造成数据错乱
# 生产部署中:
为了利用多核的优势,一般采用uwsgi部署,此时启动项目
操作系统 会根据配置uwsgi进程数,来启动多个进程启动项目
再在各自进程中 在开设多个线程去处理视图业务。
因多进程间数据是隔离的,那么定义在项目中的全局变量,会分别存放一份在启动的进程里
此时:
若不同的视图任务 都对各自进程里的同一全局变量进行修改
那么请求响应的结果 会出现数据混乱的情况
# 注:强调
uwsgi启动Python的Web项目中
使用全局变量时,应当是静态的,不能是动态修改的 (可能会出错) !!!