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项目中  
    使用全局变量时,应当是静态的,不能是动态修改的 (可能会出错) !!!
posted @ 2022-08-08 11:07  Edmond辉仔  阅读(350)  评论(0编辑  收藏  举报