9

语境

我正在尝试在我的代码中添加一些“插件”(我不确定这是不是正确的定义)。通过“插件”,我的意思是一个模块,它定义了一个模型(这是一个科学代码),它的存在足以在代码的其他任何地方使用它。

当然,这些插件必须遵循一个模板,该模板使用我的代码中定义的一些模块/函数/类。这是我的代码相关部分的一小段代码:

# [In the code]
class AllModels():
    def __init__(self):
        """
        Init.
        """
        self.count = 0

    def register(self, name, model):
        """
        Adds a model to the code
        """
        setattr(self, name, model)
        self.count += 1
        return

class Model():
    def __init__(self, **kwargs):
        """
        Some constants that defines a model
        """
        self.a = kwargs.get("a", None)
        self.b = kwargs.get("b", None)
        # and so on...

    def function1(self, *args, **kwargs):
        """
        A function that all models will have, but which needs:
            - to have a default behavior (when the instance is created)
            - to be redefinable by the "plugin" (ie. the model)
        """
        # default code for the default behavior
        return

instance = AllModels()

这是“插件”的相关部分:

# [in the plugin file]
from code import Model, instance
newmodel = Model(a="a name", b="some other stuff")

def function1(*args, **kwargs):
    """
    Work to do by this model
    """
    # some specific model-dependent work
    return

instance.register(newmodel)

附加信息和要求

  • function1对于任何模型插件都有完全相同的签名,但通常为每个插件做不同的工作。

  • 我想要一个默认行为,function1这样如果插件没有定义它,我仍然可以做一些事情(尝试不同的可能性,和/或引发警告/错误)。

  • 在插件中,function1可能会用到一些仅在本插件中定义的其他功能。我之所以这么说是因为代码与多处理模块一起运行,并且我需要能够调用子进程的instance实例。在父进程以及模型插件中定义,但将在不同的子进程中使用(虽然没有对其进行修改)。AllModelsfunction1instance

  • 真棒function1,当插件“重新定义”时,能够访问Model实例的属性(即。self)。

问题

我已经阅读了许多不同的 python 文档来源和一些 SO 问题。我只看到这个问题的两个/三个可能的解决方案:

1)不在类中声明function1方法Model,而是在插件创建它的新实例时将其设置为属性。

# [in the plugin file]
def function1(*args, **kwargs):
    # ....
    return
newmodel.function1 = function1

然后在需要时调用它。在那种情况下,function1对象中的属性可能Model会被初始化None。一个警告是没有“默认行为” function1(它必须在代码中处理,例如 testing if instance.function1 is None: ...),更大的一个是我无法self以这种方式访问​​......

2)以某种方式使用python装饰器。我从来没有使用过这个,而且我读过的文档并不是那么简单(我的意思是不直接,因为它的使用可能性很大)。但这似乎是一个很好的解决方案。但是我担心它对性能的影响(我已经读过它可能会减慢装饰函数/方法的执行速度)。如果这个解决方案是最好的选择,那么我想知道如何使用它(可能是一个快速的片段),以及是否可以使用类的属性Model

# [in the plugin file]
@mydecorator
def function1(self, *args, **kwargs):
    """
    I'm not sure I can use *self*, but it would be great since some attributes of self are used for some other function similar to *function1*...
    """
    # some stuff using *self*, eg.:
    x = self.var **2 + 3.4
    # where self.var has been defined before, eg.: newmodel.var = 100.

3)使用模块types及其MethodType......我不确定这是否与我的情况相关......但我可能是错的。

在这个长长的问题之后你可能会看到,我对这些 python 特性不是很熟悉,而且我现在对装饰器的理解真的很差。在继续阅读一些文档的同时,我认为可能值得在这里提出这个问题,因为我不确定为了解决我的问题而采取的方向。

解决方案

Senderle 答案的美妙之处在于它非常简单明了......错过了它是一种耻辱。很抱歉用那个问题污染了 SO。

4

3 回答 3

7

好吧,除非我弄错了,否则你想要子类化 Model. 这有点像创建一个实例Model并将其function1属性替换为插件模块中定义的函数(即您的选项 1);但它更清洁,并为您处理所有细节:

# [in the plugin file]
from code import Model, instance

class MyModel(Model):
    def function1(*args, **kwargs):
        """
        Work to do by this model
        """
        # some specific model-dependent work
        return

newmodel = MyModel(a="a name", b="some other stuff")
instance.register(newmodel)

这样,所有其他方法(“附加”到Model实例的函数)都继承自Model; 它们的行为方式相同,但function1会被覆盖,并遵循您的自function1定义定义。

于 2012-03-23T04:31:07.420 回答
2

function1()能在Model课堂上写raise一个虚拟函数NotImplementedError吗?那样的话,如果有人试图在Model没有实现的情况下继承function1(),他们会在尝试运行代码时得到一个异常。如果您正在为他们运行代码,则可以捕获该错误并向用户返回有用的错误消息。

例如:

class Model:
    #Your code

    def function1():
        raise NotImplementedError("You need to implement function1 
            when you inherit from Model")

然后,您可以在运行代码时执行以下操作:

try:
    modelObj.function1()
except NotImplementedError as e:
    #Perform error handling here

编辑:NotImplementedError的官方 Python 文档指出:“在用户定义的基类中,抽象方法在需要派生类覆盖方法时应该引发此异常。” 这似乎符合这里的要求。

于 2012-03-23T04:30:33.387 回答
2

您尝试做的事情可以以非常简单的方式完成 - 只需使用面向对象的技术并利用 Python 中的函数也是普通对象 -

要做的一件简单的事情就是让您的“模型”类接受“function1”作为参数,并将其存储为对象成员。

像这样的一些代码,对你的代码的改动很小——尽管更有趣的事情当然是可能的:

# [In the code]

class AllModels():
    def __init__(self):
        """
        Init.
        """
        self.count = 0

    def register(self, name, **kwargs):
        """
        Adds a model to the code
        """
        model = Model(**kwargs)
        setattr(self, name, model)
        self.count += 1
        return

class Model():
    def __init__(self, **kwargs):
        """
        Some constants that defines a model
        """
        self.a = kwargs.get("a", None)
        self.b = kwargs.get("b", None)
        if "function1" in kwargs:
            self.real_function1 = kwargs["function1"]
            self.function1.__doc__ = kwargs["function1"].__doc__
        # and so on...

    def function1(self, *args, **kwargs):
        """
        A function that all models will have, but which needs:
            - to have a default behavior (when the instance is created)
            - to be redefinable by the "plugin" (ie. the model)
        """
        if self.real_function1:
            return self.real_function1(self, *args, **kwargs)
        # default code for the default behavior
        return

instance = AllModels()

# [in the plugin file]
from code import Model, instance
newmodel = Model(a="a name", b="some other stuff")

def function1(self, *args, **kwargs):
    """
    Work to do by this model
    """
    # some specific model-dependent work
    return


instance.register("name", function1 = function1, a="a name", b="some other stuff")
于 2012-03-23T04:37:20.283 回答