如何将方法从文件加载到现有类中(a plugin'方法)

发布于 2025-02-09 12:06:04 字数 2476 浏览 2 评论 0原文

如果您愿意的话,请给我打电话奇怪,但很多人都有,但是我有一个很大的课程,我想通过从插件目录中加载的方法进行扩展。本质上,我正在猴子修补班。我几乎有效的方法,但是加载的方法并没有“看到” __ Main __中定义的全球。理想情况下,我希望一种告诉Globals()(或实际使用的任何机制来定位全局变量)使用__ main __中存在的方法。这是我拥有的代码(为了简洁起见):

#!/usr/bin/env python3

import importlib
import os
import types

main_global = "Hi, I'm in main"

class MyClass:

    def __init__(self, plugin_dir=None):

        if plugin_dir:
            self.load_plugins(plugin_dir, ext="plugin")

    def load_plugins(self, plugin_dir, ext):
        """ Load plugins

            Plugins are files in 'plugin_dir' that have the given extension.
            The functions defined within are imported as methods of this class.
        """
        cls = self.__class__

        # First check that we're not importing the same extension twice into
        # the same class.
        try:
            plugins = getattr(cls, "_plugins")
        except AttributeError:
            plugins = set()
            setattr(cls, "_plugins", plugins)

        if ext in plugins:
            return
        plugins.add(ext)

        for file in os.listdir(plugin_dir):
            if not file.endswith(ext):
                continue
            filename = os.path.join(plugin_dir, file)

            loader = importlib.machinery.SourceFileLoader("bar", filename)
            module = types.ModuleType(loader.name)
            loader.exec_module(module)

            for name in dir(module):
                if name.startswith("__"):
                    continue

                obj = getattr(module, name)
                if callable(obj):
                    obj = obj.__get__(self, cls)
                setattr(cls, name, obj)

z = MyClass(plugin_dir="plugins")
z.foo("Hello")

这是插件目录中的'foo.plugin':

#!/usr/bin/env python3

foo_global = "I am global within foo"

def foo(self, value):
  print(f"I am foo, called with {self} and {value}")
  print(f"foo_global = {foo_global}")
  print(f"main_global = {main_global}")

输出是...

I am foo, called with <__main__.MyClass object at 0x7fd4680bfac8> and Hello
foo_global = I am global within foo
Traceback (most recent call last):
  File "./plugged", line 55, in <module>
    z.foo("Hello")
  File "plugins/foo.plugin", line 8, in foo
    print(f"main_global = {main_global}")
NameError: name 'main_global' is not defined

我知道这一切都感觉有点“ hacky”,但这是一个挑战因此,请不要在风格等上发火。

想法,学会的朋友?

Call me weird if you like, many have before, but I have a large class which I'd like to make extensible with methods loaded from a plugin directory. Essentially, I'm monkey patching the class. What I have almost works but the method loaded doesn't 'see' the globals defined in __main__. Ideally I'd like a way to tell globals() (or whatever mechanism is actually used to locate global variables) to use that existing in __main__. Here is the code I have (trimmed for the sake of brevity):

#!/usr/bin/env python3

import importlib
import os
import types

main_global = "Hi, I'm in main"

class MyClass:

    def __init__(self, plugin_dir=None):

        if plugin_dir:
            self.load_plugins(plugin_dir, ext="plugin")

    def load_plugins(self, plugin_dir, ext):
        """ Load plugins

            Plugins are files in 'plugin_dir' that have the given extension.
            The functions defined within are imported as methods of this class.
        """
        cls = self.__class__

        # First check that we're not importing the same extension twice into
        # the same class.
        try:
            plugins = getattr(cls, "_plugins")
        except AttributeError:
            plugins = set()
            setattr(cls, "_plugins", plugins)

        if ext in plugins:
            return
        plugins.add(ext)

        for file in os.listdir(plugin_dir):
            if not file.endswith(ext):
                continue
            filename = os.path.join(plugin_dir, file)

            loader = importlib.machinery.SourceFileLoader("bar", filename)
            module = types.ModuleType(loader.name)
            loader.exec_module(module)

            for name in dir(module):
                if name.startswith("__"):
                    continue

                obj = getattr(module, name)
                if callable(obj):
                    obj = obj.__get__(self, cls)
                setattr(cls, name, obj)

z = MyClass(plugin_dir="plugins")
z.foo("Hello")

And this is 'foo.plugin' from the plugins directory:

#!/usr/bin/env python3

foo_global = "I am global within foo"

def foo(self, value):
  print(f"I am foo, called with {self} and {value}")
  print(f"foo_global = {foo_global}")
  print(f"main_global = {main_global}")

The output is...

I am foo, called with <__main__.MyClass object at 0x7fd4680bfac8> and Hello
foo_global = I am global within foo
Traceback (most recent call last):
  File "./plugged", line 55, in <module>
    z.foo("Hello")
  File "plugins/foo.plugin", line 8, in foo
    print(f"main_global = {main_global}")
NameError: name 'main_global' is not defined

I know it all feels a bit 'hacky', but it's become a challenge so please don't flame me on style etc. If there's another way to achieve this aim, I'm all ears.

Thoughts, learned friends?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

鹿港小镇 2025-02-16 12:06:05

您可以通过出厂功能和继承来解决问题。假设您的每个插件都是这样的,则在一个单独的导入文件中定义:

class MyPlugin:
    foo = 'bar'

    def extra_method(self):
        print(self.foo)

您可以使用这样的工厂:

def MyClassFactory(plugin_dir):

    def search_and_import_plugins(plugin_dir):
        # Look for all possible plugins and import them
        return plugin_list  # a list of plugin classes, like [MyPlugin]

    plugin_list = search_and_import_plugins(plugin_dir):

    class MyClass(*plugin_list):
         pass

    return MyClass()

z = MyClassFactory('/home/me/plugins')

You can approach the problem with a factory function and inheritance. Assuming each of your plugins is something like this, defined in a separate importable file:

class MyPlugin:
    foo = 'bar'

    def extra_method(self):
        print(self.foo)

You can use a factory like this:

def MyClassFactory(plugin_dir):

    def search_and_import_plugins(plugin_dir):
        # Look for all possible plugins and import them
        return plugin_list  # a list of plugin classes, like [MyPlugin]

    plugin_list = search_and_import_plugins(plugin_dir):

    class MyClass(*plugin_list):
         pass

    return MyClass()

z = MyClassFactory('/home/me/plugins')
憧憬巴黎街头的黎明 2025-02-16 12:06:05

您可以通过@martijn pieters'如何将变量注入装饰范围?多个值注入类方法。

from functools import wraps
import importlib
import os
from pathlib import Path
import types


main_global = "Hi, I'm in main"

class MyClass:
    def __init__(self, plugin_dir=None):
        if plugin_dir:
            self.load_plugins(plugin_dir, ext="plugin")

    def load_plugins(self, plugin_dir, ext):
        """ Load plugins

            Plugins are files in 'plugin_dir' that have the given extension.
            The functions defined within are imported as methods of this class.
        """
        cls = self.__class__

        # First check that we're not importing the same extension twice into
        # the same class.
        try:
            plugins = getattr(cls, "_plugins")
        except AttributeError:
            plugins = set()
            setattr(cls, "_plugins", plugins)

        if ext in plugins:
            return
        plugins.add(ext)

        for file in Path(plugin_dir).glob(f'*.{ext}'):
            loader = importlib.machinery.SourceFileLoader("bar", str(file))
            module = types.ModuleType(loader.name)
            loader.exec_module(module)
            namespace = globals()

            for name in dir(module):
                if name.startswith("__"):
                    continue

                obj = getattr(module, name)
                if callable(obj):
                    obj = inject(obj.__get__(self, cls), namespace)

                setattr(cls, name, obj)


def inject(method, namespace):

    @wraps(method)
    def wrapped(*args, **kwargs):
        method_globals = method.__globals__

        # Save copies of any of method's global values replaced by the namespace.
        replaced = {key: method_globals[key] for key in namespace if key in method_globals}
        method_globals.update(namespace)

        try:
            method(*args[1:], **kwargs)
        finally:
            method_globals.update(replaced)  # Restore any replaced globals.

    return wrapped


z = MyClass(plugin_dir="plugins")
z.foo("Hello")

示例输出:

I am foo, called with <__main__.MyClass object at 0x0056F670> and Hello
foo_global = I am global within foo
main_global = Hi, I'm in main

You can do what you want with a variation of the technique shown in @Martijn Pieters' answer to the the question: How to inject variable into scope with a decorator? tweaked to inject multiple values into a class method.

from functools import wraps
import importlib
import os
from pathlib import Path
import types


main_global = "Hi, I'm in main"

class MyClass:
    def __init__(self, plugin_dir=None):
        if plugin_dir:
            self.load_plugins(plugin_dir, ext="plugin")

    def load_plugins(self, plugin_dir, ext):
        """ Load plugins

            Plugins are files in 'plugin_dir' that have the given extension.
            The functions defined within are imported as methods of this class.
        """
        cls = self.__class__

        # First check that we're not importing the same extension twice into
        # the same class.
        try:
            plugins = getattr(cls, "_plugins")
        except AttributeError:
            plugins = set()
            setattr(cls, "_plugins", plugins)

        if ext in plugins:
            return
        plugins.add(ext)

        for file in Path(plugin_dir).glob(f'*.{ext}'):
            loader = importlib.machinery.SourceFileLoader("bar", str(file))
            module = types.ModuleType(loader.name)
            loader.exec_module(module)
            namespace = globals()

            for name in dir(module):
                if name.startswith("__"):
                    continue

                obj = getattr(module, name)
                if callable(obj):
                    obj = inject(obj.__get__(self, cls), namespace)

                setattr(cls, name, obj)


def inject(method, namespace):

    @wraps(method)
    def wrapped(*args, **kwargs):
        method_globals = method.__globals__

        # Save copies of any of method's global values replaced by the namespace.
        replaced = {key: method_globals[key] for key in namespace if key in method_globals}
        method_globals.update(namespace)

        try:
            method(*args[1:], **kwargs)
        finally:
            method_globals.update(replaced)  # Restore any replaced globals.

    return wrapped


z = MyClass(plugin_dir="plugins")
z.foo("Hello")

Example output:

I am foo, called with <__main__.MyClass object at 0x0056F670> and Hello
foo_global = I am global within foo
main_global = Hi, I'm in main
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文