很久不見,你們最近可好😏。經過前幾節的學習,相信你已經掌握了面向對象的大量知識,可是光知道是不夠的,須要本身多寫、多看,學一門語言無非不過這兩種祕訣嘛。所以本篇博文帶着你們剖析一次源代碼,剖析對象爲代碼調試模塊:ipdb。爲何選擇這個模塊呢?由於下一次的博文計劃寫Python代碼調試的啦~~Go!!!html
ipdb是一款調試代碼的第三方模塊python
我想這一句話就給出了ipdb的全部信息了哇shell
既然是第三方模塊,那麼就須要本身來安裝,使用pip便可,在命令行輸入:express
pip install ipdb
測試安裝是否成功,在命令行輸入:緩存
python -m ipdb
若是安裝成功則會輸出如下內容:session
usage: python -m ipdb [-c command] ... pyfile [arg] ... Debug the Python program given by pyfile. Initial commands are read from .pdbrc files in your home directory and in the current directory, if they exist. Commands supplied with -c are executed after commands from .pdbrc files. To let the script run until an exception occurs, use "-c continue". To let the script run up to a given line X in the debugged file, use "-c 'until X'" ipdb version 0.10.3.
若是安裝失敗請從新pip安裝或者換用其餘方法,以前介紹過,這裏就不列舉了app
想要剖析這一個模塊,首先應該找到源代碼的位置,因爲模塊是由pip安裝的,因此可使用pip查看模塊的詳細信息,在命令行中輸入:ide
pip show ipdb
輸出的詳細信息中,有一行Location信息,每一個人的位置可能不一樣,以本身的爲準,這裏輸出我本身的位置:函數
Location: /Users/minutesheep/.pyenv/versions/3.5.2/Python.framework/Versions/3.5/lib/python3.5/site-packages
進入上面👆所示的目錄中,會發現site-packages目錄裏有許多模塊,ipdb模塊的源代碼有兩個,一個是 ipdb ,另外一個是 ipdb-0.11-py3.5.egg-info post
若是你仔細觀察的話,你會發現每個模塊基本是都是兩個文件夾,一個文件夾是模塊自己,另外一個是以info結尾的文件夾,下面以ipdb模塊講解:
ipdb 文件夾
這個文件夾裏面存放着ipdb模塊的源代碼,裏面有
__init__.py __main__.py
__pycache__ stdout.py
ipdb-0.11-py3.5.egg-info 文件夾
從名稱上就能夠看出這是一個存放信息的文件夾,裏面有
PKG-INFO dependency_links.txt installed-files.txt
top_level.txt SOURCES.txt entry_points.txt requires.txt zip-safe
PKG-INFO:內容是模塊的信息,包括模塊名、版本、依賴、做者、代碼地址、許可、描述、歷史版本變動信息
dependency_links.txt:內容是依賴模塊連接
installed-files.txt:內容是安裝這個模塊時安裝的文件
top_level.txt:內容是父親模塊
SOURCES.TXT:內容是整個模塊全部的文件
entry_points.txt:內容是程序入口語句
requires.txt:本模塊須要的其餘模塊
zip-safe:這個文件我也不清楚🤯
『防抄襲:讀者請忽略這段文字,文章做者是博客園的MinuteSheep』
__init__.py
1 # Copyright (c) 2007-2016 Godefroid Chapelle and ipdb development team 2 # 3 # This file is part of ipdb. 4 # Redistributable under the revised BSD license 5 # https://opensource.org/licenses/BSD-3-Clause 6 7 from ipdb.__main__ import set_trace, post_mortem, pm, run # noqa 8 from ipdb.__main__ import runcall, runeval, launch_ipdb_on_exception # noqa 9 10 from ipdb.stdout import sset_trace, spost_mortem, spm # noqa 11 from ipdb.stdout import slaunch_ipdb_on_exception # noqa
__init__.py究竟是個什麼文件呢?爲何Python項目中老是會出現這個詭異的文件呢?
__init__.py實際上是將這個文件夾變成一個Python模塊,方便之後導入。
每當咱們使用import語句時,其實導入的就是這個模塊的__init__.py文件。
一般一個模塊的許多方法並不會寫在同一個文件中,而是會有分類的寫入不一樣的文件中,最後將這個模塊的全部方法都一次性寫入__init__.py文件中(至關於爲全部方法提供一個公共接口),導入的時候將會方便許多。
本模塊的__init__.py文件中,前5行是註釋信息,這裏就不翻譯了;第7行開始,進入正式代碼,能夠看到從__main__.py文件中導入了許多種方法,以後又從stdout.py中導入了許多方法
__main__.py
這個文件就是整個模塊的主程序了,源代碼就放在這個文件中
1 # Copyright (c) 2011-2016 Godefroid Chapelle and ipdb development team 2 # 3 # This file is part of ipdb. 4 # Redistributable under the revised BSD license 5 # https://opensource.org/licenses/BSD-3-Clause 6 7 8 from IPython.terminal.embed import InteractiveShellEmbed 9 from IPython.terminal.ipapp import TerminalIPythonApp 10 from IPython.core.debugger import BdbQuit_excepthook 11 from IPython import get_ipython 12 import os 13 import sys 14 15 from contextlib import contextmanager 16 17 __version__ = "0.10.3" 18 19 20 shell = get_ipython() 21 if shell is None: 22 # Not inside IPython 23 # Build a terminal app in order to force ipython to load the 24 # configuration 25 ipapp = TerminalIPythonApp() 26 # Avoid output (banner, prints) 27 ipapp.interact = False 28 ipapp.initialize([]) 29 shell = ipapp.shell 30 else: 31 # Running inside IPython 32 33 # Detect if embed shell or not and display a message 34 if isinstance(shell, InteractiveShellEmbed): 35 sys.stderr.write( 36 "\nYou are currently into an embedded ipython shell,\n" 37 "the configuration will not be loaded.\n\n" 38 ) 39 40 # Let IPython decide about which debugger class to use 41 # This is especially important for tools that fiddle with stdout 42 debugger_cls = shell.debugger_cls 43 def_colors = shell.colors 44 45 46 def _init_pdb(context=3, commands=[]): 47 try: 48 p = debugger_cls(def_colors, context=context) 49 except TypeError: 50 p = debugger_cls(def_colors) 51 p.rcLines.extend(commands) 52 return p 53 54 55 def wrap_sys_excepthook(): 56 # make sure we wrap it only once or we would end up with a cycle 57 # BdbQuit_excepthook.excepthook_ori == BdbQuit_excepthook 58 if sys.excepthook != BdbQuit_excepthook: 59 BdbQuit_excepthook.excepthook_ori = sys.excepthook 60 sys.excepthook = BdbQuit_excepthook 61 62 63 def set_trace(frame=None, context=3): 64 wrap_sys_excepthook() 65 if frame is None: 66 frame = sys._getframe().f_back 67 p = _init_pdb(context).set_trace(frame) 68 if p and hasattr(p, 'shell'): 69 p.shell.restore_sys_module_state() 70 71 72 def post_mortem(tb=None): 73 wrap_sys_excepthook() 74 p = _init_pdb() 75 p.reset() 76 if tb is None: 77 # sys.exc_info() returns (type, value, traceback) if an exception is 78 # being handled, otherwise it returns None 79 tb = sys.exc_info()[2] 80 if tb: 81 p.interaction(None, tb) 82 83 84 def pm(): 85 post_mortem(sys.last_traceback) 86 87 88 def run(statement, globals=None, locals=None): 89 _init_pdb().run(statement, globals, locals) 90 91 92 def runcall(*args, **kwargs): 93 return _init_pdb().runcall(*args, **kwargs) 94 95 96 def runeval(expression, globals=None, locals=None): 97 return _init_pdb().runeval(expression, globals, locals) 98 99 100 @contextmanager 101 def launch_ipdb_on_exception(): 102 try: 103 yield 104 except Exception: 105 e, m, tb = sys.exc_info() 106 print(m.__repr__(), file=sys.stderr) 107 post_mortem(tb) 108 finally: 109 pass 110 111 112 _usage = """\ 113 usage: python -m ipdb [-c command] ... pyfile [arg] ... 114 115 Debug the Python program given by pyfile. 116 117 Initial commands are read from .pdbrc files in your home directory 118 and in the current directory, if they exist. Commands supplied with 119 -c are executed after commands from .pdbrc files. 120 121 To let the script run until an exception occurs, use "-c continue". 122 To let the script run up to a given line X in the debugged file, use 123 "-c 'until X'" 124 ipdb version %s.""" % __version__ 125 126 127 def main(): 128 import traceback 129 import sys 130 import getopt 131 132 try: 133 from pdb import Restart 134 except ImportError: 135 class Restart(Exception): 136 pass 137 138 opts, args = getopt.getopt(sys.argv[1:], 'hc:', ['--help', '--command=']) 139 140 if not args: 141 print(_usage) 142 sys.exit(2) 143 144 commands = [] 145 for opt, optarg in opts: 146 if opt in ['-h', '--help']: 147 print(_usage) 148 sys.exit() 149 elif opt in ['-c', '--command']: 150 commands.append(optarg) 151 152 mainpyfile = args[0] # Get script filename 153 if not os.path.exists(mainpyfile): 154 print('Error:', mainpyfile, 'does not exist') 155 sys.exit(1) 156 157 sys.argv = args # Hide "pdb.py" from argument list 158 159 # Replace pdb's dir with script's dir in front of module search path. 160 sys.path[0] = os.path.dirname(mainpyfile) 161 162 # Note on saving/restoring sys.argv: it's a good idea when sys.argv was 163 # modified by the script being debugged. It's a bad idea when it was 164 # changed by the user from the command line. There is a "restart" command 165 # which allows explicit specification of command line arguments. 166 pdb = _init_pdb(commands=commands) 167 while 1: 168 try: 169 pdb._runscript(mainpyfile) 170 if pdb._user_requested_quit: 171 break 172 print("The program finished and will be restarted") 173 except Restart: 174 print("Restarting", mainpyfile, "with arguments:") 175 print("\t" + " ".join(sys.argv[1:])) 176 except SystemExit: 177 # In most cases SystemExit does not warrant a post-mortem session. 178 print("The program exited via sys.exit(). Exit status: ", end='') 179 print(sys.exc_info()[1]) 180 except: 181 traceback.print_exc() 182 print("Uncaught exception. Entering post mortem debugging") 183 print("Running 'cont' or 'step' will restart the program") 184 t = sys.exc_info()[2] 185 pdb.interaction(None, t) 186 print("Post mortem debugger finished. The " + mainpyfile + 187 " will be restarted") 188 189 190 if __name__ == '__main__': 191 main()
一會兒看到這麼長的代碼是否是蒙圈了🤪,遇到這種長的代碼,第一步就是在心理上打敗本身!要想成長,就要多看這種標準代碼,學習代碼思想,模仿代碼風格,這樣一步一步腳踏實地走下去,你本身寫出這樣優秀的代碼指日可待!
拿到這麼長的代碼,先大體瀏覽一下:
1-5行;註釋信息;8-15行:導入模塊;17行:定義版本變量;20-43行:運行一小段程序(一般是程序的配置);46-109行:定義若干函數;112-124行:定義字符串變量;127-187行:main函數;190-191:判斷主程序並運行
經過上面這種方法將程序分解掉,整個程序一會兒清晰明瞭,瞬間感受so easy~~~
來跟着我稍微詳細的走一遍整個程序的運行過程(具體的內容就不作介紹了,由於許多內容須要詳細的掌握IPython):
1.從IPthon導入四種方法,導入os和sys模塊,從contextlib導入contextmanager(這是一個裝飾器)
2.定義當前版本爲:0.10.3
3.得到一個ipython的shell環境
4.判斷這個shell是否存在:若是不存在,強制性的建立一個ipython環境;若是存在,則檢測其是否爲InteractiveShellEmbed的一個對象,若是是,則輸出標準錯誤語句「You are currently into an embedded ipython shell""the configuration will not be loaded."
5.使用當前IPython的主題和顏色
6.執行第112行語句,定義_usage字符串
7.執行第190行語句,判斷是否爲__main__,是的話運行main函數
8.執行127行語句,運行main函數
9..........
以上就是稍微詳細的運行過程,感興趣的小夥伴能夠繼續深刻到每一步是如何運行的,因爲篇幅關係,我就再也不深刻了。
stdout.py
1 import sys 2 from contextlib import contextmanager 3 from IPython.utils import io 4 from .__main__ import set_trace 5 from .__main__ import post_mortem 6 7 8 def update_stdout(): 9 # setup stdout to ensure output is available with nose 10 io.stdout = sys.stdout = sys.__stdout__ 11 12 13 def sset_trace(frame=None, context=3): 14 update_stdout() 15 if frame is None: 16 frame = sys._getframe().f_back 17 set_trace(frame, context) 18 19 20 def spost_mortem(tb=None): 21 update_stdout() 22 post_mortem(tb) 23 24 25 def spm(): 26 spost_mortem(sys.last_traceback) 27 28 29 @contextmanager 30 def slaunch_ipdb_on_exception(): 31 try: 32 yield 33 except Exception: 34 e, m, tb = sys.exc_info() 35 print(m.__repr__(), file=sys.stderr) 36 spost_mortem(tb) 37 finally: 38 pass
這個文件是ipdb模塊的另外一個文件,編寫項目時,不會將全部方法都寫入同一個文件中的,而是將不一樣的方法分類放入不一樣的文件中,這個文件的內容就不作詳細講解了。
__pycache__
這是一個文件夾,裏面存放着許多以.pyc結尾的文件,這些文件時什麼呢?
其實從文件夾的名稱就能夠看出這些是緩存文件。
Python程序爲了加快程序的運行速度,在第一次導入模塊後,會在本模塊目錄中生成__pycache__的緩存文件夾,裏面存放着編譯過的文件;下一次再次導入這個模塊時,直接執行pyc文件,大大加快了程序的運行速度;每當模塊裏的py文件的修改時間發生變化時,就會從新生成pyc文件。
以上就是ipdb模塊源代碼的剖析,相信你已經有了分析源代碼的能力了!下一篇博文將會記錄Python是如何調試代碼(debug)的,下次見!