42

私はgithubでソフトウェアを書いています。これは基本的に、いくつかの追加機能を備えたトレイ アイコンです。オプション機能の基本的な依存関係を実際にユーザーにインストールさせることなく、実際に機能するコードを提供したいのですが、使用しないものを実際にインポートしたくないので、このようなコードは「良い解決策":

---- IN LOADING FUNCTION ----
features = []

for path in sys.path:
       if os.path.exists(os.path.join(path, 'pynotify')):
              features.append('pynotify')
       if os.path.exists(os.path.join(path, 'gnomekeyring.so')):
              features.append('gnome-keyring')

#user dialog to ask for stuff
#notifications available, do you want them enabled?
dlg = ConfigDialog(features)

if not dlg.get_notifications():
    features.remove('pynotify')


service_start(features ...)

---- SOMEWHERE ELSE ------

def service_start(features, other_config):

        if 'pynotify' in features:
               import pynotify
               #use pynotify...

ただし、いくつかの問題があります。ユーザーが自分のマシンをフォーマットし、OS の最新バージョンをインストールして、このアプリケーションを再展開すると、警告なしに機能が突然消えます。解決策は、構成ウィンドウにこれを表示することです。

if 'pynotify' in features:
    #gtk checkbox
else:
    #gtk label reading "Get pynotify and enjoy notification pop ups!"

しかし、これが mac の場合、ユーザーが満たすことのできない依存関係を探してワイルド グース チェイスにユーザーを送っていないことをどのように知ることができますか?

2番目の問題は次のとおりです。

if os.path.exists(os.path.join(path, 'gnomekeyring.so')):

問題。ファイルがすべての Linux ディストリビューションで常に gnomekeyring.so と呼ばれていることを確認できますか?

他の人はこれらの機能をどのようにテストしていますか? 基本の問題点

try:
    import pynotify
except:
    pynotify = disabled

コードがグローバルであるということです。これらは散らばっている可能性があり、ユーザーがpynotifyを望まなくても....とにかくロードされます。

では、この問題を解決する最善の方法は何だと人々は考えているのでしょうか?

4

4 に答える 4

55

try:メソッドはグローバルである必要はありません。どのスコープでも使用できるため、実行時にモジュールを「遅延ロード」できます。例えば:

def foo():
    try:
        import external_module
    except ImportError:
        external_module = None 

    if external_module:
        external_module.some_whizzy_feature()
    else:
        print("You could be using a whizzy feature right now, if you had external_module.")

スクリプトが実行されるとき、ロードは試行されませんexternal_module。最初foo()に呼び出され、external_module(利用可能な場合) ロードされ、関数のローカル スコープに挿入されます。モジュールをリロードする必要なく、そのスコープにfoo()再挿入するための後続の呼び出し。external_module

一般に、Python にインポート ロジックを処理させるのが最善です。:-)

于 2009-02-18T22:05:59.867 に答える
16

基本的に上記で手動で行っていることを行うimp モジュールを確認することをお勧めします。したがって、最初にモジュールを探してから、find_module()それを介してload_module()、または単にインポートすることでロードできます(構成を確認した後)。

ところで、except を使用する場合: 関連のないエラーを誤ってキャッチしないように、特定の例外 (ここでは ImportError) を常に追加します。

于 2009-02-18T22:09:38.830 に答える
3

これが良い方法かどうかはわかりませんが、オプションのインポート (を使用importlib) とエラー処理を行う関数を作成しました。

def _optional_import(module: str, name: str = None, package: str = None):
    import importlib
    try:
        module = importlib.import_module(module)
        return module if name is None else getattr(module, name)
    except ImportError as e:
        if package is None:
            package = module
        msg = f"install the '{package}' package to make use of this feature"
        raise ValueError(msg) from e

オプションのモジュールが利用できない場合、ユーザーは少なくとも何をすべきかを理解できます。例えば

# code ...

if file.endswith('.json'):
    from json import load
elif file.endswith('.yaml'):
    # equivalent to 'from yaml import safe_load as load'
    load = _optional_import('yaml', 'safe_load', package='pyyaml')

# code using load ...

このアプローチの主な欠点は、インポートをインラインで行う必要があり、すべてがファイルの先頭にあるとは限らないことです。したがって、この関数のわずかな適応を使用することをお勧めします (関数などをインポートしていると仮定します)。

def _optional_import_(module: str, name: str = None, package: str = None):
    import importlib
    try:
        module = importlib.import_module(module)
        return module if name is None else getattr(module, name)
    except ImportError as e:
        if package is None:
            package = module
        msg = f"install the '{package}' package to make use of this feature"
        import_error = e

        def _failed_import(*args):
            raise ValueError(msg) from import_error

        return _failed_import

これで、残りのインポートでインポートを行うことができ、インポートに失敗した関数が実際に使用された場合にのみエラーが発生します。例えば

from utils import _optional_import_  # let's assume we import the function
from json import load as json_load
yaml_load = _optional_import_('yaml', 'safe_load', package='pyyaml')

# unimportant code ...

with open('test.txt', 'r') as fp:
    result = yaml_load(fp)    # will raise a value error if import was not successful

PS: 返事が遅くなってすみません!

于 2020-08-06T17:32:05.440 に答える
-2

さまざまな機能のさまざまな依存関係の問題を処理する 1 つの方法は、オプション機能をプラグインとして実装することです。こうすることで、ユーザーはアプリでアクティブ化される機能を制御できますが、依存関係を自分で追跡する責任はありません。そのタスクは、各プラグインのインストール時に処理されます。

于 2009-02-19T12:49:09.357 に答える