1
4

More than 3 years have passed since last update.

Python/C APIを読む その1(便利なマクロ)

Last updated at Posted at 2021-03-25

概要

Pythonの構造を理解する助けとして、C APIのドキュメントを読み砕いていく。

内容

環境

Python 3.7 (CPython)に話を固定するため、
以下のコミットに話を限定する。
tree (コミット; ブランチ)
(↑ githubのリンク)

内容

Py_UNREACHABLE()

<概説>

辿りつくはずのない場所に使う。(assert(0)やabort()を使いたくなるようなcase節のdefault内など。)(出典)

<定義>

定義 (cpython/Include/pymacro.h)
#  define Py_UNREACHABLE() __builtin_unreachable()

(定義出典)

  • __builtin_unreachableは、コンパイラーにここにはたどり着かないことを知らせ最適化を促す?
  • コンパイラーgccの新しいバージョンの場合。

<使用例>

使用例 (cpython/Python/ast.c)
static const char *
expr_context_name(expr_context_ty ctx)
{
    switch (ctx) {
    case Load:
        return "Load";
    case Store:
        return "Store";
    case Del:
        return "Del";
    default:
        Py_UNREACHABLE();
    }
}

(使用例出典)

Py_ABS(x)

<概説>

xの絶対値を返す。(出典)

<定義>

定義 (cpython/Include/pymacro.h)
#define Py_ABS(x) ((x) < 0 ? -(x) : (x))

(定義出典)

<使用例>

使用例 (cpython/Objects/listobject.c)
    assert(Py_ABS(Py_SIZE(v)) <= 1);

(使用例出典)

Py_MIN(x, y)

<概説>

xyの最小値を求める。(出典)

<定義>

定義 (cpython/Include/pymacro.h)
#define Py_MIN(x, y) (((x) > (y)) ? (y) : (x))

(定義出典)

<使用例>

使用例 (cpython/Modules/arraymodule.c)
        Py_ssize_t common_length = Py_MIN(Py_SIZE(va), Py_SIZE(wa));

(使用例出典)

Py_MAX(x, y)

<概説>

xyの最大値を求める。(出典)

<定義>

定義 (cpython/Include/pymacro.h)
#define Py_MAX(x, y) (((x) > (y)) ? (x) : (y))

(定義出典)

<使用例>

使用例 (cpython/Python/fileutils.c)
    first = Py_MAX(first, 0);

(使用例出典)

Py_STRINGIFY(x)

<概説>

xをC文字列へ変換する。例えば、Py_STRINGIFY(123)"123"を返す。(出典)

<定義>

定義 (cpython/Include/pymacro.h)
#define _Py_XSTRINGIFY(x) #x

/* Convert the argument to a string. For example, Py_STRINGIFY(123) is replaced
   with "123" by the preprocessor. Defines are also replaced by their value.
   For example Py_STRINGIFY(__LINE__) is replaced by the line number, not
   by "__LINE__". */
#define Py_STRINGIFY(x) _Py_XSTRINGIFY(x)

(定義出典)
マクロと#演算子については、別サイトに以下のような解説と例がある。

#演算子
プリプロセッサが#<identifier>の<identifier>を文字列に変換します。

例1
#include <stdio.h>

#define print(value) printf(#value " = %d\n", value)

int main(void)
{
  int a = 1, b = 2;
  print(a);
  print(b);
  return 0;
}
例1出力
a = 1
b = 2

また、マクロを入れ子にすることで、以下のような動作になります。

例2
#include <stdio.h>

#define STRINGIFY(n) #n
#define TOSTRING(n) STRINGIFY(n)

int main(void)
{
  puts(STRINGIFY(__LINE__));
  puts(TOSTRING(__LINE__));
  return 0;
}
例2出力
__LINE__
9

プリプロセッサがマクロを段階的に展開するためですが、詳細は出典元HPをご参照ください。

<使用例>

使用例 (cpython/
    do { if (!(expr)) { _PyObject_ASSERT_FAILED_MSG(op, Py_STRINGIFY(expr)); } } while (0)

(使用例出典)
do{...} while (0)は、複数行にわたるmacro定義の際によく使われるテクニック。(cf. Stack Overflow)

Py_MEMBER_SIZE(type, member)

<概説>

(type) 構造体の member のサイズをバイト単位で返す。(出典)

<定義>

定義 (cpython/Include/pymacro.h)
#define Py_MEMBER_SIZE(type, member) sizeof(((type *)0)->member)

(定義出典)
型キャストした後に構造体の要素にアクセスし、サイズを得ています。

<使用例>

見たところ、CPython内では使われていなさそう。

Py_CHARMASK(c)

<概説>

引数は文字か、[-128, 127]あるいは[0, 255] の範囲の整数である必要がある。このマクロは符号なし文字にキャストしたcを返す。(出典)

<定義>

定義 (cpython/Include/pymacro.h)
#define Py_CHARMASK(c) ((unsigned char)((c) & 0xff))

(定義出典)

この動作の詳細については、まだよくわかりません。わかったら追記します。

<使用例>

使用例 (cpython/Parser/string_parser.c)
    const char *s = PyBytes_AsString(t->bytes);
    (中略)
    int quote = Py_CHARMASK(*s);

(使用例出典)

Py_GETENV(s)

<概説>

getenv(s)に似ているが、コマンドラインで -E が渡された場合 (つまりPy_IgnoreEnvironmentFlagが設定された場合) NULLを返す。(出典)
PYTHONPATHPYTHONHOMEのような環境変数をとるときに使う。

<定義>

定義 (cpython/Include/cpython/pydebug.h)
#define Py_GETENV(s) (Py_IgnoreEnvironmentFlag ? NULL : getenv(s))

(定義出典)

<使用例>

使用例 (cpython/Python/thread.c)
    const char *p = Py_GETENV("PYTHONTHREADDEBUG");

(使用例出典)

Py_UNUSED(arg)

<概説>

コンパイラーからの警告を抑えるため、使わない引数を明示する。(出典)

<定義>

定義 (cpython/Include/pymacro.h)
#  define Py_UNUSED(name) _unused_ ## name __attribute__((unused))

(定義出典)

  • ##はトークン結合演算子で、プリプロセッサーが二つの文字をくっつけて置き換える。(出典)
  • __attribute__((unused))は、GNU compilerへのメッセージで、使われなくてもコンパイラーエラーを出さないためのものである。(出典)
トークン演算子(##) 例
#include <stdio.h>
#define var(i) printf("var" #i " = %d\n" , var ## i)

int main() {
        int var1 = 10 , var2 = 20;
        var(1);
        var(2);

        return 0;
}
トークン演算子(##) 出力
var1 = 10
var2 = 20
__attribute__((unused)) 例
void Variable_Attributes_unused_0()
{
    static int aStatic =0;
    int aUnused __attribute__((unused));
    int bUnused;
    aStatic++;
}

直上の例は、bUnusedが使われていないというエラーは出すが、aUnusedが使われていないというエラーは出さない。

<使用例>

使用例 (cpython/Objects/iterobject.c)
static PyObject *
iter_len(seqiterobject *it, PyObject *Py_UNUSED(ignored))
{

(使用例出典)

Py_DEPRECATED(version)

<概説>

古くなり使用されていない宣言に使う。(出典)

<定義>

定義 (cpython/Include/pyport.h)
#define Py_DEPRECATED(VERSION_UNUSED) __attribute__((__deprecated__))

(定義出典)

__attribute__((deprecated))は、gnu compilerにdeprecateされたものであることを伝える。(出典)(__deprecated__と前後に__がある理由はわからない)

<使用例>

使用例 (cpython/Include/ceval.h)
Py_DEPRECATED(3.9) PyAPI_FUNC(PyObject *) PyEval_CallObjectWithKeywords(

(使用例出典)

PyDoc_STRVAR(name, str)

<概説>

docstringsで使うことのできるnameが名前の変数を作る。(出典)

<定義>

定義 (cpython/Include/pymacro.h)
#define PyDoc_STRVAR(name,str) PyDoc_VAR(name) = PyDoc_STR(str)

(定義出典)

<使用例>

使用例 (cpython/Objects/setobject.c)
PyDoc_STRVAR(pop_doc, "Remove and return an arbitrary set element.\n\
Raises KeyError if the set is empty.");

(使用例出典)

PyDoc_STR(str)

<概説>

docstringを作成する。docstringが無効にされているときは、空文字を出力する。(出典)

<定義>

定義 (cpython/Include/pymacro.h)
#define PyDoc_VAR(name) static const char name[]

(定義出典)

<使用例>

使用例 (cpython/Objects/exceptions.c)
static PyGetSetDef BaseException_getset[] = {
    {"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict},
    {"args", (getter)BaseException_get_args, (setter)BaseException_set_args},
    {"__traceback__", (getter)BaseException_get_tb, (setter)BaseException_set_tb},
    {"__context__", BaseException_get_context,
     BaseException_set_context, PyDoc_STR("exception context")},
    {"__cause__", BaseException_get_cause,
     BaseException_set_cause, PyDoc_STR("exception cause")},
    {NULL},
};

(使用例出典)

蛇足

Python.h
...
#include "pyport.h"
#include "pymacro.h"
...
#include "pydebug.h"
...

参考にさせていただいた本・頁

上記参照

感想

内部を見れているようで面白かった。

今後

((unsigned char)((c) & 0xff))の挙動を調べたい。
ブランチが厳密には特定できていないようなので、もし気が向けばそこも修正したい。

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4