読者です 読者をやめる 読者になる 読者になる

pythonの組み込み関数をいじくってみた。

>>> bin(-12)
'-0b1100'

追記

Even if this was a good idea, it's too late to change the behavior of the builtin function.

おわり

個人的にbin()の挙動が気に食わないので下のコミュニティで軽く書いたところ、
2の補数表現は人的ミスが起こるという事で難しいとの指摘を受けた。
ごもっとも

Issue 25999: Add support of native number in bin() - Python tracker

という事で公式ではなくForkしたCPythonのbin関数をいじくってみた。

りぽじとり

github.com

いじくったファイル

  • bltinmodule.c
  • bltinmodule.c.h

bltinmodule.c.h

PyDoc_STRVAR(builtin_bin__doc__,
"bin($module, number, bit_size=None/)\n"
"--\n"
"\n"
"Return the binary representation of an integer.\n"
"\n"
"   >>> bin(2796202)\n"
"   \'0b1010101010101010101010\'\n"
"\n"
"if it_size is not None, \n"
"Return the negative number that assumed the bit_size top bit.\n"
"\n"
"   >>> bin( -12, 8)\n"
"   \'0b11110100\'");
#define BUILTIN_BIN_METHODDEF    \
    {"bin", (PyCFunction)builtin_bin, METH_VARARGS, builtin_bin__doc__},

PyDoc_STRVAR

pymacro.hに定義がある。第一引数(builtin_bin__doc__)に第二引数の値を入れる。
なので上の例だとbuiltin_bin__doc__にbin関数の使い方が入っている

#define PyDoc_VAR(name) static char name[]
#define PyDoc_STRVAR(name,str) PyDoc_VAR(name) = PyDoc_STR(str)
#ifdef WITH_DOC_STRINGS
#define PyDoc_STR(str) str
#else
#define PyDoc_STR(str) ""
#endif

BUILTIN_BIN_METHODDEF

関数の定義をここでする。ここで定義したマクロは Python/bltinmodule.cでPyMethodDef builtin_methods[]内に入り 実際に組み込まれていく。 第一要素が名前
第二要素が実装してある関数
第三要素が引数の種類
第四要素がドキュメント文字列
となっている

引数の種類

Object/methodobject.cにいろいろかいてある。
- METH_VARARGS 複数の引数
- METH_NOARGS 引数なし
- METH_O 引数がひとつ

buildtin_bin

bltinmodule.cに実際の動きを書いていく

static PyObject *
builtin_bin(PyModuleDef *module, PyObject *args)
{
    PyObject *number;
    PyObject *bit_size = Py_None;

    if(!PyArg_UnpackTuple(args, "bin", 1, 2, &number, &bit_size))
        return NULL;

    // if number < 0 and bit_size != None, It will return twos complement.
    if(bit_size != Py_None && PyObject_RichCompareBool( number, PyLong_FromLong(0), Py_LT))
        return PyNumber_ToBase(
            PyNumber_Subtract(
                PyNumber_Power(PyLong_FromLong(2), bit_size, Py_None),
                PyNumber_Negative(number))
            , 2);

    return PyNumber_ToBase( number, 2);
}

ここで関数はstatic PyObject *またはNULLを返す。

使った関数

  • PyArg_UnpackTuple
    動きはarg.rstに書いてある。
c:function:: int PyArg_UnpackTuple(PyObject *args, const char *name, Py_ssize_t min, Py_ssize_t max, ...)

引数を取得する関数
今回はbinに第二引数を新しく追加したので

PyArg_UnpackTuple(args, "bin", 1, 2, &number, &bit_size)

と書く。すると最低1引数、最大2引数を受け取り第一引数をnumber、第二引数をbit_sizeに挿入する。

  • PyNumber_Subtract
  • PyNumber_Power
  • PyNumber_Negative
    Doc/c-api/number.rstに書いてある。
    PyNumber_Addとか基本的演算は用意されている。
    数字のPyObject同士の演算をする関数。

  • PyLong_FromLong long型からPyObjectを作る関数。

  • PyObject_RichCompareBool
    Doc/c-api/object.rstに書いてある
    PyObject同士の比較をする関数。 Py_LT,Py_NE,...等ある

基本的にPy**を使って作りたいPyObjectを返せばいいっぽい

make

ある程度書いたらいつも通りmakeする

$ ./configure
$ make -j10
$ make altinstall

できたもの

$ python3.6
Python 3.6.0a0 (default, Jan  3 2016, 19:28:45)
[GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> bin(-12)
'-0b1100'
>>> bin(-12,8)
'0b11110100'
>>> bin(12,8)
'0b1100'
>>> bin(-1,8)
'0b11111111'

条件分岐が甘いが、やりたい事はできた

まとめ

割と簡単に組み込み関数はいじれるので意味は特にないがいろいろできそう