在对象实例化时选择多种方法之一

3
我有一个Python类,让我们称其为MyClass,其中包含几个绑定方法。我需要其中一种方法的行为,称为.dynamic_method(),根据在对象创建期间设置的参数值而大不相同。将有几个dynamic_method的变化,它们之间共享的代码非常少,因此将它们定义为单独的函数/方法而不是作为具有许多选项和条件的单个大型方法是有意义的。我不需要在创建对象后能够更改此方法的行为,所有内容都可以在__init__()中设置。
参考资料,MyClass管理数据集,并且.dynamic_method处理这些数据的某些IO方面,这取决于数据集和上下文。
从技术上讲,这并不难实现,但我想到的所有方法都感觉不完全正确:
  1. 将方法变化单独定义为函数,并在__init__()期间使用此线程中描述的方法之一将其附加为绑定方法:Adding a Method to an Existing Object Instance。对我来说,这似乎是最自然的选择,但是该线程中的许多答案强烈反对使用此模式。
  2. 在类定义中定义所有可能的方法,如.dynamic_method_a.dynamic_method_b等,并设置选项/别名,以便MyClass.dynamic_method()调用正确的方法。这将导致非常长的类定义,以及每个对象的大量未使用的方法。
  3. 将方法存储为单独的函数,并使.dynamic_method()仅使用self作为第一个参数调用正确的函数。这感觉像是故意滥用方法的概念,并会破坏linting。
  4. 使用工厂模式或对象生成器函数。我宁愿避免这种情况,因为它将向代码库添加最复杂性。
哪种方法被认为是最Pythonic的方式来实现这种行为?以上其中一种方法还是我错过了明显的替代方案?

当你说“参数的值”时,你是指实际的值还是作为参数传递的对象类型? - buran
@buran 值。例如,my_object = MyClass(method_variation='b') 应该返回一个对象,其中 my_class.dynamic_method() 引用方法的变体 b(显然,所有这些名称都是占位符)。 - jelcOfDice
3个回答

3

我认为你过于复杂化了这个问题。

如果你没有任何重要原因改变方法属性(第一选项),我建议只需在 dynamic_method() 中编写一个良好的 if-else 来调用其他函数(第三选项),或者如果我真的想要更加高级,我会使用 dict 制作一个类似 switch-case 的临时方案,如下所示:

def dynamic_method(self):
    return {
        <value to run foo1>: foo1,
        <value to run foo2>: foo2,
        <value to run foo3>: foo3
    }[self.method_variation](self)

def foo1(self):
    pass

def foo2(self):
    pass

def foo3(self):
    pass

1
我建议使用工厂模式,虽然会增加复杂性,但是让我指出几个优点。
  • 未来变更的灵活性
  • 在阅读代码时,仅通过类就能了解对象的行为
  • 如果使用带有类型提示的IDE(如pycharm或其他),编码将更加容易,因为IDE可以在运行前理解对象将使用哪个方法

代码示例:

class DataStruct:

    def __init__(self, input_: str):
        self.Field = input_

class MyClass:

    def __init__(self, data: DataStruct):
        self.Data: DataStruct = data

    def dynamic_method(self):
        # Abstract Method
        pass

    def __str__(self):
        return self.Data.Field

class MyClassFunctionalityOne(MyClass):

    def __init__(self, data: DataStruct):
        super().__init__(data)

    def dynamic_method(self):
        self.Data.Field = self.Data.Field.upper()


class MyClassFunctionalityTwo(MyClass):

    def __init__(self, data: DataStruct):
        super().__init__(data)

    def dynamic_method(self):
        self.Data.Field = "and now for something completely different"


class MyClassFactory:

    def __init__(self):
        pass

    @classmethod
    def manufacture(cls, input_: DataStruct) -> MyClass:
        #replace this if..elif chain to contain the tests on the data you need for determine the right method
        if input_.Field.count('one') > 0:
            obj: MyClass = MyClassFunctionalityOne(input_)
        elif input_.Field.count('two')> 0:
            obj: MyClass = MyClassFunctionalityTwo(input_)
        else:
            obj = None

        return obj


# script starts here
received_data = DataStruct('This kind of data should result in functionality one')
object1 = MyClassFactory.manufacture(received_data)
received_data = DataStruct('This kind of data should result in functionality two')
object2 = MyClassFactory.manufacture(received_data)
print (type(object1))
print (type(object2))
print ('*'*5, 'objects before dynamic_method', '*'*5)
print (object1)
print (object2)
object1.dynamic_method()
object2.dynamic_method()
print ('*'*5, 'objects after dynamic_method', '*'*5)
print (object1)
print (object2)

输出:

<class '__main__.MyClassFunctionalityOne'>
<class '__main__.MyClassFunctionalityTwo'>
 ***** objects before dynamic_method *****
This kind of data should result in functionality one
This kind of data should result in functionality two
***** objects after dynamic_method *****
THIS KIND OF DATA SHOULD RESULT IN FUNCTIONALITY ONE
and now for something completely different

1
我更倾向于选择选项3,因为我觉得它更清晰,但这取决于您需要多少实现变体。选项4(使用工厂)也不错,但它会生成更多的代码。另一方面,选项4远比可扩展性和可维护性更好。
因此,既然您想要更改__init__()dynamic_method()的行为,您可以像这样做:
class MyClass(object):
     def __init__(self, param=None):
         self.dynamic_method = {
             "a": self.dynamic_a,
             "b": self.dynamic_b,
             "c": self.dynamic_c}[param]

     def dynamic_a(self):
         print("a functionality")

     def dynamic_b(self):
         print("b functionality")

     def dynamic_c(self):
         print("c functionality")

>> m = MyClass("b")
>> m.dynamic_method()
b functionality

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接