LoginSignup
8
10

More than 5 years have passed since last update.

PythonとRubyでの「初期化していないローカル変数」の取り扱いについて

Posted at

はじめに

Pythonで以下のようなプログラムを書いたら例外(UnboundLocalError)が発生しました。

def foo(a):
    if a > 0:
        bar = 1
    print(bar)

foo(-1)

まあそうだよね、というところではあるのですが、一方Rubyでは例外にはなりません。

def foo(a)
  if a > 0
    bar = 1
  end
  p bar
end

foo(-1) # => nil

Python的にもローカル変数barの領域は確保してあるだろうしNoneでいいんじゃない?というところですがそうにはならないようです。

実装を見てみる

CPython、CRubyそれぞれでローカル変数がどう初期化されているかを確認します。

CPython

関数実行情報を入れるPyFrameObject、それを作成しているPyFrame_New関数(Objects/frameobject.c)のうちローカル変数初期化に関する部分だけ抜き出すと、

        extras = code->co_nlocals + ncells + nfrees;
        f->f_valuestack = f->f_localsplus + extras;
        for (i=0; i<extras; i++)
            f->f_localsplus[i] = NULL;

詳しい説明は省きますがf_localsplusがローカル変数に使われている領域です。NULLで初期化されています。Noneじゃないんですね。(NULLとNoneは別物です。C実装的にはNoneはPy_Noneというれっきとしたオブジェクトです)

CRuby

一方、CRubyです。vm_insnhelper.cのvm_push_frame関数でローカル変数を初期化しています。

    /* initialize local variables */
    for (i=0; i < local_size; i++) {
        *sp++ = Qnil;
    }

nilを入れていますね。というわけで「変数に初めて代入される個所」を通らなくてもnilが入っているということになります。

言語のスタンスの違い

先にRubyを学んで後からPythonを学んだ人間なので、「辞書で指定されたキーがないと例外」「リスト範囲外に代入しようとすると例外」というPythonの挙動に対し「いやそこはNone返してよ」「よしなにリスト拡張してよ」と思ったものですがPythonに慣れてくると「これはおかしな状況だし例外ぽーん」というPythonのスタンスもさもありなんと思うようになってきました。
今回の「初期化していないローカル変数」についても処理系実装知ってる分(&Rubyの動作を考えると)「こう書けるだろう」としてましたが確かに「おかしな」状況です。

では、Pythonの動作とRubyの動作どちらが望ましいかというとまあどちらもありであり、言語設計者の思想の違いかなと思いました。個人的にはグイドさんもまつもとさんも好きです。
なお、個人的にはNoneで初期化されててくれる方がうれしいです:-P

余談

そもそもなぜ件のif文を書いたかですが、例外処理にて、

bar = None
try:
    bar = some_func()
except:
    pass
print(bar)

みたいなコードが書いてあったのを、先頭行の「bar = None」消しても大丈夫なんじゃないの?(Javaとかみたいに例外処理の外側で変数初期化するのなんかダサい)→大丈夫だったということでやってしまったということになります。ちなみにこれsome_funcで例外起きると最終行のbar参照は例によってのUnboundLocalErrorになるので良い子は真似しちゃダメです。

8
10
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
8
10