web-dev-qa-db-ja.com

コードが原因で、Python)に実際のメモリリークが発生する可能性はありますか?

コードの例はありませんが、Python本質的にメモリリークを引き起こすコードを記述できるかどうか知りたいです。

54
orokusaki

はい、可能です。

それはあなたが話しているメモリリークの種類に依存します。純粋なpythonコード内では、Cのようにメモリを「解放することを忘れる」ことはできませんが、参照をどこかにぶら下げたままにすることは可能です。そのような例をいくつか示します:

関数が実行されなくなっても、スタックフレーム全体を存続させている未処理のトレースバックオブジェクト

while game.running():
    try:
        key_press = handle_input()
    except SomeException:
        etype, evalue, tb = sys.exc_info()
        # Do something with tb like inspecting or printing the traceback

このゲームループの愚かな例では、「tb」をローカルに割り当てました。私たちは善意を持っていましたが、このtbには、handle_inputで何が発生していたかというスタックに関するフレーム情報が含まれています。ゲームが続行されると仮定すると、この 'tb'は、handle_inputの次の呼び出しでも、おそらく永久に存続します。 exc_infoのドキュメント ここで、この潜在的な循環参照の問題について説明し、絶対に必要でない場合は、単にtbを割り当てないことをお勧めします。トレースバックを取得する必要がある場合は、たとえば traceback.format_exc

インスタンススコープの代わりにクラスまたはグローバルスコープに値を格納し、それを実現しません。

これは陰湿な方法で発生する可能性がありますが、クラススコープで可変型を定義するときによく発生します。

class Money(object):
    name = ''
    symbols = []   # This is the dangerous line here

    def set_name(self, name):
        self.name = name

    def add_symbol(self, symbol):
        self.symbols.append(symbol)

上記の例では、

m = Money()
m.set_name('Dollar')
m.add_symbol('$')

this特定のバグをすぐに見つけるでしょうが、この場合、クラススコープに変更可能な値を配置し、インスタンススコープで正しくアクセスしても、実際には「フォールスルー"クラスオブジェクト__dict__に。

これをオブジェクトの保持などの特定のコンテキストで使用すると、アプリケーションのヒープが永久に大きくなる可能性があり、たとえば、プロセスをときどき再起動しなかった本番Webアプリケーションで問題が発生する可能性があります。

__del__メソッドも持つクラスの循環参照。

皮肉なことに、__del__が存在すると、サイクリックガベージコレクターがインスタンスをクリーンアップできなくなります。ファイナライズの目的でデストラクタを実行したい場所があるとします。

class ClientConnection(...):
    def __del__(self):
        if self.socket is not None:
            self.socket.close()
            self.socket = None

これはそれ自体で正常に機能するようになり、ソケットが「廃棄」されることを保証するために、OSリソースの優れた管理者であると思われるかもしれません。

ただし、ClientConnectionがUserという参照を保持し、Userが接続への参照を保持している場合は、クリーンアップ時に、ユーザーに接続の参照を解除してもらいたいと思うかもしれません。 これは実際には欠陥です 、ただし、循環GCは操作の正しい順序を認識していないため、クリーンアップできません。

これに対する解決策は、たとえば、ある種のcloseを呼び出してイベントを切断し、そのメソッドに__del__以外の名前を付けるなどのクリーンアップを確実に行うことです。

C拡張機能の実装が不十分であるか、Cライブラリが想定どおりに適切に使用されていません。

Pythonでは、ガベージコレクターを信頼して、使用していないものを破棄します。ただし、CライブラリをラップするC拡張機能を使用する場合、ほとんどの場合、リソースを明示的に閉じるか割り当て解除する必要があります。ほとんどこれは文書化されていますが、この明示的な割り当て解除を行う必要がないことに慣れているpythonプログラマーは、知らないうちにそのライブラリへのハンドル(関数から戻るなど)を破棄する可能性がありますそのリソースが保持されています。

予想以上に多くのクロージャーを含むスコープ

class User:
    def set_profile(self, profile):
        def on_completed(result):
            if result.success:
                self.profile = profile

        self._db.execute(
            change={'profile': profile},
            on_complete=on_completed
        )

この不自然な例では、DB呼び出しが完了するとon_completedにコールバックする、ある種の「非同期」呼び出しを使用しているように見えます(実装は約束されていた可能性があり、最終的には同じ結果になります)。

on_completed割り当てを実行するために、self.profileクロージャがselfへの参照をバインドしていることを理解していない可能性があります。現在、おそらくDBクライアントは、アクティブなクエリとクロージャへのポインタを追跡して、それらが完了したときに呼び出すようにし(非同期であるため)、何らかの理由でクラッシュすると言います。 DBクライアントがコールバックなどを正しくクリーンアップしない場合、この場合、DBクライアントにはon_completedへの参照があり、これには_dbを保持するUserへの参照があります。これで循環参照が作成されました。収集されることはありません。

(循環参照がなくても、クロージャーがローカルやインスタンスをバインドするという事実により、収集されたと思われる値が長期間存続する可能性があります。これには、ソケット、クライアント、大きなバッファー、ツリー全体が含まれる可能性があります)

変更可能な型であるデフォルトのパラメーター

def foo(a=[]):
    a.append(time.time())
    return a

これは不自然な例ですが、aのデフォルト値が空のリストであることは、実際にはへの参照である場合、それに追加することを意味すると考えられますリスト。これもまた、あなたがそれをしたことを知らずに無制限の成長を引き起こすかもしれません。

106
Crast

メモリリークの古典的な定義は、一度使用されたメモリで、現在は使用されていませんが、再利用されていません。純粋なPythonコードでは、これはほぼ不可能です。しかし、Antoineが指摘しているように、データ構造を無制限に拡張できるようにすることで、誤ってすべてのメモリを消費するという影響を簡単に受けることができます。すべてのデータを保持する必要があります。

もちろん、C拡張機能を使用すると、管理されていない領域に戻り、あらゆることが可能になります。

15
Ned Batchelder

もちろんできます。メモリリークの典型的な例は、手動でフラッシュすることがなく、自動エビクションポリシーがないキャッシュを構築する場合です。

11
Antoine P.

割り当てを解除したことを忘れたために、スコープ外に割り当てられたオブジェクトを孤立させるという意味では、違います。 Pythonはスコープ外のオブジェクトの割り当てを自動的に解除します( ガベージコレクション )。しかし、@ Antioneが話しているという意味では、そうです。

0
Rob Curtis