Quantcast
Channel: CodeSection,代码区,Python开发技术文章_教程 - CodeSec
Viewing all articles
Browse latest Browse all 9596

[英] 如何写一个 Python 文档生成器

$
0
0

[英] 如何写一个 Python 文档生成器
How to write your own python documentation generator Introspection with the inspectmodule

In my early days with Python, one of the things that I really liked was using the built-in help function to examine classes and methods while sitting at the interpreter, trying to determine what to type next. This function imports an object and walks through its members, pulling out docstrings and generating manpage-like output to help give you an idea of how to use the object it was examining.

The beauty about it being built into the standard library is that with output being generated straight from code, it indirectly emphasizes a coding style for lazy people like me, who want to do as little extra work as possible to maintain documentation. Especially if you already choose straight forward names for your variables and functions. This style involves things like adding docstrings to your functions and classes, as well as properly identifying private and protected members by prefixing them with underscores.

Output from running help(list) at the Python interpreter

The help function is actually using the pydoc module to generate its output, which is also runnable from the command line to produce a text or html representation of any importable module in your path.

A little while ago I needed to write more detailed, formal, design documentation and ― being a fan of Markdown ― I decided to play around with mkdocs to see if I could get what I was looking for. This module makes it easy to turn your markdown text into nicely styled web pages and can serve them up as you make changes before publishing to an official location. It comes with a template for readthedocs and even provides an easy command line interface to push your changes into GitHub Pages if you wish to go down that route.

Having completed my initial batch of text describing design decisions and considerations, I wanted to add details on the actual interface methods I was developing and exposing to the other modules in the project. Since I already wrote the definitions for most of those functions, I intended to automatically generate the reference pages from my source, and I wanted it in markdown so it could render into html along with the rest of my documentation whenever I ran mkdocs.

However, it turns out there is no default way of generating markdown from source with mkdocs, but there are plugins. After googling and researching a bit more, I was not content with the projects or plugins I found ― lots of things are out of date, not maintained, or the output was just not what I was looking for ― so I decided to write my own. I thought it would be another interesting foray into learning a little more about a module I used a little while ago when building a debugger for one of my previous articles (see Hacking together a Simple Graphical Python Debugger ): the inspect module.

“The inspect module provides several useful functions to help get information about live objects… ” ― PythonDocs

Inspect this!

Originating from the standard library, inspect not only lets you look at lower level python frame and code objects, it also provides a number of methods for examining modules and classes, helping you find the items that may be of interest. It’s what pydoc uses to generate the help files mentioned previously.

Browsing through the online documentation, you’ll find a number of methods relevant to our adventure. The most important ones being getmembers() , getdoc() and signature() , as well as the many is... functions that serve as filters to getmembers . With these we can easily iterate through functions, including distinctions between generators and coroutines, and recurse into any classes and their internalsasneeded.

Importing thecode

If we’re going to inspect an object, no matter what it is, the first thing to do is provide a mechanism with which to import it into our namespace. Why even talk about imports? Depending on what you want to do, there’s a number of items to worry about, including virtual environments, custom code, standard modules and reused names. Things can get confusing, and getting it wrong can lead to some frustrating moments trying to figure things out.

We do have a few options here, the more complete one being a reuse of safeimport() directly from pydoc , which takes care of a number of special cases for us and raises a pretty ErrorDuringImport exception when things break. However, it’s also possible to simply run __import__(modulename) ourselvesifwehavemorecontroloverourenvironment.

Another item to keep in mind is the path from which code executes. There may be a need to sys.path.append() a directory in order to access the module we’re looking for. My use case is to execute from the command line and within a directory that’s in the path of the module being examined, so I added the current directory to sys.path, which was enough to take care of the typical import path issues.

Keeping this in mind, our import function would look something like this:

def generatedocs(module): try: sys.path.append(os.getcwd()) # Attempt import mod = safeimport(module) if mod is None: print("Module not found") # Module imported correctly, let's create the docs return getmarkdown(mod) except ErrorDuringImport as e: print("Error while trying to import " + module) Deciding on theoutput

Before continuing on, you’ll want to have a mental picture of how to organize the markdown output being generated. Do you want a shallow reference that does not recurse into custom classes? Which methods do we want to include? What about built-ins? Or _ and __ methods? How should we present function signatures? Should we pull annotations?

My choices were as follows:

One .md file per run with information generated from recursing into any child classes of the object being inspecting. Only include custom code that I’ve created, nothing from imported modules. The output must be identified with 2nd level markdown headers ( ## ) per item. All headers must include the full path of the item being described ( module.class.subclass.function ) Include the full function signature as pre-formatted text. Provide anchors for each header to easily link into the docs, as well as within the docs themselves. Any function that starts with

Viewing all articles
Browse latest Browse all 9596

Trending Articles