使用 Pdb 調試 Python
目錄
入門:打印變量的值
打印表達式
單步執行代碼
列出源代碼
使用斷點
繼續執行
顯示表達式
Python 來電顯示
基本的 pdb 命令
使用 pdb 調試 Python:結論
調試應用程序有時可能是一項不受歡迎的活動。您在時間緊迫的情況下忙于工作,而您只想讓它發揮作用。然而,在其他時候,您可能正在學習一種新的語言功能或嘗試一種新的方法,并希望更深入地了解某些東西是如何工作的。
不管情況如何,調試代碼都是必要的,因此在調試器中工作起來很舒服是個好主意。在本教程中,我將向您展示使用 pdb(Python 的交互式源代碼調試器)的基礎知識。
我將帶您了解 pdb 的一些常見用法。您可能希望將本教程添加為書簽,以便稍后在您真正需要時快速參考。pdb 和其他調試器是必不可少的工具。當您需要調試器時,沒有替代品。你真的需要它。
在本教程結束時,您將了解如何使用調試器查看應用程序中任何變量的狀態。您還可以隨時停止和恢復應用程序的執行流程,因此您可以準確了解每行代碼如何影響其內部狀態。
這對于追蹤難以發現的錯誤非常有用,并允許您更快、更可靠地修復錯誤代碼。有時,單步執行 pdb 中的代碼并查看值如何變化會讓人大開眼界,并導致“啊哈”時刻,以及偶爾的“手掌”。
pdb 是 Python 標準庫的一部分,因此它始終存在并可供使用。如果您需要在無法訪問您熟悉的 GUI 調試器的環境中調試代碼,這可以成為救命稻草。
本教程中的示例代碼使用 Python 3.6。您可以在GitHub上找到這些示例的源代碼。
在本教程的末尾,有一個基本 pdb 命令的快速參考。
還有一個可打印的 pdb 命令參考,您可以在調試時用作備忘單:
入門:打印變量的值
在第一個示例中,我們將看看以最簡單的形式使用 pdb:檢查變量的值。
在要中斷調試器的位置插入以下代碼:
import pdb; pdb.set_trace()
當上面的行被執行時,Python 停止并等待你告訴它下一步做什么。你會看到一個(Pdb)提示。這意味著您現在在交互式調試器中暫停并且可以輸入命令。
從 Python 3.7 開始,還有另一種進入 debugger 的方法。PEP 553描述了內置函數breakpoint(),這使得進入調試器變得容易且一致:
breakpoint()
默認情況下,breakpoint()將導入?pdb和調用pdb.set_trace(),如上所示。但是, usingbreakpoint()更靈活,允許您通過其 API 和環境變量的使用來控制調試行為PYTHONBREAKPOINT。例如,PYTHONBREAKPOINT=0在您的環境中設置將完全禁用breakpoint(),從而禁用調試。如果你正在使用Python 3.7或更高版本,我鼓勵你使用breakpoint()的不是pdb.set_trace()。
您還可以通過直接從命令行運行 Python 并傳遞選項來闖入調試器,而無需修改源代碼和使用pdb.set_trace()或。如果您的應用程序接受命令行參數,請像往常一樣在文件名之后傳遞它們。例如:breakpoint()-m pdb
$ python3 -m pdb app.py arg1 arg2
有很多 pdb 命令可用。在本教程的末尾,有一個基本 pdb 命令列表。現在,讓我們使用該p命令來打印變量的值。p variable_name在(Pdb)提示符下輸入以打印其值。
讓我們看一下這個例子。這是example1.py來源:
#!/usr/bin/env python3 filename = __file__ import pdb; pdb.set_trace() print(f'path = {filename}')
如果你從你的 shell 運行它,你應該得到以下輸出:
$ ./example1.py > /code/example1.py(5)
如果您在從命令行獲取示例或您自己的代碼時遇到問題,請閱讀如何使用 Python 創建自己的命令行命令?如果您使用的是 Windows,請查看Python Windows 常見問題解答。
現在輸入p filename。你應該看到:
(Pdb) p filename './example1.py' (Pdb)
由于您在 shell 中并使用 CLI(命令行界面),因此請注意字符和格式。他們會給你你需要的上下文:
>從第一行開始,告訴您您所在的源文件。在文件名之后,括號中是當前行號。接下來是函數的名稱。在這個例子中,由于我們沒有在函數內部和模塊級別暫停,我們看到
->開始第二行,是 Python 暫停的當前源代碼行。該行尚未執行。在本例中,這是來自上一行的line?5in?。example1.py>
(Pdb)是 pdb 的提示。它在等待命令。
使用該命令q退出調試并退出。
打印表達式
使用 print 命令時p,您將傳遞一個要由 Python 計算的表達式。如果傳遞變量名,pdb 將打印其當前值。但是,您可以做更多的事情來調查正在運行的應用程序的狀態。
在這個例子中,函數get_path()被調用。為了檢查這個函數中發生了什么,我pdb.set_trace()在它返回之前插入了一個暫停執行的調用:
#!/usr/bin/env python3 import os def get_path(filename): """Return file's path or empty string if no path.""" head, tail = os.path.split(filename) import pdb; pdb.set_trace() return head filename = __file__ print(f'path = {get_path(filename)}')
如果你從你的 shell 運行它,你應該得到輸出:
$ ./example2.py > /code/example2.py(10)get_path() -> return head (Pdb)
我們在哪?
>:我們在源文件中example2.py上線10了函數get_path()。這是p命令將用于解析變量名稱的參考框架,即當前作用域或上下文。
->: 執行已暫停return head。該行尚未執行。這是行10中example2.py在函數get_path(),從>上面的行。
讓我們打印一些表達式來查看應用程序的當前狀態。我ll最初使用命令(longlist) 來列出函數的源代碼:
(Pdb) ll 6 def get_path(filename): 7 """Return file's path or empty string if no path.""" 8 head, tail = os.path.split(filename) 9 import pdb; pdb.set_trace() 10 -> return head (Pdb) p filename './example2.py' (Pdb) p head, tail ('.', 'example2.py') (Pdb) p 'filename: ' + filename 'filename: ./example2.py' (Pdb) p get_path
您可以將任何有效的 Python 表達式傳遞給以p進行評估。
當您正在調試并希望在運行時直接在應用程序中測試替代實現時,這尤其有用。
您還可以使用命令pp(pretty-print) 來漂亮地打印表達式。如果您想打印具有大量輸出(例如列表和字典)的變量或表達式,這將很有幫助。如果可以的話,漂亮打印將對象保留在一行上,如果它們不適合允許的寬度,則將它們分成多行。
單步執行代碼
在調試時,您可以使用兩個命令來單步調試代碼:
有一個名為unt(until)的第三個命令。它與n(下)有關。我們將在本教程后面的繼續執行部分中查看它。
n(next) 和s(step)之間的區別在于pdb 停止的位置。
使用n(next) 繼續執行直到下一行并停留在當前函數中,即如果調用了一個外部函數,則不會停止。將下一步視為“留在本地”或“跨步”。
使用s(step) 執行當前行并在調用外部函數時停止。將步驟視為“步入”。如果執行在另一個函數中停止,s將打印--Call--.
雙方n并s達到當前函數結束時將停止執行并打印--Return--與之后的下一行的末尾的返回值一起->。
讓我們看一個使用這兩個命令的示例。這是example3.py來源:
#!/usr/bin/env python3 import os def get_path(filename): """Return file's path or empty string if no path.""" head, tail = os.path.split(filename) return head filename = __file__ import pdb; pdb.set_trace() filename_path = get_path(filename) print(f'path = {filename_path}')
如果你從你的 shell 運行它并輸入n,你應該得到輸出:
$ ./example3.py > /code/example3.py(14)
隨著n(next),我們停在 line?15,下一行。我們“留在本地”
讓我們試試s:
$ ./example3.py > /code/example3.py(14)
使用s(step),我們6在函數中在線停止,get_path()因為它是在線調用的14。注意命令--Call--后面的行s。
方便的是,pdb 會記住您的上一個命令。如果您要單步執行大量代碼,只需按Enter重復最后一個命令即可。
下面是使用s和 單n步執行代碼的示例。我s最初進入是因為我想“步入”該功能get_path()并停止。然后我輸入n一次以“保持本地”或“跳過”任何其他函數調用,然后按Enter重復該n命令,直到到達最后一個源代碼行。
$ ./example3.py > /code/example3.py(14)
注意行--Call--和--Return--。這是 pdb,讓您知道執行停止的原因。n(next) 和s(step) 將在函數返回之前停止。這就是為什么你會看到--Return--上面的線條。
還要注意上面->'.'第一個之后的行尾--Return--:
--Return-- > /code/example3.py(9)get_path()->'.' -> return head (Pdb)
當 pdb 在函數返回之前停止在函數末尾時,它還會為您打印返回值。在這個例子中,它是'.'.
列出源代碼
不要忘記命令ll(長列表:列出當前函數或框架的整個源代碼)。當您單步執行不熟悉的代碼或只想查看整個函數的上下文時,這真的很有幫助。
下面是一個例子:
$ ./example3.py > /code/example3.py(14)
要查看更短的代碼片段,請使用命令l(list)。如果沒有參數,它將在當前行周圍打印 11 行或繼續上一個列表。傳遞參數.以始終在當前行周圍列出 11 行:l .
$ ./example3.py > /code/example3.py(14)
使用斷點
斷點非常方便,可以為您節省大量時間。無需單步執行您不感興趣的數十行,只需在要調查的位置創建一個斷點即可。或者,您還可以告訴 pdb 僅在特定條件為真時中斷。
使用命令b(break) 設置斷點。您可以指定停止執行的行號或函數名稱。
中斷的語法是:
b(reak) [ ([filename:]lineno | function) [, condition] ]
如果filename:未在行號之前指定lineno,則使用當前源文件。
請注意,可選的第二個參數b:condition。這是非常強大的。想象一下這樣一種情況,您僅在特定條件存在時才想中斷。如果您將 Python 表達式作為第二個參數傳遞,則當表達式的計算結果為 true 時 pdb 將中斷。我們將在下面的示例中執行此操作。
在這個例子中,有一個實用程序模塊util.py。讓我們在函數中設置一個斷點來停止執行get_path()。
這是主腳本的來源example4.py:
#!/usr/bin/env python3 import util filename = __file__ import pdb; pdb.set_trace() filename_path = util.get_path(filename) print(f'path = {filename_path}')
這是實用程序模塊的來源util.py:
def get_path(filename): """Return file's path or empty string if no path.""" import os head, tail = os.path.split(filename) return head
首先,讓我們使用源文件名和行號設置斷點:
$ ./example4.py > /code/example4.py(7)
命令c(continue) 繼續執行,直到找到斷點。
接下來,讓我們使用函數名稱設置斷點:
$ ./example4.py > /code/example4.py(7)
b不帶參數輸入以查看所有斷點的列表:
(Pdb) b Num Type Disp Enb Where 1 breakpoint keep yes at /code/util.py:1 (Pdb)
您可以使用命令disable bpnumber和禁用和重新啟用斷點enable bpnumber。bpnumber是斷點列表第一列中的斷點編號Num。注意Enb列的值變化:
(Pdb) disable 1 Disabled breakpoint 1 at /code/util.py:1 (Pdb) b Num Type Disp Enb Where 1 breakpoint keep no at /code/util.py:1 (Pdb) enable 1 Enabled breakpoint 1 at /code/util.py:1 (Pdb) b Num Type Disp Enb Where 1 breakpoint keep yes at /code/util.py:1 (Pdb)
要刪除斷點,請使用命令cl(clear):
cl(ear) filename:lineno cl(ear) [bpnumber [bpnumber...]]
現在讓我們使用 Python 表達式來設置斷點。想象一下這樣一種情況:只有當您的問題函數收到某個輸入時,您才想中斷。
在此示例場景中,get_path()函數在收到相對路徑時失敗,即文件的路徑不以/.?在這種情況下,我將創建一個計算結果為 true 的表達式,并將其b作為第二個參數傳遞給:
$ ./example4.py > /code/example4.py(7)
創建上面的斷點并輸入c繼續執行后,當表達式計算為真時,pdb 停止。命令a(args) 打印當前函數的參數列表。
在上面的示例中,當您使用函數名稱而不是行號設置斷點時,請注意表達式應僅使用函數參數或在輸入函數時可用的全局變量。否則,斷點將停止在函數中執行,而不管表達式的值如何。
如果您需要使用變量名位于函數內的表達式來中斷,即變量名不在函數的參數列表中,請指定行號:
$ ./example4.py > /code/example4.py(7)
您還可以使用命令設置臨時斷點tbreak。當它第一次被擊中時它會自動移除。它使用與b.
繼續執行
到目前為止,我們已經研究了使用n(next) 和s(step) 單步執行代碼以及使用b(break) 和c(continue)使用斷點。
還有一個相關的命令:(unt直到)。
用于unt像 一樣繼續執行c,但在比當前行大的下一行停止。有時unt使用起來更方便、更快捷,這正是您想要的。我將通過下面的示例來演示這一點。
我們先來看看語法和描述unt:
命令
句法
描述
unt
unt (il) [lineno]
如果沒有lineno,則繼續執行,直到到達編號大于當前編號的行。使用lineno,繼續執行,直到到達數字大于或等于該數字的行。在這兩種情況下,也在當前幀返回時停止。
繼續執行
到目前為止,我們已經研究了使用n(next) 和s(step) 單步執行代碼以及使用b(break) 和c(continue)使用斷點。
還有一個相關的命令:(unt直到)。
用于unt像 一樣繼續執行c,但在比當前行大的下一行停止。有時unt使用起來更方便、更快捷,這正是您想要的。我將通過下面的示例來演示這一點。
我們先來看看語法和描述unt:
根據您是否傳遞行號參數lineno,unt可以以兩種方式運行:
如果沒有lineno,則繼續執行,直到到達編號大于當前編號的行。這類似于n(下一個)。這是執行和“跳過”代碼的另一種方式。之間的差n和unt是unt達到與一個大于當前一個的線,只有當停止。n將在下一個邏輯執行的行處停止。
使用lineno,繼續執行,直到到達數字大于或等于該數字的行。這就像c(繼續)帶有行號參數。
在這兩種情況下,unt在當前幀(函數)返回時停止,就像n(next) 和s(step) 一樣。
需要注意的主要行為unt是,當達到大于或等于當前或指定行的行號時,它將停止。
unt當您想繼續執行并在當前源文件中更遠的地方停止時使用。您可以將其視為n(next) 和b(break)的混合體,具體取決于您是否傳遞行號參數。
在下面的示例中,有一個帶有循環的函數。在這里,您希望繼續執行代碼并在循環后停止,而不是單步執行循環的每次迭代或設置斷點:
這是示例源example4unt.py:
#!/usr/bin/env python3 import os def get_path(fname): """Return file's path or empty string if no path.""" import pdb; pdb.set_trace() head, tail = os.path.split(fname) for char in tail: pass # Check filename char return head filename = __file__ filename_path = get_path(filename) print(f'path = {filename_path}')
和控制臺輸出使用unt:
$ ./example4unt.py > /code/example4unt.py(9)get_path() -> head, tail = os.path.split(fname) (Pdb) ll 6 def get_path(fname): 7 """Return file's path or empty string if no path.""" 8 import pdb; pdb.set_trace() 9 -> head, tail = os.path.split(fname) 10 for char in tail: 11 pass # Check filename char 12 return head (Pdb) unt > /code/example4unt.py(10)get_path() -> for char in tail: (Pdb) > /code/example4unt.py(11)get_path() -> pass # Check filename char (Pdb) > /code/example4unt.py(12)get_path() -> return head (Pdb) p char, tail ('y', 'example4unt.py')
該ll命令首先用于打印函數的源代碼,然后是unt.?pdb 會記住上次輸入的命令,所以我只需按下Enter即可重復該unt命令。這將繼續執行代碼,直到到達比當前行大的源代碼行。
請注意,在上面的控制臺輸出中,pdb 僅在行10和11.?由于unt已使用,因此僅在循環的第一次迭代中停止執行。但是,循環的每次迭代都被執行。這可以在輸出的最后一行進行驗證。該char變量的值'y'等于在最后一個字符tail的值'example4unt.py'。
顯示表達式
與使用pand打印表達式類似pp,您可以使用該命令display [expression]告訴 pdb 在執行停止時自動顯示表達式的值(如果它發生更改)。使用該命令undisplay [expression]清除顯示表達式。
以下是這兩個命令的語法和說明:
下面是一個示例,example4display.py演示了它在循環中的使用:
$ ./example4display.py > /code/example4display.py(9)get_path() -> head, tail = os.path.split(fname) (Pdb) ll 6 def get_path(fname): 7 """Return file's path or empty string if no path.""" 8 import pdb; pdb.set_trace() 9 -> head, tail = os.path.split(fname) 10 for char in tail: 11 pass # Check filename char 12 return head (Pdb) b 11 Breakpoint 1 at /code/example4display.py:11 (Pdb) c > /code/example4display.py(11)get_path() -> pass # Check filename char (Pdb) display char display char: 'e' (Pdb) c > /code/example4display.py(11)get_path() -> pass # Check filename char display char: 'x' [old: 'e'] (Pdb) > /code/example4display.py(11)get_path() -> pass # Check filename char display char: 'a' [old: 'x'] (Pdb) > /code/example4display.py(11)get_path() -> pass # Check filename char display char: 'm' [old: 'a']
在上面的輸出中,pdb 會自動顯示char變量的值,因為每次遇到斷點時,它的值都會發生變化。有時這很有幫助并且正是您想要的,但是還有另一種使用display.
您可以display多次輸入以構建表達式監視列表。這比p.?添加您感興趣的所有表達式后,只需輸入display即可查看當前值:
$ ./example4display.py > /code/example4display.py(9)get_path() -> head, tail = os.path.split(fname) (Pdb) ll 6 def get_path(fname): 7 """Return file's path or empty string if no path.""" 8 import pdb; pdb.set_trace() 9 -> head, tail = os.path.split(fname) 10 for char in tail: 11 pass # Check filename char 12 return head (Pdb) b 11 Breakpoint 1 at /code/example4display.py:11 (Pdb) c > /code/example4display.py(11)get_path() -> pass # Check filename char (Pdb) display char display char: 'e' (Pdb) display fname display fname: './example4display.py' (Pdb) display head display head: '.' (Pdb) display tail display tail: 'example4display.py' (Pdb) c > /code/example4display.py(11)get_path() -> pass # Check filename char display char: 'x' [old: 'e'] (Pdb) display Currently displaying: char: 'x' fname: './example4display.py' head: '.' tail: 'example4display.py'
Python Caller ID
在最后一節中,我們將建立在我們迄今為止所學的基礎上,并以不錯的回報結束。我使用名稱“來電顯示”來指代電話系統的來電顯示功能。這正是本示例演示的內容,只是它應用于 Python。
這是主腳本的來源example5.py:
#!/usr/bin/env python3 import fileutil def get_file_info(full_fname): file_path = fileutil.get_path(full_fname) return file_path filename = __file__ filename_path = get_file_info(filename) print(f'path = {filename_path}')
這是實用程序模塊fileutil.py:
def get_path(fname): """Return file's path or empty string if no path.""" import os import pdb; pdb.set_trace() head, tail = os.path.split(fname) return head
在這種情況下,假設有一個大型代碼庫,其中包含一個實用程序模塊中的函數,get_path()使用無效輸入調用該函數。但是,它是從不同包中的許多地方調用的。
How do you find who the caller is?
使用命令w(where) 打印堆棧跟蹤,最新的幀位于底部:
$ ./example5.py > /code/fileutil.py(5)get_path() -> head, tail = os.path.split(fname) (Pdb) w /code/example5.py(12)
如果這看起來令人困惑,或者您不確定堆棧跟蹤或框架是什么,請不要擔心。我將在下面解釋這些術語。這并不像聽起來那么困難。
由于最近的幀在底部,從那里開始并從下往上閱讀。查看以 開頭的行->,但跳過第一個實例,因為它pdb.set_trace()是用于在函數中輸入 pdb 的位置get_path()。在此示例中,調用該函數的源代碼行get_path()是:
-> file_path = fileutil.get_path(full_fname)
每個上面的行->包含文件名、行號(在括號中)和源行所在的函數名。所以調用者是:
/code/example5.py(7)get_file_info() -> file_path = fileutil.get_path(full_fname)
在這個用于演示目的的小示例中,這并不奇怪,但想象一下一個大型應用程序,您在其中設置了一個帶有條件的斷點,以確定錯誤輸入值的來源。
現在我們知道如何找到調用者了。
但是這個堆棧跟蹤和框架的東西呢?
一個堆棧跟蹤只是所有的Python已經創建的跟蹤函數調用的幀列表。框架是 Python 在調用函數時創建并在函數返回時刪除的數據結構。堆棧只是在任何時間點的幀或函數調用的有序列表。(函數調用)堆棧在應用程序的整個生命周期中隨著函數被調用然后返回而增長和縮小。
打印時,這個有序的幀列表,即堆棧,稱為堆棧跟蹤。您可以隨時通過輸入命令來查看它w,就像我們在上面找到調用者一樣。
有關詳細信息,請參閱維基百科上的此調用堆棧文章。
為了更好地理解并充分利用 pdb,讓我們更仔細地查看以下幫助w:
(Pdb) h w w(here) Print a stack trace, with the most recent frame at the bottom. An arrow indicates the "current frame", which determines the context of most commands. 'bt' is an alias for this command.
pdb 的“當前幀”是什么意思?
將當前幀視為 pdb 已停止執行的當前函數。換句話說,當前幀是您的應用程序當前暫停的位置,并用作 pdb 命令p(如(print))的“參考幀”?。
p和其他命令將在需要時使用當前幀作為上下文。在 的情況下p,當前幀將用于查找和打印變量引用。
當 pdb 打印堆棧跟蹤時,箭頭>指示當前幀。
這有什么用?
您可以使用u(向上)和d(向下)兩個命令來更改當前幀。結合p,這允許您在任何幀中調用堆棧的任何點檢查應用程序中的變量和狀態。
以下是這兩個命令的語法和說明:
讓我們看一個使用uandd命令的例子。在這種情況下,我們要檢查變量full_fname局部于功能get_file_info()在example5.py。為此,我們必須使用以下命令將當前幀向上更改一級u:
$ ./example5.py > /code/fileutil.py(5)get_path() -> head, tail = os.path.split(fname) (Pdb) w /code/example5.py(12)
該呼叫pdb.set_trace()是在fileutil.py該函數get_path(),因此當前幀的初始設置那里。您可以在上面的第一行輸出中看到它:
> /code/fileutil.py(5)get_path()
為了訪問和打印full_fname函數get_file_info()中的局部變量example5.py,該命令u用于向上移動一級:
(Pdb) u > /code/example5.py(7)get_file_info() -> file_path = fileutil.get_path(full_fname)
請注意,在u上面的輸出中,pdb>在第一行的開頭打印了箭頭。這是 pdb,讓您知道框架已更改,并且此源位置現在是當前框架。full_fname現在可以訪問該變量。此外,重要的是要意識到從->第 2 行開始的源代碼行已被執行。由于幀被向上移動到堆棧中,fileutil.get_path()因此被調用。使用u,我們搬到了堆棧(從某種意義上說,早在時間)的功能example5.get_file_info()在那里fileutil.get_path()被調用。
繼續這個例子,在full_fname被打印之后,當前幀使用 移動到它的原始位置d,并且局部變量fnameinget_path()被打印。
如果我們愿意,我們可以通過將count參數傳遞給uor來一次移動多個幀d。例如,我們可以example5.py通過輸入u 2以下內容進入模塊級別:
$ ./example5.py > /code/fileutil.py(5)get_path() -> head, tail = os.path.split(fname) (Pdb) u 2 > /code/example5.py(12)
當您進行調試和思考許多不同的事情時,很容易忘記您身在何處。請記住,您始終可以使用恰當命名的命令w(where) 來查看執行暫停的位置以及當前幀是什么。
基本的 pdb 命令
一旦您花一點時間使用 pdb,您就會意識到一點點知識大有幫助。該h命令始終提供幫助。
只需輸入h或help
為了快速參考,以下是基本命令列表:
使用 pdb 調試 Python:結論
在本教程中,我們介紹了 pdb 的一些基本和常見用法:
打印表達式
使用n(next) 和s(step) 單步執行代碼
使用斷點
繼續執行unt(直到)
顯示表達式
查找函數的調用者
我希望它對你有幫助。如果您想了解更多信息,請參閱:
在您附近的 pdb 提示符處查看 pdb 的完整文檔:?(Pdb) h pdb
Python 的 pdb 文檔
示例中使用的源代碼可以在相關的GitHub 存儲庫中找到。請務必查看我們的可打印 pdb 命令參考,您可以在調試時將其用作備忘單:
此外,如果您想嘗試基于 GUI 的 Python 調試器,請閱讀我們的Python IDE 和編輯器指南,了解哪些選項最適合您。
Python 網絡
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。