デフォルトのスコープ
私は少し前に Lua を学びましたが、Lua のデフォルトのスコープが Python のデフォルトのスコープとは逆であることがわかりました。 Lua が変数を定義する場合、変数のデフォルトのスコープはグローバルです。これはあまり正確ではありません。Lua が x = 1 のようなステートメントを実行する場合、現在の環境から開始して x レイヤーを検索します。 find x グローバル変数は特定の状況下でのみ定義されます)、Python が変数を定義する場合、変数のスコープはデフォルトでローカル (現在のブロック) になります。さらに、Lua では変数を定義するときに変数の前に local キーワードを追加することでローカル変数を定義できますが、Python には同様のキーワードがなく、Python 変数は現在のブロックでのみ定義できます。
グローバル変数は良くないことはわかっていますが、ローカル変数は良いものです。プログラムを作成するときは、ローカル変数を使用するようにしてください。そのため、最初は Python の規則の方が優れていると考えていました。その利点は、入力が少なくて済むことです。 Lua プログラムを書くとき、私は心の中で「ローカルを忘れるな、ローカルを忘れるな」と言い続けていますが、時々、いくつかのことがネットをすり抜けて魔法のバグを引き起こすことがあります。
クロージャー
Python のデフォルトのスコープの問題に初めて気づいたのは、クロージャーを使用するときでした。クロージャに関しては、Lua チュートリアルに次のコードがあります:
function new_counter() local n = 0 local function counter() n = n + 1 return n end return counter end c1 = new_counter() c2 = new_counter() print(c1()) -- 打印1 print(c2()) -- 打印1 print(c1()) -- 打印2 print(c2()) -- 打印2
クロージャの本質は、SICP の第 3 章の環境モデルを参照できます。ここでは、関数カウンターにプライベート メンバー n があると簡単に想像できます。
ここで質問が来ます: Python を使用して、同じ関数を持つクロージャを実装したいのですが?
まず、Lua コードを Python コードに直接書き換えます:
def new_counter(): n = 0 def counter(): n = n + 1 return n return counter
その後、私は唖然としました。このプログラムは実行できず、未割り当ての変数 n が 4 行目でアクセスされます。エラーの理由は、Python がクロージャをサポートしていないためではなく、Python の代入操作が上位レベルの変数 n にアクセスできないことです (実際、Python はそれを代入ではなくローカル変数の定義であるとみなします。ローカル変数の定義とPython での代入操作は構文的に矛盾しているため、Python は単に再定義可能な定義ステートメントのみをサポートしています)。 Python のデフォルトのスコープはローカルであるため、プログラムが n = n + 1 まで実行されると、Python はこれが変数定義操作であると判断し、(初期化されていない) ローカル変数 n を作成し、このレベルで new_counter n を正常に上書きしてから試行します。 n + 1 を n に代入しようとしましたが、n が初期化されていないため、n + 1 を計算できないため、プログラムはエラーを報告します。
クロージャ代入の関数を実装するには、ちょっとしたトリックを使うことができます:
def new_counter(): n = [0] def counter(): n[0] = n[0] + 1 return n[0] return counter
n[0] = n[0] + 1 が間違っていない理由は、ここに等号があるためです。とその前の n = n + 1 の等号は意味が異なります。 n[0] = n[0] + 1 の等号は、n の属性を変更することを意味します。実際、この等号は最終的に list の __setitem__ メソッドを呼び出します。 n = n + 1 の等号は、値 n + 1 を現在の環境のシンボル n にバインドすることを意味します (シンボル n が現在の環境にすでに存在する場合は、それを上書きします)。
もう一つの余談: くそー、こんなふうに書く必要はない、とても醜い。とにかく、Python はオブジェクト指向言語です。カウンターを実装するには、クラスを作成する必要があります。
定義と代入の分離
まず、Python と Lua のデフォルト スコープの特徴をまとめてみましょう:
プログラムを作成するときは、ローカル キーワードを覚えておいてください (本当に定義したい場合を除く)。グローバル変数)、誤ってローカルを忘れた場合、プロンプトは表示されず、バグが修正されるまで待ちます。
2. Python のデフォルトのスコープはローカルです。プログラムを書く際の精神的な負担は少なくなりますが、上位レベルの変数に値を代入する機能が失われます (変更は可能ですが、言語がより複雑になります)。 。
両方のデフォルトのスコープに問題があるようですね?個人的には、上記の問題の原因は、Python と Lua が定義と代入の分離を認識していないことにあると考えています。 Python と Lua では、x = 1 のようなステートメントは定義または代入を表すことができます。実際、これら 2 つの言語に限らず、多くの高級言語は定義と代入の分離を実現していません。定義と代入は機能的には似ていますが、本質的には異なります。
以下では、例として x = 1 を使用して定義と代入を説明します:
定義とは、シンボル x を現在の環境に登録し、1 に初期化することを意味します。 x がすでに存在する場合、エラーが報告される (再定義は許可されない) か、上書きされます (再定義は許可されます)。
代入とは、現在の環境から始めて、シンボル x が初めて見つかるまでレイヤーごとに検索し、その値を 1 に変更することを意味します。見つからない場合は、エラーが報告されます (変数が存在しません)。
ここで、定義と代入を分離するために Python を少し変更します。定義を示すには「:=」を使用し、代入を示すには「=」を使用します。次に、実行できない new_counter サンプルを書き直します (Python の代入操作はローカル変数の定義と競合します。つまり、Python には実際には代入操作がありません。そのため、単純にすべての「=」を「:=」に置き換えるだけで済みます)。 ) 、どこが間違っているかを確認してください:
def new_counter(): n := 0 def counter(): n := n + 1 return n return counter
なぜこのプログラムが間違っているのかは明らかです。 4 行目で必要なのは、定義操作ではなく代入操作です。これを正しい書き方に変更します:
def new_counter(): n := 0 def counter(): n = n + 1 return n return counter
このようにすると、正しく実行されます (Python インタープリター XD の修正バージョンがある場合)。
最後に、Lua について話しましょう。 Lua では、定義と割り当ての分離が半分達成されたように感じます。 local キーワードを含む等号ステートメントを定義する必要があります。問題は、ローカルのない等号ステートメントです。この種のステートメントの場合、Lua は次の処理を実行します。まず代入を試み、代入が失敗した (変数が存在しない) 場合は、最も外側の環境 (グローバル環境) で変数を定義します。つまり、ローカルのない等号ステートメントでは、定義と代入が混在します。さらに、定義と割り当ての分離が達成されている場合、デフォルトのスコープの問題を考慮する必要はありません。すべての定義は現在の環境で定義され、すべてローカル変数を定義します。関数本体や他のブロックでグローバル変数を定義するメリットはまったく思いつきません。