Pythonのデフォルト引数は１回しか評価されない問題

# 問題
早速ですが、この記事にたどり着いた方に問題です。以下の`mondai.py`を`python`コマンドで実行した時に出力される数字はいくつでしょうか？

```mondai.py
class TrainCar(object):
    def __init__(self, color_type=None, passenger_list=[]):
        self.color_type = color_type
        self.passenger_list = passenger_list


class Train(object):
    def __init__(self):
        self.cars = []

    @property
    def passenger_count(self):
        return sum([len(car.passenger_list) for car in self.cars])


if __name__=="__main__":
    train = Train()
    for i in range(0, 10):
        train_car = TrainCar(color_type="keiyo")
        train_car.passenger_list.append("Taro Yamada")
        train.cars.append(train_car)

    print(train.passenger_count)
```

上のプログラムでは、2つのクラスが定義されており、それらクラスの関係は `Train`(電車クラス) has many `TrainCar`(車両クラス) となっています。また、車両(`TrainCar`インスタンス) は`passenger_list`(乗客リスト) を持てます。

つまり、変数`train`は10両編成の電車で、それぞれの車両に`"Taro Yamada"`さんが1人ずつ乗るわけですね。
で、出力される数字はその合計（電車に乗っている全乗客）人数だから、**10** が正解ですね！

・・・と、思っていた時代もありました。

**100** という答えにたどり着いた方、おめでとうございます。立派なニシキヘビ使いになることができた自分へのご褒美として、今夜はコタツでハーゲンダッツを食べましょう。😄
あ、安心してください。お代は、この電車に乗っている100人もの`"Taro Yamada"`さんのうちの誰かがきっと払ってくれます。👍


# 解説
さて、なぜこんなにも多くの`"Taro Yamada"`さんが乗車していたのか？

先の問題のプログラムでは、「車両を用意し、そこに`"Taro Yamada"`さんを1人乗せ」る行為を10回行なっていました。
と日本語で書くと、車両1つにつき`"Taro Yamada"`さん1人が乗車しているから10両編成だと合計10人であると錯覚します。むしろ、それが自然です。・・・そう、用意した車両に誰も人が乗ってなければね。

実は、**Pythonのデフォルト引数は1回しか評価されない**のです。
つまり、`train_car = TrainCar(color_type="keiyo")`としただけでは`train_car.passenger_list = []`とは**ならない**のです。

よって、`for`文の中で`train_car.passenger_list.append("Taro Yamada")`が実行されるたびに、前の車両に乗ってた`"Taro Yamada"`さんはリセットされないがために、単純に1人ずつ増えていくことになります、なので、1両目の`TrainCar`インスタンスには1人、2両目には2人・・・となり、10両編成にすると 1+2+...+10 で 55 人となるわけです。が、あれ、まだ違うぞ。。。？
そういえば、Pythonの引数は参照渡しでした。なので、先頭から最後尾までの車両`train.cars`内の`passenger_list`は、全て同じになってしまってます。つまり全車両に10人乗っているので、10*10の 100 人が正解です。ナンテコッタ！😇


# 新たな疑問とその答え
ここで新たな疑問が生まれました。それは、`mondai.py`内において`color_type="keiyo"`の方は評価されているかどうか。という疑問です。

ということで、この疑問を解消するために、`mondai.py`を以下のように書き換えて実行してみましょう。

```mondai_2.py
class TrainCar(object):
    def __init__(self, color_type=None, passenger_list=[]):
        self.color_type = color_type
        self.passenger_list = passenger_list


class Train(object):
    def __init__(self):
        self.cars = []

    @property
    def passenger_count(self):
        return sum([len(car.passenger_list) for car in self.cars])


if __name__=="__main__":
    train = Train()
    for i in range(0, 10):
        color_type = "keiyo" if i < 6 else "chuo"
        train_car = TrainCar(color_type=color_type)
        train_car.passenger_list.append("Taro Yamada")
        train.cars.append(train_car)

    print(train.passenger_count)
    print([car.color_type for car in train.cars])
```

7両目以降は色を変えておきましょう。新しく追加する色は、似たものが良いです。`"Taro Yamada"`さんに不審に思われて乗車拒否されたら計画が狂ってしまいますからね。
そして、実行して結果を確認してみましょう。

```
100
['keiyo', 'keiyo', 'keiyo', 'keiyo', 'keiyo', 'keiyo', 'chuo', 'chuo', 'chuo', 'chuo']
```

期待通り、7両目以降の`color_type`は`keiyo`から`chuo`になりましたね！
どうやら、**引数を指定した**`color_type`の**方は評価されます**。👍
（まあ、そうじゃないと困りますが。）

# 補足
実は、[Pythonのチュートリアル](https://docs.python.jp/3/tutorial/controlflow.html#default-argument-values) 内の **重要な警告** にこの内容が書かれていました。とほほ。。。


# 追記
**2017年11月7日**
100が出力される根拠となる計算式が間違っていたので、修正しました。間違いをご指摘くださった方々、感謝申し上げます。
**2017年11月10日**
解説に「Pythonの引数は参照渡し」であることを追加（参考：[Pythonの引数はすべて参照渡し](https://qiita.com/urakarin/items/1d6e0e76bffb9297606e)）