#6 ipdb模塊源代碼解讀

前言

很久不見,你們最近可好😏。經過前幾節的學習,相信你已經掌握了面向對象的大量知識,可是光知道是不夠的,須要本身多寫、多看,學一門語言無非不過這兩種祕訣嘛。所以本篇博文帶着你們剖析一次源代碼,剖析對象爲代碼調試模塊:ipdb。爲何選擇這個模塊呢?由於下一次的博文計劃寫Python代碼調試的啦~~Go!!!html

1、ipdb介紹

1.1 ipdb介紹

ipdb是一款調試代碼的第三方模塊python

我想這一句話就給出了ipdb的全部信息了哇shell

1.2 ipdb安裝

既然是第三方模塊,那麼就須要本身來安裝,使用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

2、源代碼剖析

2.1 源代碼位置

想要剖析這一個模塊,首先應該找到源代碼的位置,因爲模塊是由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

2.2 源代碼文件剖析

若是你仔細觀察的話,你會發現每個模塊基本是都是兩個文件夾,一個文件夾是模塊自己,另外一個是以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

2.3 源代碼剖析

__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)的,下次見!

相關文章
相關標籤/搜索