70

IPython を使用して Python で大規模なプロジェクト (複数のファイルとフォルダーに分割) を開発しているときに、インポートされたモジュールのキャッシュの問題に遭遇しました。

問題は、そのモジュールが変更された場合でも、命令import moduleがモジュールを一度しか読み取らないことです! そのため、パッケージ内の何かを変更するたびに、IPython を終了して再起動する必要があります。痛い。

一部のモジュールを適切に強制的にリロードする方法はありますか? または、Python がそれらをキャッシュしないようにする方がよいでしょうか?

いくつかのアプローチを試しましたが、どれもうまくいきません。特に、一部のモジュールや変数が不思議なことに等しくなるなど、本当に、本当に奇妙なバグに遭遇しNoneます...

私が見つけた唯一の賢明なリソースはReloading Python modules、 from pyunit ですが、チェックしていません。私はそのようなものが欲しいです。

IPython を再起動するか、何らかの方法で Python インタープリターを再起動することをお勧めします。

では、Python で開発している場合、この問題に対してどのような解決策を見つけましたか?

編集

明確にするために: 明らかに、モジュールの以前の状態に依存するいくつかの古い変数が残っている可能性があることは理解しています。それは私には問題ありません。奇妙なエラーが発生することなくモジュールを強制的にリロードすることが Python で非常に難しいのはなぜですか?

より具体的には、モジュール全体が1 つのファイルmodule.pyにある場合、次のようにするとうまくいきます。

import sys
try:
    del sys.modules['module']
except AttributeError:
    pass
import module

obj = module.my_class()

このコードは美しく機能し、IPython を何ヶ月もやめることなく開発できます。

ただし、モジュールが複数のサブモジュールで構成されている場合は常に、地獄が解き放たれます。

import os
for mod in ['module.submod1', 'module.submod2']:
    try:
        del sys.module[mod]
    except AttributeError:
        pass
# sometimes this works, sometimes not. WHY?

モジュールが 1 つの大きなファイルにあるのか、複数のサブモジュールにあるのかによって、Python でこれほど異なるのはなぜですか? なぜそのアプローチがうまくいかないのでしょうか??

4

8 に答える 8

29

importモジュールがにあるかどうかを確認し、あるsys.modules場合はそれを返します。インポートしてモジュールをディスクから新しくロードする場合は、sys.modules最初に適切なキーを削除できます。

reloadモジュールオブジェクトを指定すると、ディスクからリロードし、に配置される組み込み関数がありますsys.modules編集-実際には、ディスク上のファイルからコードを再コンパイルしてから、既存のモジュールで再評価します__dict__。新しいモジュールオブジェクトを作成することとは非常に異なる可能性があります。

マイク・グラハムは正しいです。不要になったモジュールのコンテンツを参照するライブオブジェクトがいくつかある場合でも、正しくリロードするのは困難です。既存のオブジェクトは、インスタンス化されたクラスを引き続き参照しますが、これは明らかな問題ですが、それによって作成されたすべての参照はfrom module import symbol、古いバージョンのモジュールのオブジェクトを指します。多くの微妙に間違ったことが可能です。

編集:私は、通訳を再開することが断然最も信頼できることであるというコンセンサスに同意します。しかし、デバッグの目的で、次のようなことを試すことができると思います。これが機能しないコーナーケースがあることは確かですが、パッケージにモジュールをロードすることで(そうでなければ)あまりにもクレイジーなことをしていなければ、それは役に立つかもしれません

def reload_package(root_module):
    package_name = root_module.__name__

    # get a reference to each loaded module
    loaded_package_modules = dict([
        (key, value) for key, value in sys.modules.items() 
        if key.startswith(package_name) and isinstance(value, types.ModuleType)])

    # delete references to these loaded modules from sys.modules
    for key in loaded_package_modules:
        del sys.modules[key]

    # load each of the modules again; 
    # make old modules share state with new modules
    for key in loaded_package_modules:
        print 'loading %s' % key
        newmodule = __import__(key)
        oldmodule = loaded_package_modules[key]
        oldmodule.__dict__.clear()
        oldmodule.__dict__.update(newmodule.__dict__)

私はそのように非常に簡単にテストしました:

import email, email.mime, email.mime.application
reload_package(email)

印刷:

reloading email.iterators
reloading email.mime
reloading email.quoprimime
reloading email.encoders
reloading email.errors
reloading email
reloading email.charset
reloading email.mime.application
reloading email._parseaddr
reloading email.utils
reloading email.mime.base
reloading email.message
reloading email.mime.nonmultipart
reloading email.base64mime
于 2010-05-27T06:28:50.307 に答える
18

インタープリターを終了して再起動するのが最善の解決策です。もはや存在しないモジュールからのオブジェクトが存在する可能性があり、モジュールが状態を保存することがあり、ユースケースが実際にホットリロードを許可している場合でも、考えるのは複雑すぎるため、あらゆる種類のライブリロードまたはキャッシュなしの戦略はシームレスに機能しません。それだけの価値があります。

于 2010-05-27T06:20:29.513 に答える
14

IPython には、各関数呼び出しの前にインポートを自動的に繰り返すautoreload 拡張機能が付属しています。少なくとも単純なケースでは機能しますが、あまり依存しないでください。私の経験では、特にコードの変更が間接的にインポートされたコードでのみ発生する場合は、インタープリターの再起動が時々必要になります。

リンク先ページの使用例:

In [1]: %load_ext autoreload

In [2]: %autoreload 2

In [3]: from foo import some_function

In [4]: some_function()
Out[4]: 42

In [5]: # open foo.py in an editor and change some_function to return 43

In [6]: some_function()
Out[6]: 43
于 2013-07-09T14:46:28.193 に答える
3

PEP 302で説明されているインポート フック機構を使用して、モジュール自体をロードするのではなく、基になるモジュール オブジェクトで必要なことを何でも実行できるようにする何らかの種類のプロキシ オブジェクトをロードできます — リロード、参照の削除など。

追加の利点は、現在の既存のコードを変更する必要がなく、この追加のモジュール機能をコード内の 1 つのポイント (実際に finder に追加する場所) から切り離すことができることですsys.meta_path

実装に関するいくつかの考え: ビルトイン以外のモジュールを見つけることに同意するファインダーを作成し (ビルトイン モジュールとは何の関係もありません)、types.ModuleType実際のモジュール オブジェクトの代わりにサブクラス化されたプロキシ オブジェクトを返すローダーを作成します。ローダー オブジェクトは、読み込まれたモジュールへの明示的な参照を に作成する必要はありませんsys.modulesが、強くお勧めします。プロキシ オブジェクトはすべてをキャッチして転送し、__getattr__参照を保持している基になる実際のモジュールに転送する必要があります。おそらく定義する必要はありません__setattr____delattr____getattribute__プロキシ メソッドで実際のモジュール コンテンツを非表示にしないためです。したがって、何らかの方法でプロキシと通信する必要があります。特別なメソッドを作成して、基になる参照を削除し、モジュールをインポートし、返されたプロキシから参照を抽出し、プロキシを削除して、再ロードされたモジュールへの参照を保持できます。ふぅ、恐ろしく見えますが、毎回 Python をリロードしなくても問題は解決するはずです。

于 2010-10-12T02:29:57.497 に答える
3

プロジェクトで PythonNet を使用しています。幸いなことに、この問題を完全に解決できるコマンドがあることがわかりました。

using (Py.GIL())
        {
            dynamic mod = Py.Import(this.moduleName);
            if (mod == null)
                throw new Exception( string.Format("Cannot find module {0}. Python script may not be complied successfully or module name is illegal.", this.moduleName));

            // This command works perfect for me!
            PythonEngine.ReloadModule(mod);

            dynamic instance = mod.ClassName();
于 2016-11-18T00:53:54.820 に答える