首页 > 编程语言 >Python如何实现docstring

Python如何实现docstring

时间:2023-07-06 18:00:37浏览次数:47  
标签:__ co tok Python doc docstring 如何 str return

doc

Python语言从排版上看强制要求了一些书写规范,算是强制赋予了每个程序员一个"代码洁癖"。作为规范的一部分,可以在在类函数的开始增加注释,并且语言本身为这种注释做了"背书":可以通过help展示这个帮助文档的内容。
这个本来是Python一个很细小的功能,也是一个很有意思的语法糖(因为其它语言并不常见这种语法),所以还是可以看看这个语法的一些实现细节。
这里主要考虑的问题是文档如何存储的问题。其实识别这个注释字符串是一个语法层面的内容判断,存储文档字符串也并不麻烦(在运行时存储字符串是基本操作,各种属性名字符串操作不在话下)。更重要的是一个运行时的处理逻辑,最简单的想法就是增加一个虚拟机指令,用来安装文档信息。

测试可以看到,对于文件(模块)级别的文档,的确是通过抓们的STORE_NAME指令来完成的,但是对于函数的实现并没有对应的虚拟机指令来安装。

tsecer@harry: cat py_doc_str.py
"""file doc"""
def tsecer():
    """func doc"""
    pass

tsecer@harry: python3 -m dis py_doc_str.py
  1           0 LOAD_CONST               0 ('file doc')
              2 STORE_NAME               0 (__doc__)

  2           4 LOAD_CONST               1 (<code object tsecer at 0x7fa8ac8b0810, file "py_doc_str.py", line 2>)
              6 LOAD_CONST               2 ('tsecer')
              8 MAKE_FUNCTION            0
             10 STORE_NAME               1 (tsecer)
             12 LOAD_CONST               3 (None)
             14 RETURN_VALUE
tsecer@harry: python3 
>>> import dis, py_doc_str
>>> dis.dis(py_doc_str)
Disassembly of tsecer:
  4           0 LOAD_CONST               1 (None)
              2 RETURN_VALUE

>>> help(py_doc_str.tsecer)

>>> print(py_doc_str.__doc__)
file doc
>>> print(py_doc_str.tsecer.__doc__)
func doc
>>> 

语法

函数创建

从前面的虚拟机指令可以看到,def 函数执行的对应虚拟机指令是MAKE_FUNCTION,对应的动作会从代码的co_consts数组的第一个槽位(PyTuple_GetItem(consts, 0))中的内容,并把这个内容安装到定义函数的func_doc字段中(当然,这里检测了consts的数量是否大于0,并且第一个const是一个Unicode类型的对象)。

PyObject *
PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname)
{
///...
    consts = ((PyCodeObject *)code)->co_consts;
    if (PyTuple_Size(consts) >= 1) {
        doc = PyTuple_GetItem(consts, 0);
        if (!PyUnicode_Check(doc))
            doc = Py_None;
    }
    else
        doc = Py_None;
    Py_INCREF(doc);
    op->func_doc = doc;
///...
}

每个function对象它类型描述中,描述了对象的doc从狗结构的func_doc位置取,也就是前面设置的字符串位置。

/* Methods */

#define OFF(x) offsetof(PyFunctionObject, x)

static PyMemberDef func_memberlist[] = {
    {"__closure__",   T_OBJECT,     OFF(func_closure),
     RESTRICTED|READONLY},
    {"__doc__",       T_OBJECT,     OFF(func_doc), PY_WRITE_RESTRICTED},
    {"__globals__",   T_OBJECT,     OFF(func_globals),
     RESTRICTED|READONLY},
    {"__module__",    T_OBJECT,     OFF(func_module), PY_WRITE_RESTRICTED},
    {NULL}  /* Sentinel */
};

验证

可以看到,在生成的function定义中,consts是一个tuple类型(有序),并且第一个元素是约定的函数注释。

>>> dir(py_doc_str.tsecer)            
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> dir(py_doc_str.tsecer.__code__)            
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_kwonlyargcount', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']
>>> type(py_doc_str.tsecer.__code__.co_consts) 
<class 'tuple'>
>>> print(py_doc_str.tsecer.__code__.co_consts)
('func doc', None)
>>> 

parser

词法

在词法分析阶段,一个特殊的处理事判断如果是单引号的处理。可以看到,

  • 引号具体是使用单引号还是双引号并没有区别,只要结束位置相同即可。
  • 引号数量可以为1个,两个,最多三个。不论多少个,最后只识别相同符号(单引号/双引号)相同数量的符号作为结束。
  • 如果是单引号的情况下不接受中间换行。
  • 返回的词法单位是一个STRING类型。
static int
tok_get(struct tok_state *tok, char **p_start, char **p_end)
{
///...
        /* Get rest of string */
        while (end_quote_size != quote_size) {
            c = tok_nextc(tok);
            if (c == EOF) {
                if (quote_size == 3) {
                    tok->done = E_EOFS;
                }
                else {
                    tok->done = E_EOLS;
                }
                tok->cur = tok->inp;
                return ERRORTOKEN;
            }
            if (quote_size == 1 && c == '\n') {
                tok->done = E_EOLS;
                tok->cur = tok->inp;
                return ERRORTOKEN;
            }
            if (c == quote) {
                end_quote_size += 1;
            }
            else {
                end_quote_size = 0;
                if (c == '\\') {
                    tok_nextc(tok);  /* skip escaped char */
                }
            }
        }

        *p_start = tok->start;
        *p_end = tok->cur;
        return STRING;
    }

语法

语法分析阶段,字符串生成一个Str_kind类型节点。

/* Make a Str node, but decref the PyUnicode object being added. */
static expr_ty
make_str_node_and_del(PyObject **str, struct compiling *c, const node* n)
{
    PyObject *s = *str;
    *str = NULL;
    assert(PyUnicode_CheckExact(s));
    if (PyArena_AddPyObject(c->c_arena, s) < 0) {
        Py_DECREF(s);
        return NULL;
    }
    return Str(s, LINENO(n), n->n_col_offset, c->c_arena);
}
expr_ty
Str(string s, int lineno, int col_offset, PyArena *arena)
{
    expr_ty p;
    if (!s) {
        PyErr_SetString(PyExc_ValueError,
                        "field s is required for Str");
        return NULL;
    }
    p = (expr_ty)PyArena_Malloc(arena, sizeof(*p));
    if (!p)
        return NULL;
    p->kind = Str_kind;
    p->v.Str.s = s;
    p->lineno = lineno;
    p->col_offset = col_offset;
    return p;
}

doc_string

显然不是所有的字符节点都是docstring,只有一个block的第一条语句如果是Str_kind的话才算是doc-string,而这个就需要在需要支持docstring的上下文中自己判断是否把第一条语句的Str_kind作为docstring。可见下面的逻辑都是判断了是否是第一条语句。由于compiler_body只有在class和module中做了判断,所以也就只有这两种语法结构和函数定义中可以包含docstring。

static int
compiler_isdocstring(stmt_ty s)
{
    if (s->kind != Expr_kind)
        return 0;
    if (s->v.Expr.value->kind == Str_kind)
        return 1;
    if (s->v.Expr.value->kind == Constant_kind)
        return PyUnicode_CheckExact(s->v.Expr.value->v.Constant.value);
    return 0;
}


/* Compile a sequence of statements, checking for a docstring
   and for annotations. */

static int
compiler_body(struct compiler *c, asdl_seq *stmts)
{
    int i = 0;
    stmt_ty st;

    /* Set current line number to the line number of first statement.
       This way line number for SETUP_ANNOTATIONS will always
       coincide with the line number of first "real" statement in module.
       If body is empy, then lineno will be set later in assemble. */
    if (c->u->u_scope_type == COMPILER_SCOPE_MODULE &&
        !c->u->u_lineno && asdl_seq_LEN(stmts)) {
        st = (stmt_ty)asdl_seq_GET(stmts, 0);
        c->u->u_lineno = st->lineno;
    }
    /* Every annotated class and module should have __annotations__. */
    if (find_ann(stmts)) {
        ADDOP(c, SETUP_ANNOTATIONS);
    }
    if (!asdl_seq_LEN(stmts))
        return 1;
    st = (stmt_ty)asdl_seq_GET(stmts, 0);
    if (compiler_isdocstring(st) && c->c_optimize < 2) {
        /* don't generate docstrings if -OO */
        i = 1;
        VISIT(c, expr, st->v.Expr.value);
        if (!compiler_nameop(c, __doc__, Store))
            return 0;
    }
    for (; i < asdl_seq_LEN(stmts); i++)
        VISIT(c, stmt, (stmt_ty)asdl_seq_GET(stmts, i));
    return 1;
}

static int
compiler_function(struct compiler *c, stmt_ty s, int is_async)
{
///...
    st = (stmt_ty)asdl_seq_GET(body, 0);
    docstring = compiler_isdocstring(st);
///...
}

help的doc由来

读取的就是对象的__doc__属性。

###:@file:Python-3.6.0\Lib\inspect.py
def _finddoc(obj):
    if isclass(obj):
        for base in obj.__mro__:
            if base is not object:
                try:
                    doc = base.__doc__
                except AttributeError:
                    continue
                if doc is not None:
                    return doc
        return None

区别

在函数中不适用LOADDOC可以节省一条虚拟机指令。那么为什么模块不也采用这种方法呢?毕竟module也是默认作为一个函数的。 或许是因为类似于builtin这种模块是通过C代码创建而,而不是通过Python代码创建;或者是因为NameSpace也是一个module而天生没有docstring;或者只是历史遗留问题?

标签:__,co,tok,Python,doc,docstring,如何,str,return
From: https://www.cnblogs.com/tsecer/p/17532913.html

相关文章

  • Python3读写TOML文件
    TOML(Tom'sObvious,MinimalLanguage)是一种易于阅读和编写的配置文件格式。它的设计目标是提供一种简单而灵活的方式来表示配置数据,以便于人类阅读和编辑。基础示例#config.toml[server]host="localhost"port=8080[database]name="mydb"user="myuser"passwor......
  • Python常用命令总结
    1.print()默认是print(end='\n')  如果不想换行可以print(end='')2.使print内容变成一行print(end='\t')3.不设置指定位置,按默认顺序"{}{}".format("hello","world")  eg打印99乘法表print({}*{}={}\t'.format(i,j,i*j),end......
  • uniapp如何给空包进行签名操作
    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助首先安装sdkhttps://www.oracle.com/java/technologies/downloads/正常下一步即可~安装完毕后,进入在sdk根目录执行cmdC:\ProgramFiles\Java\jdk-18.0.1.1\bin 生成keystore例:keytool-genkey-aliast......
  • eclipse 如何通过OSGI 服务从一个插件给另一个插件发通知
    注册服务:BundleContextbundleContext=FrameworkUtil.getBundle(当前类.class).getBundleContext();EventHandlereventCreateNewConfigEventHandler=newEventHandler(){ @Override publicvoidhandleEvent(finalorg.osgi.service.event.Eventevent){ doSomet......
  • MegEngine 使用小技巧:如何使用 MegCC 进行模型编译
    MegEngine 作为一个训推一体的AI框架,为用户提供了模型训练以及部署的能力。但是在部署模型时,由于会存在对于部署的模型计算来说不必要的代码,导致SDK体积比较大。为了解决上述问题,我们提供了新的工具:AI编译器 MegCC。MegCC有以下特性:只生成完成输入模型计算的必要的ke......
  • python 并发编程之线程
    一、队列的使用1、在python中,内置的有一个类,Queue就是队列2、队列的使用frommultiprocessingimportQueueif__name__=='__main__':q=Queue(3)#队列的大小默认很大#1.如何入队、"""obj,block=True,timeout=None"""q.put('hellow......
  • VSCode如何通过Ctrl+P快速打开node_modules中的文件
    背景咱们新建一个NodeJS项目,必然会安装许多依赖包,因此经常需要查阅某些依赖包的源码文件。但是,由于node_modules目录包含的文件太多,出于性能考虑,在VSCode中默认情况下是禁止搜索node_modules目录的。在这种情况下,我们将不得不依次展开node_modules的文件目录树,来查找我们所需要的......
  • Python 异常处理(转载)
    Python异常处理什么是异常异常就是程序运行时发生错误的信号(在程序出现错误时,则会产生一个异常,若程序没有处理它,则会抛出该异常,程序的运行也随之终止),在python中,错误触发的异常如下语法错误这种错误,根本过不了python解释器的语法检测,必须在程序执行前就改正#语法错误示......
  • 如何在 Windows10 Professional 服务器上搭建自己的 Git 服务器。
    一、简介以前,在别家的公司,一般早就把源代码管理工具搭建好了,很少有机会自己搭建一套。最近,公司也许要把现在不少的源码进行管理,于是我打算自己搭建源代码管理服务器。说起源代码管理,当然有很多中解决方案,我个人偏向搭建一个Git服务器。毕竟这个自己用的比较多,也熟悉。......
  • python基础 如何查看进程的id号、队列的使用(queue)、解决进程之间隔离关系、生产者消
    如何查看进程id号进程都有几个属性:进程名、进程id号(pid-->processid)每一个进程都有一个唯一的id号,通过这个id号就能找到这个进程importosimporttimedeftask():print("task中的子进程号:",os.getpid())print("主进程中的进程号:",os.getppid())#parent......