37

PythonでRAIIを見つけようとしています。Resource Allocation Is Initialization は、オブジェクトが作成時に初期化される C++ のパターンです。失敗すると、例外がスローされます。このようにして、プログラマーは、オブジェクトが半分構築された状態のままになることは決してないことを知っています。Python はこれだけのことができます。

ただし、RAII は C++ のスコープ規則とも連携して、オブジェクトの迅速な破棄を保証します。変数がスタックからポップされるとすぐに、変数は破棄されます。これは Python で発生する可能性がありますが、外部参照または循環参照がない場合に限られます。

さらに重要なことに、オブジェクトの名前は、それが含まれている関数が終了するまで (場合によってはそれよりも長く) 存在し続けます。モジュール レベルの変数は、モジュールの存続期間中保持されます。

次のようなことをすると、エラーが発生します。

for x in some_list:
    ...

... 100 lines later ...

for i in x:
    # Oops! Forgot to define x first, but... where's my error?
    ...

使用後に名前を手動で削除することもできますが、それは非常に見苦しく、私の努力が必要です。

そして、この場合、Do-What-I-Mean を実行したいと思います。

for x in some_list:
    surface = x.getSurface()
    new_points = []
    for x,y,z in surface.points:
        ...     # Do something with the points
        new_points.append( (x,y,z) )
    surface.points = new_points
    x.setSurface(surface)

Python はある程度の範囲指定を行いますが、インデント レベルではなく、機能レベルでのみ行います。名前を再利用できるように、変数の範囲を限定するためだけに新しい関数を作成する必要があるのはばかげているように思えます。

Python 2.5 には"with" ステートメント がありますが、それには関数を明示的に配置する必要があり__enter____exit__通常は終了ベクトルに関係なく、ファイルやミューテックス ロックなどのリソースをクリーンアップする方向に向いているようです。スコーピングには役立ちません。または、何か不足していますか?

「Python RAII」と「Python スコープ」を検索しましたが、この問題に直接かつ正式に対処するものは見つかりませんでした。私はすべてのPEPに目を通しました。この概念は、Python 内では扱われていないようです。

Python でスコープ変数を使用したいので、私は悪い人ですか? それはあまりにも非Pythonicですか?

私はそれを理解していませんか?

おそらく、言語の動的な側面の利点を取り除こうとしているのでしょう。スコープを強制したい場合があるのは利己的ですか?

怠慢な変数の再利用の間違いをコンパイラ/インタプリタに見つけてもらいたいと思うのは怠惰ですか? そうですね、もちろん怠け者ですが、悪い意味で怠け者なのでしょうか。

4

5 に答える 5

39

tl; dr RAIIは不可能です。一般的にスコープと混同し、これらの余分なスコープを見逃すと、おそらく悪いコードを書いていることになります。

おそらく、私はあなたの質問を受け取らないか、Pythonについていくつかの非常に重要なことを理解していません...まず、スコープに関連付けられた決定論的なオブジェクトの破壊は、ガベージコレクション言語では不可能です。Pythonの変数は単なる参照です。それを指すポインタがスコープから外れるとすぐにmalloc、メモリのチャンクが「d」になるのは望ましくありませんか?参照カウントを使用する場合の実際的な例外ですが、正確な実装を石で設定するのに十分な言語はありませfree

また、CPythonのように参照カウントがある場合でも、それは実装の詳細です。一般に、参照カウントを使用しないさまざまな実装があるPythonを含め、メモリがなくなるまですべてのオブジェクトがハングするようにコーディングする必要があります。

関数呼び出しの残りの部分に存在する名前について:ステートメントを使用して、現在のスコープまたはグローバルスコープから名前を削除できます。delただし、これは手動のメモリ管理とは関係ありません。参照を削除するだけです。これにより、参照されるオブジェクトがGCされる場合と発生しない場合があり、演習のポイントではありません。

  • コードがこれで名前の衝突を引き起こすのに十分な長さである場合は、より小さな関数を作成する必要があります。そして、よりわかりやすく、衝突しにくい名前を使用します。ネストされたループが出力ループの反復変数を上書きする場合も同じです。この問題はまだ発生していないので、名前が十分に説明されていないか、これらのループを分解する必要がありますか?

あなたは正しいです。withスコーピングとは何の関係もありません。決定論的なクリーンアップとは関係ありません(したがって、最終的にはRAIIと重複しますが、手段では重複しません)。

おそらく私は、言語の動的な側面の利点を取り除こうとしています。スコープを強制したい場合があるのは利己的ですか?

いいえ。まともな字句スコープは、動的/静的性に依存しないメリットです。確かに、Python(2〜3はこれをほぼ修正しました)には、この点で弱点がありますが、クロージャの領域にあります。

しかし、「理由」を説明するには、Pythonは新しいスコープを開始する場所を控えめにする必要があります。これは、宣言なしで名前を割り当てると、最も内側/現在のスコープに対してローカルになるためです。したがって、たとえばforループに独自のスコープがある場合、ループ外の変数を簡単に変更することはできません。

コンパイラー/インタープリターに私の怠慢な変数の再利用の間違いを捕まえさせたいのは怠惰ですか?ええ、はい、もちろん私は怠け者ですが、私は悪い意味で怠惰ですか?

繰り返しになりますが、(エラーや落とし穴をもたらすような方法で)名前を誤って再利用することはまれであり、とにかく小さなことだと思います。

編集:これをできるだけ明確にもう一度述べるには:

  • GCを使用する言語でスタックベースのクリーンアップを行うことはできません。定義上、それはおそらくあり得ません。変数は、変数がスコープから外れることを知らず、気にしないヒープ上のオブジェクトへの潜在的に多くの参照の1つであり、すべてのメモリ管理は、GCが実行するときに実行されます。スタックフレームがポップされたときではなく、好きです。リソースのクリーンアップは別の方法で解決されます。以下を参照してください。
  • 決定論的なクリーンアップは、withステートメントを通じて行われます。はい、新しいスコープは導入されていません(以下を参照)。これは、その目的ではないためです。管理対象オブジェクトがバインドされている名前が削除されていないかどうかは関係ありません。それでもクリーンアップは行われましたが、残っているのは「使用できない私に触れないでください」オブジェクト(閉じたファイルストリームなど)です。
  • Pythonには、関数、クラス、モジュールごとにスコープがあります。限目。あなたがそれを好むかどうかにかかわらず、それは言語がどのように機能するかです。よりきめ細かいスコープが必要/「必要」な場合は、コードをよりきめ細かい関数に分割します。よりきめ細かいスコーピングが必要な場合もありますが、そうではありません。この回答の前半で指摘した理由(「編集:」の上の3つの段落)により、これには理由があります。好むと好まざるとにかかわらず、これが言語の仕組みです。
于 2011-02-21T21:17:07.647 に答える
18
  1. あなたは正しいですwith-それは変数のスコープとはまったく関係ありません。

  2. 問題があると思われる場合は、グローバル変数を避けてください。これには、モジュール レベルの変数が含まれます。

  3. Python で状態を非表示にする主なツールはクラスです。

  4. ジェネレーター式 (および Python 3 ではリスト内包表記も) には、独自のスコープがあります。

  5. 関数がローカル変数を見失うほど長い場合は、おそらくコードをリファクタリングする必要があります。

于 2011-02-21T21:00:24.060 に答える
12

ただし、RAII は C++ のスコープ規則とも連携して、オブジェクトの迅速な破棄を保証します。

これは、メモリが代替可能であるという考えに基づいている GC 言語では重要ではないと考えられています。新しいオブジェクトを割り当てるのに十分なメモリが他の場所にある限り、オブジェクトのメモリを再利用する必要はありません。ファイル ハンドル、ソケット、ミューテックスなどの代替不可能なリソースは、特別に扱われる特殊なケースと見なされます (例: with)。これは、すべてのリソースを同じように扱う C++ のモデルとは対照的です。

変数がスタックからポップされるとすぐに、変数は破棄されます。

Python にはスタック変数がありません。C++ の用語では、すべてshared_ptr.

Python はある程度の範囲指定を行いますが、インデント レベルではなく、機能レベルでのみ行います。名前を再利用できるように、変数の範囲を限定するためだけに新しい関数を作成する必要があるのはばかげているように思えます。

また、ジェネレータ内包表記レベル (および 3.x ではすべての内包表記)でのスコープも行います。

ループ変数を壊したくない場合はfor、あまり多くのforループを使用しないでください。append特に、ループで使用するのは非 Pythonicです。それ以外の:

new_points = []
for x,y,z in surface.points:
    ...     # Do something with the points
    new_points.append( (x,y,z) )

書きます:

new_points = [do_something_with(x, y, z) for (x, y, z) in surface.points]

また

# Can be used in Python 2.4-2.7 to reduce scope of variables.
new_points = list(do_something_with(x, y, z) for (x, y, z) in surface.points)
于 2011-02-22T13:30:13.803 に答える
2

基本的に、間違った言語を使用している可能性があります。適切なスコープ規則と信頼できる破棄が必要な場合は、C++ を使用するか、Perl を試してください。メモリがいつ解放されるかについての GC の議論は、的を射ていないようです。これは、ミューテックスやファイル ハンドルなどの他のリソースを解放することです。C# では、参照カウントがゼロになったときに呼び出されるデストラクタと、メモリのリサイクルを決定したときに呼び出されるデストラクタを区別していると思います。人々はメモリの再利用についてそれほど心配していませんが、参照されなくなったらすぐに知りたいと思っています。Python には言語としての可能性があっただけに残念です。しかし、型にはまらないスコープと信頼性の低いデストラクタ (または少なくとも実装に依存するデストラクタ) は、C++ と Perl で得られる能力が否定されていることを意味します。

興味深いのは、GC で古いメモリをリサイクルするのではなく、利用可能な場合は新しいメモリを使用することについてのコメントです。それは、メモリリークが発生するという派手な言い方ではありません:-)

于 2011-08-25T14:08:08.883 に答える