2013-02-12 9 views
5

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

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

  1. メソッド名の解析:メインモジュールは、プラグインモジュール をインポートして、特定のフォーマットに一致するメソッド名のためにそれをスキャンします。 の例では、download_file_command(file)メソッドを dictに "download file" - > download_file_commandというように追加することがあります。しかし、 の簡潔で使いやすいコマンド名(「dl」など)を取得するには、 関数の名前も短くする必要があります。これはコード の読みやすさには適していません。また、プラグイン開発者は、正確な命名形式 に従う必要があります。

  2. クロスモジュールのデコレータ:デコレータは、彼が望んでいるものは何でも彼の機能に名前を付ける プラグイン開発者を聞かせて、単に @ Main.register(「DL」)のようなものを追加し、彼らは必ずしも は私の両方のことを必要とするであろうう別のモジュールのネームスペースを変更して、メインモジュールにグローバル状態の を保持します。私はこれがとても悪いことを理解しています。

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

このように、私が本当に必要なのは、それをトリガーするコマンド名と機能に注釈を付けるためのPython的な方法があり、その方法は、関数の名前にすることはできません。モジュールをインポートするときにコマンド名 - >関数マッピングを抽出できるようにする必要があります。プラグインの開発者側の作業はそれほど大きくはありません。

私のPythonの理解に何らかの欠陥がある場合は、助けてくれてありがとう、謝罪します。私はその言語には比較的新しいです。

  1. 出会うプラグイン
  2. トリガープラグイン

あなたの質問アドレスのみ第二部では、提案ソリューションのいくつかのコード実行:

+1

はちょうどあなたがこれを実現チェックが、デコレータ(および機能の定義)は、実行時に実行するには、Pythonはコンパイル時にない唯一のものは、あなたのコードをコンパイルしています。 – Duncan

+1

方法2で何も間違っていません。 'import plugin'のようなことをして、' @ plugin.register( 'my_method_name')\ ndef somefunc_meeting_an_api_spec():... ' –

+0

[plac](http://pypi.python.org/pypi/plac)はそれを行います。 – georg

答えて

4

ユーザー定義関数は任意の属性を持つことができます。そのため、プラグイン関数に特定の名前の属性があることを指定できます。その後

、あなたのモジュールで、あなたがこのようなマッピングを構築することができ:例えばthe docs

+0

これはまさに私が探していたタイプのものです! – mieubrisse

+0

私はこの質問が少し古いことを認識しますが、ここで私の関連する質問を参照してくださいhttp://stackoverflow.com/questions/37192712/how-can-attributes-on-functions-survive-wrapping。関数に格納された属性を使って折り返す問題に遭遇しましたか? – matthewatabet

1

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

プラグインを有効にするために、あなたの要件などに応じて、両方を実装するための多くの方法、彼らはあなたのアプリケーションの構成ファイルで指定することができますプラグインモジュールのロードを実装するには

plugins = some_package.plugin_for_your_app 
    another_plugin_module 
    # ... 

command name -> function

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

plugins = [importlib.import_module(name) for name in config.get("plugins")] 
は辞書を取得するには0

プラグインの作成者は、接頭辞やデコレータなどを使用して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} 

これは、2つのコマンドaddmultiplyを定義します。

+0

ありがとうございます。私はこれに背後にあるデザインの説明が好きです。この説明は、get_commands()リストを実装するスーパークラスが良いアイデアかもしれないことを示唆しているので、開発者はできるだけ多くの作業をする必要があります。 – mieubrisse

+0

@mieubrisse:クラスは不要です。私は完全なプラグインの例を追加しました。 – jfs

4

ビルを参照するか、または上に立って、ユーザー定義関数のための任意の属性について読むために

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 

を@ 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} 
関連する問題