Help us understand the problem. What is going on with this article?

CPythonでmain()から>>>が表示されるまでのツアーを駆け抜けてみる

More than 1 year has passed since last update.

##編集途中##

概要

CPythonの動作を理解するための準備として、ツアー[2]のChapter 1に部分的に参加してみる。
main()から>>>が表示されるまで。

この投稿は、[2]のいびつな抜粋和訳になることをお許しくださいm(_ _)m

目的

CPythonの内部構造を理解するための準備として、main関数の挙動を確認するため。

環境

os: maxOS Sierra Version 10.12.6

内容

詳細

レポジトリのダウンロード

今回は、事前にダウンロード済みだが、以下のようにターミナルで実行すればいいはずである。[1]

ターミナル
$ git clone https://github.com/python/cpython.git

レポジトリのブランチの変更

ツアーで用いるソースコードはPython 3.5のものであるため、gitのブランチを3.5に変更した。

ターミナル
$ pwd
/Users/なんやらかんやら/cpython
$ git branch -a
* master
  remotes/origin/2.7
  remotes/origin/3.4
  remotes/origin/3.5
  remotes/origin/3.6
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
$ git checkout 3.5
Checking out files: 100% (2624/2624), done.
Branch 3.5 set up to track remote branch 3.5 from origin.
Switched to a new branch '3.5'
$ git branch
* 3.5
  master

ブランチが、3.5に変更された。

main() in Programs/python.c

全てのC言語は、main()から実行が開始される。

Programs/python.c(部分)
int
main(int argc, char **argv)
{
    wchar_t **argv_copy;
    /* We need a second copy, as Python might modify the first one. */
    wchar_t **argv_copy2;
    int i, res;
    char *oldloc;

(中略)
    res = Py_Main(argc, argv_copy);
(中略)
    return res;
}

Py_Main()が呼ばれていることが分かった。

The thing to note is that the code here just ends up calling Py_Main() , which is defined in Modules/main.c . On to that file! (PS. There’s an old historical reason why it lives in Modules , not in Python . But it’s not important here.)

Py_Main() in Modules/main.c

Modules/main.c(部分)
/* Main program */

int
Py_Main(int argc, wchar_t **argv)
{
(中略:オプション解釈など)
    Py_Initialize();
(中略)
    if ((Py_InspectFlag || (command == NULL && filename == NULL && module == NULL)) &&
        isatty(fileno(stdin))) {
        PyObject *v;
        v = PyImport_ImportModule("readline");
        if (v == NULL)
            PyErr_Clear();
        else
            Py_DECREF(v);
    }
(中略)
    if (Py_InspectFlag && stdin_is_interactive &&
        (filename != NULL || command != NULL || module != NULL)) {
        Py_InspectFlag = 0;
        RunInteractiveHook();
        /* XXX */
        sts = PyRun_AnyFileFlags(stdin, "<stdin>", &cf) != 0;
    }

    Py_Finalize();
(中略)
    return sts;
}

(Py_Initialize()は、インタープリタを開始し、あらゆるオブジェクトを使用可能にする。

The key thing is that once Py_Initialize() has run, the interpreter is open for business, and we can use all kinds of objects.

v = PyImport_ImportModule("readline");では、インタラクティブなコマンドラインを、Pythonモジュール"readline"としてimportしようとしている。

After this there’s some code that tries to import the readline module, which provides interactive command line editing at the >>> prompt. Sometimes this can’t be imported, and we clear the exception in that case. This is a pretty standard pattern and good to understand:

副作用のみに関心があるから、今回の駆け抜けではそこまで気にしなくて良い?)

PyRun_AnyFileFlags()が呼ばれていることが分かった。

This function lives in Python/pythonrun.c and is a wrapper that calls either PyRun_InteractiveLoopFlags() or PyRun_SimpleFileExFlags() , both defined in the same file. And this is what I’ve promised: the code that prints the >>> prompt lives in the former.

PyRun_AnyFileFlags()(,PyRun_InteractiveLoopFlags()) in Python/pythonrun.c

Python/pythonrun.c(部分)
int
PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
                     PyCompilerFlags *flags)
{
    if (filename == NULL)
        filename = "???";
    if (Py_FdIsInteractive(fp, filename)) {
        int err = PyRun_InteractiveLoopFlags(fp, filename, flags);
        if (closeit)
            fclose(fp);
        return err;
    }
    else
        return PyRun_SimpleFileExFlags(fp, filename, closeit, flags);
}

int
PyRun_InteractiveLoopFlags(FILE *fp, const char *filename_str, PyCompilerFlags *flags)
{
(中略)
    v = _PySys_GetObjectId(&PyId_ps1);
    if (v == NULL) {
        _PySys_SetObjectId(&PyId_ps1, v = PyUnicode_FromString(">>> "));
        Py_XDECREF(v);
    }
    v = _PySys_GetObjectId(&PyId_ps2);
    if (v == NULL) {
        _PySys_SetObjectId(&PyId_ps2, v = PyUnicode_FromString("... "));
        Py_XDECREF(v);
    }
    err = -1;
    for (;;) {
        ret = PyRun_InteractiveOneObject(fp, filename, flags);
        _PY_DEBUG_PRINT_TOTAL_REFS();
        if (ret == E_EOF) {
            err = 0;
            break;
        }
        /*
        if (ret == E_NOMEM)
            break;
        */
    }
    Py_DECREF(filename);
    return err;
}

PyRun_InteractiveOneObject()が呼ばれていることが分かった。

PyRun_InteractiveOneObject() in Python/pythonrun.c

Python/pythonrun.c(部分)
int
PyRun_InteractiveOneObject(FILE *fp, PyObject *filename, PyCompilerFlags *flags)
{
(中略)
    v = _PySys_GetObjectId(&PyId_ps1);
(中略)
            ps1 = _PyUnicode_AsString(v);
(中略)
    w = _PySys_GetObjectId(&PyId_ps2);
(中略)
            ps2 = _PyUnicode_AsString(w);
(中略)
    mod = PyParser_ASTFromFileObject(fp, filename, enc,
                                     Py_single_input, ps1, ps2,
                                     flags, &errcode, arena);
(中略)
    v = run_mod(mod, filename, d, d, flags, arena);
(中略)
    return 0;
}

PyParser_ASTFromFileObject()が呼ばれていることが分かった。

PyParser_ASTFromFileObject() in Python/pythonrun.c

Python/pythonrun.c(部分)
mod_ty
PyParser_ASTFromFileObject(FILE *fp, PyObject *filename, const char* enc,
                           int start, char *ps1,
                           char *ps2, PyCompilerFlags *flags, int *errcode,
                           PyArena *arena)
{
    mod_ty mod;
    PyCompilerFlags localflags;
    perrdetail err;
    int iflags = PARSER_FLAGS(flags);

    node *n = PyParser_ParseFileObject(fp, filename, enc,
                                       &_PyParser_Grammar,
                                       start, ps1, ps2, &err, &iflags);
(中略)
    if (n) {
        flags->cf_flags |= iflags & PyCF_MASK;
        mod = PyAST_FromNodeObject(n, flags, filename, arena);
        PyNode_Free(n);
    }
(中略)
    return mod;
}

PyParser_ParseFileObject()が呼ばれていることが分かった。

PyParser_ParseFileObject() in Parser/parsetok.c

Parser/parsetok.c(部分)
node *
PyParser_ParseFileObject(FILE *fp, PyObject *filename,
                         const char *enc, grammar *g, int start,
                         const char *ps1, const char *ps2,
                         perrdetail *err_ret, int *flags)
{
    struct tok_state *tok;
(中略)
    if ((tok = PyTokenizer_FromFile(fp, enc, ps1, ps2)) == NULL) {
        err_ret->error = E_NOMEM;
        return NULL;
    }
(中略)
    return parsetok(tok, g, start, err_ret, flags);
}

PyTokenizer_FromFile()でプロンプトがtokにセットされ、
parsetok()が呼ばれていることが分かった。

PyTokenizer_FromFile() in Parser/parsetok.c (ps1, ps2の格納をみるため)

Parser/parsetok.c(部分)
/* Set up tokenizer for file */

struct tok_state *
PyTokenizer_FromFile(FILE *fp, const char* enc,
                     const char *ps1, const char *ps2)
{
    struct tok_state *tok = tok_new();
(中略)
    tok->prompt = ps1;
    tok->nextprompt = ps2;
(中略)
    return tok;
}

parsetok() in Parser/parsetok.c

Parser/parsetok.c(部分)
/* Parse input coming from the given tokenizer structure.
   Return error code. */

static node *
parsetok(struct tok_state *tok, grammar *g, int start, perrdetail *err_ret,
         int *flags)
{
    parser_state *ps;
    node *n;
    int started = 0;

(中略)
    for (;;) {
        char *a, *b;
        int type;
        size_t len;
        char *str;
        int col_offset;

        type = PyTokenizer_Get(tok, &a, &b);
(中略)
        if ((err_ret->error =
             PyParser_AddToken(ps, (int)type, str,
                               tok->lineno, col_offset,
                               &(err_ret->expected))) != E_OK) {
            if (err_ret->error != E_DONE) {
                PyObject_FREE(str);
                err_ret->token = type;
            }
            break;
        }
    }

    if (err_ret->error == E_DONE) {
        n = ps->p_tree;
        ps->p_tree = NULL;
(中略)
    }
(中略)

    return n;
}

PyTokenizer_Get()が呼ばれていることが分かった。

PyTokenizer_Get() in Parser/tokenizer.c

Parser/tokenizer.c(部分)
int
PyTokenizer_Get(struct tok_state *tok, char **p_start, char **p_end)
{
    int result = tok_get(tok, p_start, p_end);
    if (tok->decoding_erred) {
        result = ERRORTOKEN;
        tok->done = E_DECODE;
    }
    return result;
}

tok_get()が呼ばれていることが分かった。

tok_get() in Parser/tokenizer.c

Parser/tokenizer.c(部分)
/* Get next token, after space stripping etc. */

static int
tok_get(struct tok_state *tok, char **p_start, char **p_end)
{
    int c;
    int blankline, nonascii;
(中略)
    /* Get indentation level */
    if (tok->atbol) {
        int col = 0;
        int altcol = 0;
        tok->atbol = 0;
        for (;;) {
            c = tok_nextc(tok);
(中略)
    return PyToken_OneChar(c);
}

tok_nextc()が呼ばれていることが分かった。

tok_nextc() in Parser/tokenizer.c

Parser/tokenizer.c(部分)
/* Get next char, updating state; error code goes into tok->done */

static int
tok_nextc(struct tok_state *tok)
{
    for (;;) {
        if (tok->cur != tok->inp) {
            return Py_CHARMASK(*tok->cur++); /* Fast path */
        }

(中略)
        if (tok->prompt != NULL) {
            char *newtok = PyOS_Readline(stdin, stdout, tok->prompt);

PyOS_Readline()が呼ばれていることが分かった。

PyOS_Readline() in Parser/myreadline.c

原文では、Python/myreadline.cにあるとなっているが、Parser/myreadline.cのタイプミスだと思われる。

Parser/myreadline.c(部分)
/* Interface used by tokenizer.c and bltinmodule.c */

char *
PyOS_Readline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
{
    char *rv, *res;
    size_t len;
(中略)
    if (!isatty (fileno (sys_stdin)) || !isatty (fileno (sys_stdout)))
        rv = PyOS_StdioReadline (sys_stdin, sys_stdout, prompt);
(中略)
    len = strlen(rv) + 1;
    res = PyMem_Malloc(len);
    if (res != NULL)
        memcpy(res, rv, len);
    PyMem_RawFree(rv);

    return res;
}

PyOS_StdioReeadline()が呼ばれていることが分かった。

PyOS_StdioReeadline() in Parser/myreadline.c

Parser/myreadline.c(部分)
/* Readline implementation using fgets() */

char *
PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
{
(中略)
    fflush(sys_stdout);
    if (prompt)
        fprintf(stderr, "%s", prompt);
    fflush(stderr);
(中略)
    switch (my_fgets(p, (int)n, sys_stdin)) {
(中略)
    }
(中略)
    return pr;
}

fprintf!!

参考文献

[1] python/cpython(https://github.com/python/cpython)
[2]Yet another guided tour of CPython(https://paper.dropbox.com/doc/Yet-another-guided-tour-of-CPython-XY7KgFGn88zMNivGJ4Jzv)

tans
勉強中脳内外部ログ
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした