Python-多进程中关于类以及类实例的一些思考



1. 背景

在最近完成了一个小工具,关于一个日志识别、比较的小工具,虽然目前这个小工具很多功能需要进行完善,但是并不影响我想在这里推荐的决心: CessTop - CessTop ---- A Smart Tool written in Python to Parse and Compare the Cisco Firewall Config File with TopSec Firewall Config File

在这个过程中,因为需要重构我的代码,我需要为三个不同的进程需要扮演不同的角色,第一个进程负责处理 Cisco 的配置文档内容, 第二个进程负责处理 TopSec 的配置文档内容,第三个进程等待前两个进程处理完相关的数据结构之后,再进行比对,即第三个相当于在运行的前期起到了一个服务监听的功能。

在这个过程中,为每一个进程都设计了一个独立的类来定义不同的数据变量,因此需要为每一个类的实例对象创建一个进程;

这些是撰写这篇博客的一个背景....

一点点小的思路 - 火花(或者撰写这篇博客的动力?!):

  • 在 Pycharm IDE 中如果不定义 @staticmethod 就会一直提示建议将你的新定义的函数转换成为 Global 的函数定义,我不明白为什么会出现这个问题,但是我觉得有必要了解一下类中函数的定义规则;

  • 在进程的创建中,都知道 Python 的多进程实现是基于 multiprocessing 的Package来实现的,至于怎么实现多进程,在Windows 和 类Unix 的系统是不同的,在这里我只研究 类Unix 的实现,即调用 fork 函数带来的问题;

    1.在对于线程池 (pool) 的调用 apply 以及 apply_async 函数时候的问题;

    2.怎么去实现多进程间的通讯来保证进程之间的参数传递?使用Pipe还是Queue?

2. Python 类中的函数 - staticmethod / classmethod

肯定很多朋友对这个概念已经很熟悉了,下边简单的说一下并举几个例子:


staticmethod

@staticmethod 定义了类中的静态函数,这个静态函数有几个特性:

  • 可以不被类的实例调用,即直接从类就可以调用,即不需要声明一个实例:

    class A(object):
    @staticmethod
    def demo_method(info:str):
    print(info)
    A.demo_method("This is staticmethod") # This is staticmethod
  • 静态方法相当于已经从类中分出去了,但是也可以通过 self 来调用类中的私有变量,但前提是必须要创建一个类的实例,因为这个函数不可以与类实例绑定,因此需要为 self 进行传参,即 self 的值为一个新的类实例变量:

    class A(object):
    __private_var = "This is Private Variable"
    @staticmethod
    def demo_method(self):
    print(self.__private_var)
    A_instance = A()
    # 这里需要为 self 制定一个参数,参数为新创建的 temp
    A.demo_method(A_instance)
  • 静态方法是可以被子类所继承的,继承的方式遵循类的继承方式:

    class A():
    @staticmethod
    def A_method(info):
    print(info) # B类继承A类的函数 A_method
    class B(A):
    @staticmethod
    def B_method(self, info):
    super().A_method(info) # 这里创建一个新的B的实例
    B_instance = B()
    B_instance.B_method(B_instance, "This is B invokes A staticmethod")
    # 这里可以打印出 This is B invokes A staticmethod
    # 即B调用了A的A_method

    我们都知道虽然 @staticmethod 定义了一个类外的函数,因此继承过的类实例是不能访问被继承类中的私有变量的,除非你为被继承的类声明一个类实例;

    上边的静态也可以被写成以下的形式:

    class A():
    @staticmethod
    def A_method(info):
    print(info) # B类继承A类的函数 A_method
    class B(A):
    @classmethod
    def B_method(cls, info):
    super().A_method(info) B().B_method("This is B invokes A staticmethod")
    # 这里可以打印出 This is B invokes A staticmethod
    # 即B调用了A的A_method

    具体的解释由 classmethod 的章节来进行进一步的讲解;

classmethod

classmethod 定义了一个类中的 类方法, 由 @classmethod 装饰器进行定义,其特点如下:

  • 由于 以装饰器 @staticmethod 进行修饰的类方法可以直接将类通过 cls 绑定,因此调用不需要声明一个类的实例:

    class A():
    @classmethod
    def A_method(cls):
    print("This is A classmethod method") A.A_method()
    # 打印出: This is A classmethod method

    当然,这并不影响你创建一个新的类实例,然后调用函数:

    class A():
    @classmethod
    def A_method(cls):
    print("This is A classmethod method") A_instance = A()
    A_instance.A_method()
    # 打印出: This is A classmethod method
  • 对于一个被声明了 类方法 的函数想要调用类中定义的变量,以及私有变量,可以吗?答案是可以的!

    class A():
    class_var = "This is Class Variable\n"
    __private_var = "This is Class Private Variable\n"
    @classmethod
    def A_method(cls):
    print(cls.class_var)
    print(cls.__private_var) A.A_method()
    # 打印出:
    # This is Class Variable
    # This is Class Private Variable

    但是这里就涉及到了一个问题,在没有实例的情况下,即在 堆栈中没有创建一个 类实例,如果改变类的变量,这个类的变量会被修改吗? - - - 好像会被改变......

    class A():
    num = 1
    @classmethod
    def A_method(cls):
    print(cls.num)
    @classmethod
    def change_method(cls):
    cls.num = 2 A.change_method()
    A.A_method()
    # 我一开始认为是不能修改的,但是结果让我很吃惊,居然被更改了.... 分析一下为啥被修改了还是有什么影响...
    # 输出是: 2
    # 但是,目前我不认为这个类的定义被修改了,因此尝试 新定义一个 类的实例
    A_instance = A()
    print(A_instance.num)
    # 输出是: 2
    # 好吧, 被修改了....
    # 分析一下这个过程

    接着上边的继续分析,我们需要了解一下 Python 对类的定义,即 声明这个类 到底被存在什么位置?

    class A():
    num = 1
    @classmethod
    def A_method(cls):
    print(cls.num)
    @classmethod
    def change_method(cls):
    cls.num = 2
    print(cls) # 140683689759152 A.change_method() # 140683689759152
    A.A_method()
    # 打印一下 python 的函数在内存中的位置
    print(id(A)) # 140683689759152

    即在上边调用的类是存储到相同地址的定义;

    因此,因为引用了相同地址的类变量,因此存在了可能会改变类定义变量的情况;

  • 现在,已经明白了在Pyton 类定义的常量可能会发生改变,那么继承的子类调用super的地址是什么呢? 即:super 调用的是在全局变量的类中定义? 还是类实例的地址?

    • 如果直接调用子类 (B、C)而不创建一个子类的实例,那么调用的父类不是直接定义的父类,即不会改变原来父类中的定义!从下边的代码可以看到,在全局变量中创建的 A、B 两个类的地址是不一样的; 在 B 中打印超类(父类)的地址与全局变量的地址是不相同的,那么就不会存在改变父类定义属性的情况;

      class A():
      def A_method():
      print("This is A method!") class B(A):
      @classmethod
      def B_method(cls):
      print(id(super())) class C(A):
      @classmethod
      def C_method(cls):
      print(id(super())) print(id(A)) # 140512863619088
      print(id(B)) # 140512863620032
      B.B_method() # 140511333031744
      C.C_method() # 140511869048192
    • 验证一下上边的给出的定义:

      class A():
      num = 1
      def A_method():
      print("This is A method!") @classmethod
      def A_ChangeMethod(cls):
      cls.num = 2 @classmethod
      def A_PrintNum(cls):
      print(cls.num) class B(A):
      @classmethod
      def B_method(cls):
      # print(id(super()))
      super().A_ChangeMethod()
      super().A_PrintNum() class C(A):
      @classmethod
      def C_method(cls):
      print(super().num) # print(id(B))
      B.B_method() # 2
      # print(id(A))
      C.C_method() # 1
  • 生成类的实例,再次验证,即不会被修改!

    class A():
    num = 1
    def A_method():
    print("This is A method!") @classmethod
    def A_ChangeMethod(cls):
    cls.num = 2 @classmethod
    def A_PrintNum(cls):
    print(cls.num) class B(A):
    @classmethod
    def B_method(cls):
    super().A_ChangeMethod()
    super().A_PrintNum() class C(A):
    @classmethod
    def C_method(cls):
    print(super().num) B_instance = B()
    B.B_method() # 2
    C_instance = C()
    C.C_method() # 1
  • 定义的类实例的 cls 的地址是什么?

    class A():
    def A_method():
    print("This is A method!")
    class B():
    @classmethod
    def B_Method(cls):
    print(id(cls)) print(id(B)) # 140512865761952
    B_instance = B
    B_instance.B_Method() # 140512865761952
    B_instance_2 = B
    B_instance.B_Method() # 140512865761952

staticmethod 以及 classmethod 的比较

  • cls 以及 self 的区别:

    • 两者都有一种 C++ 中指针的感觉;

    • 从上边的例子可以看出,cls 被用来指代函数定义的当前类中的指向存储地址的变量:

      • 如果没有声明类的实例,那么 cls 在被直接调用的时候被指向(引用)第一次定义类的地址,即全局变量类的地址,即如果直接调用 cls 修改类的属性,就会被修改,这点要非常注意!;
      • 创建了类的实例,那么 cls 也被指向第一次定义类的地址,因此做到 cls 来调用属性 或者 修改类的属性要非常小心,可能会存在意外改变的情况,因此 cls 可以做到对类属性的追加;
    • self 被用来指代 当前的类实例变量,并没有什么可以探讨的;

一点小思考

  • 在直接调用类引用的时候,是: 定义全局变量类的调用,因此如果修改属性会导致修改;
  • 在考虑到继承的因素的情况下,每一次继承,编译器会创建(深拷贝)一个临时的父类来提供继承的属性以及方法,这种情况不考虑是否创建类实例,即不管创建一个实例与否编译器都会深拷贝一个父类,因此 super 不会改变定义的全局变量类的定义,super 我认为是非常安全的;
  • 在 Python 的类继承中,子类会深拷贝 一个父类,从而实现调用 父类的属性以及功能
    • 这点带来的优点是: 对于一个定义来说是非常安全的,即不会出现意外的错误;
    • 缺点: 占用资源;

3. Python 中的进程间的通信 - multiprocessing/Queue

在最近的构建的小工具,中间用到了进程中的通信,具体的实现过程请参考我的代码;

这里说一下遇到的一个小问题,即 multiprocessing.Pool 中不同的进程之间的通信问题,特此说明一下;

都知道在 Python 下有进程池的相关概念,调用非常简单,即使用 Pool.add 以及 Pool.apply_async 或者 Pool.apply 来开启相关的进程;

三个进程中,需要 前两个进程来处理文件,第三个进程来分析处理完的相关数据,因此我希望设计第三个进程为一个服务,等待前两个进程处理完相关数据并返回结果在进行处理,有很多实现方式,我选择了 Queue 来处理进程间的通信,下边的演示代码说明了这个过程:

from multiprocessing import Process, Pool, Queue
if __name__=='__main__':
queue_1 = Queue(maxsize=3)
pool = Pool(3)
with pool as pro:
result = pro.apply_async(f, args=(queue_1,),)
pool.close()
pool.join()

即我需要将 Queue 传入到启动函数中,完成参数在进程中的通讯,这个时候遇到了报错:

RuntimeError: Queue objects should only be shared between processes through inheritance

分析一下这个错误

  • 首先,查询了相关的 API 即,apply_async 返回一个 AsyncResult 类型的参数,这个参数可以返回进程的状态,因为调用 apply_async 之后,queues_1 不支持直接传入到 apply_async 的函数中;

  • 但是在 Process 中定义可以直接被传入,即下边的这种是被支持的:

    from multiprocessing import Process, Pool, Queue
    if __name__=='__main__':
    queue_1 = Queue(maxsize=3)
    process_1 = Process(target=f, args=(queue_1,))
    process_2 = Process(target=f, args=(queue_1,))
    process_3 = Process(target=f, args=(queue_1,))

解决方法

  • 调用 multiprocess.Manager 来创建一个允许多进程之间通信的 multiprocess.Manager.Queue ,然后被 Pool 对象调用;
  • Pool 对象换成 Process 对象;

写到最后

  • 在多进程的调用中, 如果你自己写了一个启动进程函数而不重新覆盖 Process.Run 函数,那么你需要定义一个启动函数,如果类中该函数的被定义为 staticmethod 并定义了 self, 那么你需要定义一个类的实例,然后通过 Process 传参:

    在类中的定义的启动函数:

    @staticmethod
    # def Start_Processing(self):
    def Start_Processing(self, queue: multiprocessing.Queue):
    try:
    self.access_list = self.Process_Cisco_LogFile_ToList(filename=self.filename)
    self.LogFileList_toPandasDF(self, Logfile_List=self.access_list)
    except Exception as err:
    raise err
    finally:
    queue.put(self.df_cisco)
    self.df_cisco.to_csv(config.default_config_dict["default"].cisco_csv_Name, sep=',',
    header=config.default_config_dict["default"].df_format,
    index=True)

    调用启动函数:

    cisco_instance = cisco_function.Cisco_Function(filename_dict["cisco_filename"])
    cisco_process = Process(target=cisco_instance.Start_Processing, args=(cisco_instance, queue_cisco,))

    可以看到,必须为 Start_processing 函数的self 赋值一个类实例,才能正常启动该函数;

Python - 关于类(self/cls) 以及 多进程通讯的思考的更多相关文章

  1. python Event对象、队列和多进程基础

    Event对象 用于线程间通信,即程序中的其一个线程需要通过判断某个线程的状态来确定自己下一步的操作,就用到了event对象 event对象默认为假(Flase),即遇到event对象在等待就阻塞线程 ...

  2. Python深入类和对象

    一. 鸭子类型和多态 1.什么是鸭子类型: 在程序设计中,鸭子类型(英语:Duck typing)是动态类型和某些静态语言的一种对象推断风格."鸭子类型"像多态一样工作,但是没有继 ...

  3. 1.面向过程编程 2.面向对象编程 3.类和对象 4.python 创建类和对象 如何使用对象 5.属性的查找顺序 6.初始化函数 7.绑定方法 与非绑定方法

    1.面向过程编程 面向过程:一种编程思想在编写代码时 要时刻想着过程这个两个字过程指的是什么? 解决问题的步骤 流程,即第一步干什么 第二步干什么,其目的是将一个复杂的问题,拆分为若干的小的问题,按照 ...

  4. python面向对象(类的成员及类方法)

    类的普通成员 字段 方法 属性 类的高级成员 静态字段 静态方法 属性方法   类方法 类成员修饰符 类的成员 类的成员可以分为三大类:字段.方法和属性 注:所有成员中,只有普通字段的内容保存对象中, ...

  5. Python 中 logging 日志模块在多进程环境下的使用

    因为我的个人网站 restran.net 已经启用,博客园的内容已经不再更新.请访问我的个人网站获取这篇文章的最新内容,Python 中 logging 日志模块在多进程环境下的使用 使用 Pytho ...

  6. python 面向对象(类的特殊成员)

    python 面向对象: (思维导图 ↑↑↑↑) 类的特殊成员 python的类成员存在着一些具有特殊含义的成员 1.__init__: 类名() 自动执行 __init__ class Foo(ob ...

  7. python:类的基本特征------继承、多态与封装

    一.继承 1,什么是继承 继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类 python中类的继承分为:单继承和多继承 cl ...

  8. 百万年薪python之路 -- 并发编程之 多进程 一

    并发编程之 多进程 一. multiprocessing模块介绍 ​ python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大 ...

  9. 第十五章、python中的进程操作-开启多进程

    目录 第十五章.python中的进程操作-开启多进程 一.multprocess模块 二.multprocess.process模块 三.Process()对象方法介绍 四.Process()对象属性 ...

随机推荐

  1. js 监听ajax请求

    function hookSend(hook) { if (!XMLHttpRequest.prototype._oldSend) XMLHttpRequest.prototype._oldSend ...

  2. js 位掩码

    原文 定义掩码 const mask0 = parseInt("00000001", 2); const mask1 = parseInt("00000010" ...

  3. 美最大政媒《国会山报》罕见发文阐述BTC,华盛顿金融盛赞SPC

    比特币价格突破4万美元创下历史新高,美国最大政治媒体之一<国会山报>罕见的发表了文章对比特币进行阐明. 2021年已经过去一周,但比特币依然没有停下上涨的步伐.在刚刚过去2020年里,比特 ...

  4. np.mean(img, axis=(0, 1))

    np.mean(img, axis=(0, 1))   img 是shape为(H,W,3)的图片 np.mean(img, axis=(0, 1)) 是求出各个通道的平均值,shape是 (3, ) ...

  5. 通过setMouseTracking实现用鼠标拖动控件

    1 import sys 2 from PyQt5.Qt import * 3 4 class Mwindow(QWidget): 5 leftclick = False 6 7 def __init ...

  6. 微信小程序(二十)-UI组件(Vant Weapp)-01按装配置

    1.官网 https://vant-contrib.gitee.io/vant-weapp/#/intro https://gitee.com/vant-contrib/vant-weapp 2.按装 ...

  7. LiteOS:SpinLock自旋锁及LockDep死锁检测

    摘要:除了多核的自旋锁机制,本文会介绍下LiteOS 5.0引入的LockDep死锁检测特性. 2020年12月发布的LiteOS 5.0推出了全新的内核,支持SMP多核调度功能.想学习SMP多核调度 ...

  8. nacos服务注册与发现之客户端

    服务注册 1.1 NamingService.registerInstance的方法为客户端提供的服务注册接口 1.2 客户端通过调用NamingService.registerService上报到n ...

  9. sentry SSRF

    目录 Sentry介绍 exp测试步骤 自己构造blind发包 修复方式 参考 Sentry介绍 Sentry 是一个实时的事件日志和聚合平台,基于 Django 构建.一般在url上.或者logo上 ...

  10. jmeter数据库链接配置

    通常使用数据库有3个要求,性能好.数据一致性有保障.数据安全可靠:数据库优化的前提也是这三个要求.有句玩笑话叫少做少犯错,不做不犯错.DB优化的思路就是少做,减少请求次数,减少数据传输量,减少运算量. ...