12

Python 2.7 モジュールの形で非常に基本的なプラットフォームを構築しています。このモジュールには、入力されたユーザー コマンドが関数呼び出しにマップされる read-eval-print ループがあります。私は自分のプラットフォーム用のプラグイン モジュールを簡単にビルドできるようにしようとしているので、関数呼び出しはメイン モジュールから任意のプラグイン モジュールになります。プラグインビルダーが自分の関数をトリガーしたいコマンドを指定できるようにしたいので、メインモジュールの command->function dict にマッピングをリモートで入力する Pythonic の方法を探していました。プラグイン モジュール。

私はいくつかのことを見てきました:

  1. メソッド名の解析: Main モジュールはプラグイン モジュールをインポートし、特定の形式に一致するメソッド名をスキャンします。たとえば、download_file_command(file) メソッドを "download file" -> download_file_command として dict に追加する場合があります。ただし、簡潔で入力しやすいコマンド名 (「dl」など) を取得するには、関数の名前も短くする必要があり、コードの可読性には適していません。また、プラグイン開発者は正確な命名形式に準拠する必要があります。

  2. クロスモジュール デコレータ:デコレータを使用すると、プラグイン開発者は自分の関数に好きな名前を付けて、@Main.register("dl") のようなものを追加できますが、別のモジュールの名前空間を変更し、グローバルな状態を維持する必要があります。メインモジュール。これは非常に悪いことだと理解しています。

  3. 同じモジュール デコレーター:上記と同じロジックを使用して、関数の名前をコマンド名に追加するデコレーターを追加できます->プラグイン モジュールにローカルな関数マッピングを行い、API 呼び出しでメイン モジュールへのマッピングを取得します。ただし、これには特定のメソッドが常に存在または継承されている必要があり、デコレータに関する私の理解が正しければ、関数は最初に実行されたときにのみ登録され、その後は不必要に再登録されます。

したがって、私が本当に必要としているのは、関数をトリガーするコマンド名で関数に注釈を付けるPythonicの方法であり、その方法は関数の名前にはなりません。モジュールをインポートするときにコマンド名 -> 関数のマッピングを抽出できるようにする必要があり、プラグイン開発者側の作業が少しでも減れば、大きなプラスになります。

助けてくれてありがとう。Python の理解に不備があればお詫びします。私はその言語に比較的慣れていません。

4

3 に答える 3

12

@ericstalbot の回答の最初の部分を構築または実行すると、次のようなデコレータを使用すると便利な場合があります。

################################################################################
import functools
def register(command_name):
    def wrapped(fn):
        @functools.wraps(fn)
        def wrapped_f(*args, **kwargs):
            return fn(*args, **kwargs)
        wrapped_f.__doc__ += "(command=%s)" % command_name
        wrapped_f.command_name = command_name
        return wrapped_f
    return wrapped
################################################################################
@register('cp')
def copy_all_the_files(*args, **kwargs):
    """Copy many files."""
    print "copy_all_the_files:", args, kwargs
################################################################################

print  "Command Name: ", copy_all_the_files.command_name
print  "Docstring   : ", copy_all_the_files.__doc__

copy_all_the_files("a", "b", keep=True)

実行時の出力:

Command Name:  cp
Docstring   :  Copy many files.(command=cp)
copy_all_the_files: ('a', 'b') {'keep': True}
于 2013-02-13T10:28:52.363 に答える
7

ユーザー定義関数は、任意の属性を持つことができます。したがって、プラグイン関数が特定の名前の属性を持つように指定できます。例えば:

def a():
  return 1

a.command_name = 'get_one'

次に、モジュールで次のようなマッピングを作成できます。

import inspect #from standard library

import plugin

mapping = {}

for v in plugin.__dict__.itervalues():
    if inspect.isfunction(v) and v.hasattr('command_name'):
        mapping[v.command_name] = v

ユーザー定義関数の任意の属性について読むには、ドキュメントを参照してください

于 2013-02-12T12:36:38.207 に答える
2

プラグインシステムには2つの部分があります。

  1. プラグインを発見する
  2. プラグインでコード実行をトリガーする

あなたの質問で提案された解決策は、2番目の部分だけを扱っています。

要件に応じて両方を実装する方法はたくさんあります。たとえば、プラグインを有効にするには、アプリケーションの構成ファイルで指定できます。

plugins = some_package.plugin_for_your_app
    another_plugin_module
    # ...

プラグインモジュールのロードを実装するには:

plugins = [importlib.import_module(name) for name in config.get("plugins")]

辞書を取得するにはcommand name -> function::

commands = {name: func 
            for plugin in plugins
            for name, func in plugin.get_commands().items()}

プラグインの作成者は、プレフィックスやデコレータを使用するなど、任意の方法を使用して実装できます。メインアプリケーションは、各プラグインのコマンドディクショナリを返すget_commands()限り、気にする必要はありません。get_commands()

some_plugin.py:(完全なソース):

def f(a, b):
    return a + b

def get_commands():
    return {"add": f, "multiply": lambda x,y: x*y}

add2つのコマンドを定義しますmultiply

于 2013-02-12T11:47:05.327 に答える