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