Sphinx 的 autodoc 載入問題

為了使用 autodoc, 我在 conf.py 裡這樣寫:

sys.path.append('/usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/ext')
extensions = ['graphviz', 'autodoc']

結果跑出:

autodoc documenter <class 'autodoc.ModuleDocumenter'> must be a subclass of Documenter

正確的寫法是:

extensions = ['sphinx.ext.graphviz', 'sphinx.ext.autodoc']

開 pdb 追下去, 發現是 sphinx.application 和 sphinx.ext.autodoc 之間 circular import 造成的問題。

在 sphinx.application 裡:

def add_autodocumenter(self, cls):
    from sphinx.ext import autodoc
    autodoc.add_documenter(cls)
    self.add_directive('auto' + cls.objtype, autodoc.AutoDirective)

會載入 sphinx.ext.autodoc, 但我原本的寫法, 讓 sphinx 透過 __import__ 載入 autodoc, 結果產生兩個不同的物件, 但都是 autodoc, 結果剩下的一些程式操作, 就發生了悲劇。

在 autodoc 裡的這段程式就丟出 exception:

if not issubclass(cls, Documenter):
    raise ExtensionError('autodoc documenter %r must be a subclass '

cls (即 ModuleDocumenter) 真的不是 Documenter 的 subclass, 因為這個 ModuleDocumenter 是其中一個 autodoc 的, 而 Documenter 卻是另一個 autodoc 的。

若硬註解掉這兩行, 雖然 autodoc 的部份指令能用, 卻也會部份失效, 像 :members: 就會完全沒效果, 原因也是出在有兩個 autodoc 上, 結果找 members 的那個 autodoc 的 AutoDirective._registry 是空的, 造成它找不到能處理 members 的 Documenter。

備註: autodoc.AutoDirective 負責處理 directive auto*。AutoDirective 依指令名稱從 _registry 中找出適用的 class 來處理, 比方說 automodule 就會找 ModuleDocumenter 來處理。正常的程序下, autodoc.setup() 會填好 AutoDirective._registry, 但在上面說的錯誤設定下, 會變成 sphinx.ext.autodoc.AutoDirective._registry 有設好, 而 autodoc.AutoDirective._registry 是空的。

下面是用 pdb 看設錯情況發生的狀況, 滿有趣的:

> /usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/ext/autodoc.py(1177)run()
-> documenter.generate(more_content=self.content)
  /usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/ext/autodoc.py(715)generate()
-> self.document_members(all_members)
  /usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/ext/autodoc.py(981)document_members()
-> ModuleLevelDocumenter.document_members(self, all_members)
  /usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/ext/autodoc.py(609)document_members()
-> for (mname, member, isattr) in self.filter_members(members, want_all):
(Pdb) l
1172            # process the options with the selected documenter's option_spec
1173            self.genopt = Options(assemble_option_dict(
1174                self.options.items(), doc_class.option_spec))
1175            # generate the output
1176            documenter = doc_class(self, self.arguments[0])
1177 ->         documenter.generate(more_content=self.content)
1178            if not self.result:
1179                return self.warnings
1180
1181            # record all filenames as dependencies -- this will at least
1182            # partially make automatic invalidation possible
(Pdb) documenter.__module__
'autodoc'
(Pdb) id(AutoDirective)
21622640
(Pdb) down
> /usr/lib/python2.5/site-packages/Sphinx-1.0.7-py2.5.egg/sphinx/ext/autodoc.py(715)generate()
-> self.document_members(all_members)
(Pdb) id(AutoDirective)
20956032
(Pdb) import sys
(Pdb) id(sys.modules['autodoc'].AutoDirective)
20956032
(Pdb) id(sys.modules['sphinx.ext.autodoc'].AutoDirective)
21622640

表示一開始在 sphinx.ext.autodoc, 但 documenter 是從 autodoc 取來的, 結果就無聲無息的掛了。

留言

這個網誌中的熱門文章

(C/C++ ) 如何在 Linux 上使用自行編譯的第三方函式庫

熟悉系統工具好處多多

virtualbox 使用 USB 裝置