在最近完成了一個小工具,完成了關於日誌識別、比較的相關功能,雖然目前這個小工具不少功能須要進行完善,可是並不影響我想在這裏推薦的決心: 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
賦值一個類實例,才能正常啓動該函數;