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

DoHerasYang 2021-04-06 11:56:58
Python 进程 self 多进 cls


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 赋值一个类实例,才能正常启动该函数;

版权声明
本文为[DoHerasYang]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/doherasyang/p/14621088.html

  1. 利用Python爬虫获取招聘网站职位信息
  2. Using Python crawler to obtain job information of recruitment website
  3. Several highly rated Python libraries arrow, jsonpath, psutil and tenacity are recommended
  4. Python装饰器
  5. Python实现LDAP认证
  6. Python decorator
  7. Implementing LDAP authentication with Python
  8. Vscode configures Python development environment!
  9. In Python, how dare you say you can't log module? ️
  10. 我收藏的有关Python的电子书和资料
  11. python 中 lambda的一些tips
  12. python中字典的一些tips
  13. python 用生成器生成斐波那契数列
  14. python脚本转pyc踩了个坑。。。
  15. My collection of e-books and materials about Python
  16. Some tips of lambda in Python
  17. Some tips of dictionary in Python
  18. Using Python generator to generate Fibonacci sequence
  19. The conversion of Python script to PyC stepped on a pit...
  20. Python游戏开发,pygame模块,Python实现扫雷小游戏
  21. Python game development, pyGame module, python implementation of minesweeping games
  22. Python实用工具,email模块,Python实现邮件远程控制自己电脑
  23. Python utility, email module, python realizes mail remote control of its own computer
  24. 毫无头绪的自学Python,你可能连门槛都摸不到!【最佳学习路线】
  25. Python读取二进制文件代码方法解析
  26. Python字典的实现原理
  27. Without a clue, you may not even touch the threshold【 Best learning route]
  28. Parsing method of Python reading binary file code
  29. Implementation principle of Python dictionary
  30. You must know the function of pandas to parse JSON data - JSON_ normalize()
  31. Python实用案例,私人定制,Python自动化生成爱豆专属2021日历
  32. Python practical case, private customization, python automatic generation of Adu exclusive 2021 calendar
  33. 《Python实例》震惊了,用Python这么简单实现了聊天系统的脏话,广告检测
  34. "Python instance" was shocked and realized the dirty words and advertisement detection of the chat system in Python
  35. Convolutional neural network processing sequence for Python deep learning
  36. Python data structure and algorithm (1) -- enum type enum
  37. 超全大厂算法岗百问百答(推荐系统/机器学习/深度学习/C++/Spark/python)
  38. 【Python进阶】你真的明白NumPy中的ndarray吗?
  39. All questions and answers for algorithm posts of super large factories (recommended system / machine learning / deep learning / C + + / spark / Python)
  40. [advanced Python] do you really understand ndarray in numpy?
  41. 【Python进阶】Python进阶专栏栏主自述:不忘初心,砥砺前行
  42. [advanced Python] Python advanced column main readme: never forget the original intention and forge ahead
  43. python垃圾回收和缓存管理
  44. java调用Python程序
  45. java调用Python程序
  46. Python常用函数有哪些?Python基础入门课程
  47. Python garbage collection and cache management
  48. Java calling Python program
  49. Java calling Python program
  50. What functions are commonly used in Python? Introduction to Python Basics
  51. Python basic knowledge
  52. Anaconda5.2 安装 Python 库(MySQLdb)的方法
  53. Python实现对脑电数据情绪分析
  54. Anaconda 5.2 method of installing Python Library (mysqldb)
  55. Python implements emotion analysis of EEG data
  56. Master some advanced usage of Python in 30 seconds, which makes others envy it
  57. python爬取百度图片并对图片做一系列处理
  58. Python crawls Baidu pictures and does a series of processing on them
  59. python链接mysql数据库
  60. Python link MySQL database