Hatena::ブログ(Diary)

西尾泰和のはてなダイアリー

2010-08-10

(Python|Ruby)でネストしたスコープの外側の変数に再束縛する

抜粋翻訳 PEP3104: Access to Names in Outer Scopesの内容をコードで。

Rubyでの挙動(1.9.3dev)

メソッドの中で定義されたメソッドは外のスコープにアクセス出来ない。

> def foo(x)
>     def bar()
>         p x
>     end
>     bar
> end
=> nil
> foo 1
NameError: undefined local variable or method `x' for main:Object
	from (irb):32:in `bar'
	from (irb):34:in `foo'
	from (irb):36
	from /usr/local/bin/irb:12:in `<main>'

メソッドの中で定義されたブロックでは、その定義されたスコープに存在しない名前への束縛のみブロックのローカル変数とし、それ以外は外の変数への再束縛にする。

> def foo(x)
>     lambda {x = 1; y = 2}.call
>     p x
>     p y
> end
=> nil
> foo 100
1
NameError: undefined local variable or method `y' for main:Object
	from (irb):27:in `foo'
	from (irb):29
	from /usr/local/bin/irb:12:in `<main>'

ささださんに教えてもらった: Matzにっき(2010-06-16): Ruby2.0の新機能(かもしれないもの)(2) nested def as local function

追記: セミコロンを前置することでローカル変数に強制することが出来る。Ruby Freaks Lounge:第3回 Ruby1.9の新機能ひとめぐり(中編):洗練された文法と意味論|gihyo.jp … 技術評論社

> def foo(x)
>     lambda {|;x| x = 1}.call
>     p x
> end
=> nil
irb(main):014:0> foo 100
100
=> 100

Pythonでの挙動(3.0)

関数の中で定義された関数は、外の値を参照することはできるが、

>>> def foo(x):
...     def bar():
...         print(x)
...     bar()
... 
>>> foo(100)
100

再束縛をする場合ローカル変数となるので外側の値を参照することができなくなる。

>>> def foo(x):
...     def bar():
...         x *= 2
...     bar()
...     print(x)
... 
>>> foo(100)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in foo
  File "<stdin>", line 3, in bar
UnboundLocalError: local variable 'x' referenced before assignment

この挙動をやめさせる(再束縛するxを外の変数だとみなさせる)にはnonlocal宣言を使う

>>> def foo(x):
...     def bar():
...         nonlocal x
...         x *= 2
...     bar()
...     print(x)
... 
>>> foo(100)
200

JavaScriptでの挙動(1.8、というかFirefox3)

JavaScript、Perlなどその他多くの言語ではローカル変数をつくるのに宣言が必要である。なので宣言がなければ外のスコープの名前にアクセスする。

function foo(x){
    function bar(){
        x *= 2;
    }
    bar();
    console.log(x); 
}

foo(100); // -> 200
function foo(x){
    function bar(){
        var x = 2;
    }
    bar();
    console.log(x);
}

foo(100); // -> 100