在最近完成了一个小工具,完成了关于日志识别、比较的相关功能,虽然目前这个小工具不少功能须要进行完善,可是并不影响我想在这里推荐的决心: CessTop - CessTop ---- A Smart Tool written in Python to Parse and Compare the Cisco Firewall Config File with TopSec Firewall Config Filepython
在这个过程当中,由于须要重构个人代码,我须要为三个不一样的进程须要扮演不一样的角色,第一个进程负责处理 Cisco 的配置文档内容, 第二个进程负责处理 TopSec 的配置文档内容,第三个进程等待前两个进程处理完相关的数据结构以后,再进行比对,即第三个至关于在运行的前期起到了一个服务监听的功能。git
在这个过程当中,为每个进程都设计了一个独立的类来定义不一样的数据变量,所以须要为每个类的实例对象建立一个进程;github
这些是撰写这篇博客的一个背景....shell
一点点小的思路 - 火花(或者撰写这篇博客的动力?!):安全
在 Pycharm IDE 中若是不定义 @staticmethod
就会一直提示建议将你的新定义的函数转换成为 Global 的函数定义,我不明白为何会出现这个问题,可是我以为有必要了解一下类中函数的定义规则;数据结构
在进程的建立中,都知道 Python 的多进程实现是基于 multiprocessing
的Package来实现的,至于怎么实现多进程,在Windows 和 类Unix 的系统是不一样的,在这里我只研究 类Unix 的实现,即调用 fork
函数带来的问题;app
1.在对于线程池 (pool) 的调用 apply
以及 apply_async
函数时候的问题;async
2.怎么去实现多进程间的通信来保证进程之间的参数传递?使用Pipe仍是Queue?函数
确定不少朋友对这个概念已经很熟悉了,下边简单的说一下并举几个例子:工具
@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
装饰器进行定义,其特色以下:
因为 以装饰器 @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
cls
以及 self
的区别:
二者都有一种 C++ 中指针的感受;
从上边的例子能够看出,cls
被用来指代函数定义的当前类中的指向存储地址的变量:
cls
在被直接调用的时候被指向(引用)第一次定义类的地址,即全局变量类的地址,即若是直接调用 cls
修改类的属性,就会被修改,这点要很是注意!;cls
也被指向第一次定义类的地址,所以作到 cls
来调用属性 或者 修改类的属性要很是当心,可能会存在乎外改变的状况,所以 cls
能够作到对类属性的追加;self
被用来指代 当前的类实例变量,并无什么能够探讨的;
super
不会改变定义的全局变量类的定义,super
我认为是很是安全的;在最近的构建的小工具,中间用到了进程中的通讯,具体的实现过程请参考个人代码;
这里说一下遇到的一个小问题,即 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
赋值一个类实例,才能正常启动该函数;