- 締切済み
python: nonlocalとglobal文
pythonで次のような関数の書き方がチュートリアル本に書かれていました ------------------------------------------- スコープと名前空間の例 def scope(): loc = "abc" def do_local(): loc = "local" def do_nonlocal(): nonlocal loc loc = "nonlocal" def do_global(): global loc loc = "global" ------------------------------------------- VS Codeでブレークポイントを置き走らせ、 nonlocal loc → loc = "nonlocal" global loc →loc = "global" の代入(バインディング?)が起こることはわかりました チュートリアル本では、nonlocalとglobalは次のように書かれています 説明文 nonlocal文 その変数が自分を取り囲むスコープにある global文 その変数がグローバルスコープにある 質問; 上の2行(説明文)の 1)nonlocal文 その変数が自分を取り囲むスコープにある 2)global文 その変数がグローバルスコープにある これらのスコープが何、どこ、どのように、指しているか教えてください また 3)これらと名前空間の関係をわかりやすく教えてください (本ではわからず) 【回答上のご注意】 回答は、解答(答え)を求めています わたしはプログラマーではないので、昭和的な「自分で考えろ」的なものは求めていません わからなければ答えない自由もあなたにはあります 不明点があれば説明いたします
- みんなの回答 (2)
- 専門家の回答
みんなの回答
- cametan_42
- ベストアンサー率62% (162/261)
> まあ資格試験ではそこまで問われていないようなので、これでよしとしようと思います あ〜、資格試験・・・・・・。 模擬試験見た事あるんだけど、「重箱の隅をつつく」ような問題が多くて、あまりプログラミングには役立たないんだよね。 加えると、「国家資格でも何でもない」んで、Pythonの資格試験は私企業的な・・・・・・ま、いっか、その辺は。 > ただ最初の行の loc = "a"を、global文がloc = "global"に書き換えるロジックがまだわからずにいます いや、そこもそのまんまだよ。 loc = "a"は大域的に定義されてるグローバル変数だ。 ローカル関数do_global内でglobal宣言された変数locは「これはdo_global内の変数じゃなくってグローバル変数だ」と言う意味になる。 だからグローバル変数locの中身("a")を「書き換えに行ってる」。 そこはロジックじゃないんだ。単に「宣言したように」振る舞っている。 もう一度言うけど、Pythonのこの挙動・・・っつーかユーザーの手を煩わす「設計方針」ってのは他のモダンなプログラミング言語じゃありえない仕様なんだ。 Pythonはスコープがデタラメだ。よって「ユーザーが書いたようにPythonは解釈する」しかあり得ないんだよ。 Pythonは完全なレキシカルスコープを持っていない。 loc = "a" # ③' do_global() によって "global" に書き換わる def scope(): loc = "abc" # ②' do_nonlocal() によって "non-local" に書き換わる def do_local(): loc = "local" ① ここは外部からアクセス出来ない(見えない) def do_nonlocal(): nonlocal loc ② このスコープの外でlocを見つけたらそれを使う loc = "nonlocal" def do_global(): global loc ③ トップレベル(グローバルスコープ)へとアクセス loc = "global" do_local() # アクセス先はscope()のloc ②' print("A: ", loc, "local代入は変化させない") do_nonlocal() # ここでscope()のloc(②')が書き換わる print("B: ", loc) do_global() print("C: ", loc) # ここの第二引数もscope()のloc(②') scope() print("D: globalで代入したlocのglobalになるはず:" , loc) ここは大域変数③' > ローカル代入がスコープ内でバインディングしない 「バインディング」は「代入」って読み替えてもいい。 バインディング(束縛)ってのは、ちと技術的な用語で、暗黙に「代入とは違いますよ」っつってんだけど、やってる事は(初心者時点では)「代入」と捉えていいです。
- cametan_42
- ベストアンサー率62% (162/261)
まず、Python自体は割にスコープがデタラメな言語で、globalやnonlocalはad-hocな拡張だ、って思っていた方がいい。 要はスコープの設計が「マズい」為、付け足された機能と考えていいんで、あんま美しくないんだ。 まずは「使わなければ使わない方が良い」グローバル変数の話からしよう。(2)の話、からだ。 例えば次のような関数を定義する。 def do_global(): global loc loc = 'global' その後、インタプリタでこの関数を実行する。 >>> do_global() そうすると、インタプリタで入力された筈がないlocと言う変数が「勝手に」定義されてる事が分かるだろう。 >>> loc 'global' 通常、関数内で定義された変数はその外側から覗いたり変更する事が出来ない。関数内での変数は関数内のみで使えてる、と言う「有効範囲」が厳密に決められている(これを「スコープ」と呼ぶわけだ)。 一方、Pythonの関数内でglobal宣言された変数は大域変数、つまり「定義された関数の外側」にアクセスしてる。結果、プログラム内のどの関数からでもアクセスが可能だ。 大域的にアクセス出来るスコープなんでこれをグローバルスコープと呼ぶわけだ。また、言い換えるとグローバルスコープで定義された変数をグローバル変数とか大域変数と表現する。 ただし、モダンなプログラミングではこのグローバル変数を変更したりするテクニックは推奨されない(参照するのは構わないが)。よってこういうプログラミング方法は避けよう。データは引数を通じてやり取りすべきだ。 また、コンピュータサイエンス上、上のdo_global()関数のようにグローバルスコープにアクセスしてそこにある変数を変更したりするものを、通常、関数と呼ばずプロシージャ(手続き)と呼ぶ。 要は関数は作っていいけど、プロシージャは作るな、って事だ。 次は(1)だ。これを理解するには、有名なハッカーであるポール・グレアムの出したアキュムレータの問題を考えるのが一番簡単だろう。 > アキュムレータを生成する関数、すなわち、数nを取り、 「数iを取ってnをiだけ増加させ、その増加した値を返す関数」を返すような関数だ。 > > (「増加させる」に注意。ただ足すだけではない。 アキュムレータ(累積器)だから累積させなければ)。 これはPythonだと次のように書けば良さそうなんだが、生憎これじゃ上手く行かない。 def foo(n): def bar(i): n += i return n return bar これをインタプリタ上で次のように実行すると、エラーになる筈だ。 >>> baz = foo(5) >>> baz(1) Traceback (most recent call last): File "<pyshell#116>", line 1, in <module> baz(1) File "<pyshell#114>", line 3, in bar n += i UnboundLocalError: cannot access local variable 'n' where it is not associated with a value 何でこれが出てくるのか、と言うと、ローカル関数としてbarを定義してるが、このbar内で使われてる変数nが「無い」ってぇんで文句を言ってるわけだ。 ちなみに、こういう事はフツーのプログラミング言語では「起きない」。と言うのも、ローカル関数で定義されてるnがそのローカル関数内で見つからなかった場合、その外側に通常は「探しに行く」から、だ(Pythonのスコープがデタラメだ、って言った原因はこれだ)。 例えば同様の関数をPythonのライバルの一つ、JavaScriptで書いても問題なく動作する。 js> function foo(n) { function bar(i) { n += i; return n; }; return bar;} js> baz = foo(5); function bar(i) { n += i; return n; } js> baz(1); 6 js> baz(2); 8 js> baz(3); 11 js> 上のPythonのコードと同様に、ローカル関数bar内に変数nが唐突に登場してるが、JavaScriptの場合、nが何なのか、barが形成してるスコープ外へと探しに行く。 繰り返すが、これが静的スコープ(レキシカル・スコープとも呼ぶ)を持ったモダンな言語では当たり前、なんだけど、Pythonはこの辺へなちょこでそういう機能がない、んだ。 JavaScriptのような動作をPythonで実現するには、コードを次のように書かないとならない。 def foo(n): def bar(i): nonlocal n n += i return n return bar ローカル関数bar内でnonlocalでnを宣言すると、「変数nはこのbarのスコープ内には無い。外へ探しに行きな。」と言う意味になる。これによって、外側のfooが形成してるスコープで、引数になってる変数nを見つけるわけだ。これで無事解決、ってわけ。 >>> baz = foo(5) >>> baz(1) 6 >>> baz(2) 8 >>> baz(3) 11 (3)に付いては、初心者向け説明としてはスコープ≒名前空間、と考えてほぼ間違いない。 もうちょっと専門的に言うと、Pythonは1ファイルで1名前空間を形成するが、その1ファイル内に定義された関数/クラス群はそれぞれに別個で名前空間を生成する・・・よって、1ファイル内で定義された「大域変数」は、単純には別ファイルからそのままアクセス出来る大域変数にはなり得ない(そういう意味では、原義的には「グローバル」じゃない)。 そして、もうちょっと専門的に言うと、「空間」とは数学用語で言うトコの「集合」だ。よってある名前空間内では名前の重複は許されない。単純に、例えば1ファイル内で、 var = 1 def foo(var): return var と書いた場合、大域変数のvarと関数内のvarは一見同じモノに見えるが別物だ。大域変数でのvarはファイル内で唯一無二の変数名になるが、関数は別の名前空間を形成するんで、外に置かれたvarと関数foo内のvarは同じモノじゃない・・・っつーか「違うモノだと保証されている」。両者のvarは同じ名前空間に属していないんだ。
お礼
ありがとうございます いただいた回答は難しすぎてわからなかったのですが、VS CodeのデバッグコンソールとAIと100回くらいやりとりしてなんとなく動くコードが書けました テキスト本にあった「ローカル代入がスコープ内でバインディングしない」という意味をAIに聞き、次のコードを試してみました #------- x = 0 def example(): x = 1 print("x is initially", x) def example2(): x = 2 print("x is updated to ", x) example2() print(x) example() #-------------- ローカル代入はバインディングを変更しないのであれば、global文は外側のスコープを読みに行っているのだなと仮定(理解)し、次のコードが動いたので一応理解できたようです #スコープと変数 loc = "a" def scope(): loc = "abc" def do_local(): loc = "local" def do_nonlocal(): nonlocal loc loc = "nonlocal" def do_global(): global loc loc = "global" do_local() print("A: ", loc, "local代入は変化させない") do_nonlocal() print("B: ", loc) do_global() print("C: ", loc) scope() print("D: globalで代入したlocのglobalになるはず:" , loc) #-------------- ただ最初の行の loc = "a"を、global文がloc = "global"に書き換えるロジックがまだわからずにいます まあ資格試験ではそこまで問われていないようなので、これでよしとしようと思います
お礼
遅くなりました この意味をずっと考えていたのですが、global変数の裏での動作(呼び出し)がいまいちわかっていないようです 1回試験受けたのですが、落ちて別の問題をやってだいぶ覚えてきました またよろしくお願いします