Pythonのsum()関数は
a = [1, 2, 3, 4]
print(sum(a))
> 10
b = (1, 3, 5, 7)
> 16
c = {1: 2, 3: 4, 5: 6, 7: 8}
print(sum(c.keys()))
> 16
print(sum(c.values()))
> 20
など、int, flootで構成されたlist, tuple, dictionaryのkey, valueに対しての総和を計算することができる。
ところで、
a = 2
b = 3
print(a + b)
> 5
a = 3
b = 4.125
print(a + b)
> 7.125
a = 'Hello'
b = 'World.'
print(a + ' ' + b)
> Hello World.
となることは自明の理である。(int+int, int+float,str+strはそれぞれint,float,strを返す)
しかし、
a = ['Hello', ' ', 'World.']
print(sum(a))
>Traceback (most recent call last):
File "test.py", line 2, in <module>
print(sum(a))
TypeError: unsupported operand type(s) for +: 'int' and 'str'
となってしまう。エラー文を読むとintとstrを足し合わせていると出ている。listの中身にはstrしかないのに何故このようなエラー文が出力されたのか。最初に見た時の想像としては初期値0として要素を足していっているのかなと思ったが実際はどうなのか。
そこで、CPythonで書かれたsum関数の中身(https://hg.python.org/cpython/file/2.7/Python/bltinmodule.c#l2307 )を詳しく見てみると
static PyObject*
builtin_sum(PyObject *self, PyObject *args)
{
PyObject *seq;
PyObject *result = NULL;
PyObject *temp, *item, *iter;
if (!PyArg_UnpackTuple(args, "sum", 1, 2, &seq, &result))
return NULL;
iter = PyObject_GetIter(seq);
if (iter == NULL)
return NULL;
if (result == NULL) {
result = PyInt_FromLong(0);
if (result == NULL) {
Py_DECREF(iter);
return NULL;
}
} else {
/* reject string values for 'start' parameter */
if (PyObject_TypeCheck(result, &PyBaseString_Type)) {
PyErr_SetString(PyExc_TypeError,
"sum() can't sum strings [use ''.join(seq) instead]");
Py_DECREF(iter);
return NULL;
}
Py_INCREF(result);
}
...
簡単に中身を書くと(最初の段落)
- 1段落目...諸々の変数の定義
- 2段落目...処理のためのパラメータ取得。
PyArg_UnpackTuple
が成功した時はTrue
を返すが、引数の数が今回は1未満や3以上など、様々な例外で失敗した時にはFalse
が返る。今回は失敗した時にはnull
が返される。seq
には第一引数(sum
を計算したい構造体),result
には第二引数(返り値の初期値であるstart
)が入る。 - 3段落目...2段落目のパラメータ取得がうまくいった際に、
seq
のイテレータを返す。イテレータはC
,C++
でよく使うがPython
ではあまり使用されない(forで充分なのかも)。オブジェクトが反復処理不可能(__iter__
メソッドを持たないもの)であった場合、TypeError
が出る他、null
が返される。 - 4段落目...
result
がnull
でない(=入力の際に2変数入力された)時は、result
の型をチェックする。strだった場合はエラーを出す。実際に試すと
print(sum([1, 2, 3], 0))
> 6
print(sum(["Hello", '', "World."], 'a'))
>Traceback (most recent call last):
File "test.py", line 1, in <module>
print(sum(["Hello", '', "World."], 'a'))
TypeError: sum() can't sum strings [use ''.join(seq) instead]
となる。また、Py_DECREFを行うことで参照カウントを減らし、メモリ開放を行う。
そうでない場合(=入力の際に1変数入力された時)は、result
にint
型の0を入れる。公式ドキュメント(https://docs.python.org/ja/2.7/c-api/int.html#c.PyInt_FromLong )を読むと、整数の定義を変えることで値を変えることもできるらしい?その後にもresult
の値の判定があるのかはそのせい?
ここで大事なのは4段落目で、sum
の2引数目に値を入れていようが入れていなかろうが、初期値はint
またはfloat
として扱われることになる。したがって、計算対象もint
またはfloat
でなければいけない。仮説は正しかった。
文字列を計算したいなら有名ではあるが''.join(合わせたいリスト)
とするのが良い。
print(''.join(["Hello", "", "World."]))
>Hello World.
print(''.join[1, 2, 3])
>Traceback (most recent call last):
File "Main.py", line 1, in <module>
print(''.join([1, 2, 3]))
TypeError: sequence item 0: expected str instance, int found