7

私は現在、ユーザーが「プラグイン」タイプのアーキテクチャを介してそれを拡張できるようにするアプリケーションを書いています。彼らは私が提供するBaseClassオブジェクトに基づいて追加のPythonクラスを書くことができ、これらはさまざまなアプリケーション信号に対してロードされます。プラグインとしてロードされるクラスの正確な数と名前は、アプリケーションが開始される前は不明ですが、起動時に1回だけロードされます。

これに取り組むための最良の方法を研究している間に、私は2つの一般的な解決策を思いつきました。

オプション1-imp、pkgutilなどを使用して自分でロール
します。 たとえば、この回答またはこれを参照してください。

オプション2-プラグインマネージャーライブラリを使用する
カップルをランダムに選択する

私の質問は、新しいプラグインをロードするためにアプリケーションを再起動する必要があるという条件で、このSOの回答のようなものから着想を得たものよりも上記の方法の利点はありますか?

import inspect
import sys
import my_plugins

def predicate(c):
    # filter to classes
    return inspect.isclass(c)

def load_plugins():
    for name, obj in inspect.getmembers(sys.modules['my_plugins'], predicate):
        obj.register_signals()

上記のアプローチと比較して、このアプローチに不利な点はありますか?(すべてのプラグインが同じファイルにある必要があることを除いて)ありがとう!

コメントの編集
はさらに情報を要求します...私が追加すると考えることができる唯一の追加のことは、プラグインがサブスクライブする信号を提供するためにブリンカーライブラリを使用することです。各プラグインは、さまざまなタイプのさまざまなシグナルをサブスクライブする可能性があるため、独自の「登録」メソッドが必要です。

4

3 に答える 3

10

Python 3.6以降、新しいサブクラスが作成されるたびに、基本クラスで呼び出される新しいクラスメソッド__init_subclass__が追加されています。

このメソッドは、メタクラスを削除することにより、上記のwill-hartによって提供されるソリューションをさらに簡素化できます。

この__init_subclass__メソッドは、PEP 487:クラス作成のより簡単なカスタマイズで導入されました。PEPには、プラグインアーキテクチャの最小限の例が付属しています。

メタクラスを使用せずにサブクラスの作成をカスタマイズできるようになりました。新しい__init_subclass__サブクラスが作成されるたびに、新しいクラスメソッドが基本クラスで呼び出されます。

class PluginBase:
    subclasses = []

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.subclasses.append(cls)

class Plugin1(PluginBase):
    pass

class Plugin2(PluginBase):
    pass

上記のPEPの例では、Plugin.pluginsフィールド内のクラスへの参照が格納されています。

プラグインクラスのインスタンスを保存する場合は、次のような構造を使用できます。

class Plugin:
    """Base class for all plugins. Singleton instances of subclasses are created automatically and stored in Plugin.plugins class field."""
    plugins = []

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.plugins.append(cls())

class MyPlugin1(Plugin):
    def __init__(self):
        print("MyPlugin1 instance created")

    def do_work(self):
        print("Do something")

class MyPlugin2(Plugin):
    def __init__(self):
        print("MyPlugin2 instance created")

    def do_work(self):
        print("Do something else")

for plugin in Plugin.plugins:
    plugin.do_work()

出力:

MyPlugin1 instance created
MyPlugin2 instance created
Do something
Do something else
于 2017-01-29T18:19:08.463 に答える
7

メタクラスアプローチは、Python <3.6のこの問題に役立ちます(Python 3.6以降の@quasoftの回答を参照してください)。これは非常にシンプルで、インポートされたモジュールに対して自動的に機能します。さらに、複雑なロジックをプラグイン登録にほとんど労力をかけずに適用できます。必要なもの:

メタクラスアプローチは次のように機能します。

1)PluginMountすべてのプラグインのリストを維持するカスタムメタクラスが定義されています

2)メタクラスとしてPlugin設定するクラスが定義されているPluginMount

Plugin3)たとえば、-から派生したオブジェクトMyPluginがインポートさ__init__れると、メタクラスのメソッドがトリガーされます。これにより、プラグインが登録され、アプリケーション固有のロジックとイベントサブスクリプションが実行されます。

または、PluginMount.__init__ロジックを挿入すると、派生クラスPluginMount.__new__の新しいインスタンスが作成されるたびに呼び出されます。Plugin

class PluginMount(type):
    """
    A plugin mount point derived from:
        http://martyalchin.com/2008/jan/10/simple-plugin-framework/
    Acts as a metaclass which creates anything inheriting from Plugin
    """

    def __init__(cls, name, bases, attrs):
        """Called when a Plugin derived class is imported"""

        if not hasattr(cls, 'plugins'):
            # Called when the metaclass is first instantiated
            cls.plugins = []
        else:
            # Called when a plugin class is imported
            cls.register_plugin(cls)

    def register_plugin(cls, plugin):
        """Add the plugin to the plugin list and perform any registration logic"""

        # create a plugin instance and store it
        # optionally you could just store the plugin class and lazily instantiate
        instance = plugin()

        # save the plugin reference
        cls.plugins.append(instance)

        # apply plugin logic - in this case connect the plugin to blinker signals
        # this must be defined in the derived class
        instance.register_signals()

次に、次のような基本プラグインクラスを作成します。

class Plugin(object):
    """A plugin which must provide a register_signals() method"""
    __metaclass__ = PluginMount

最後に、実際のプラグインクラスは次のようになります。

class MyPlugin(Plugin):
    def register_signals(self):
        print "Class created and registering signals"

    def other_plugin_stuff(self):
        print "I can do other plugin stuff"

プラグインには、インポートしたPythonモジュールからアクセスできますPlugin

for plugin in Plugin.plugins:
    plugin.other_plugin_stuff()

完全な実例を見る

于 2013-07-01T09:38:06.680 に答える
0

ウィルハートからのアプローチは私にとって最も有用なものでした!より多くの制御が必要なため、次のような関数でPluginBaseクラスをラップしました。

def get_plugin_base(name='Plugin',
                       cls=object,
                       metaclass=PluginMount):

    def iter_func(self):
        for mod in self._models:
            yield mod

    bases = not isinstance(cls, tuple) and (cls,) or cls

    class_dict = dict(
        _models=None,
        session=None
    )

    class_dict['__iter__'] = iter_func

    return metaclass(name, bases, class_dict)

その後:

from plugin import get_plugin_base
Plugin = get_plugin_base()

これにより、ベースクラスを追加したり、別のメタクラスに切り替えたりすることができます。

于 2016-06-14T10:15:53.723 に答える