【问题标题】:Choosing one of several methods on object instantiation选择对象实例化的几种方法之一
【发布时间】:2019-02-12 13:28:50
【问题描述】:

我有一个 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 值。例如my_object = MyClass(method_variation='b') 应该返回一个对象,其中my_class.dynamic_method() 指的是方法的变体b(显然所有这些名称都是占位符)。

标签: python python-3.x methods


【解决方案1】:

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

如果您没有任何重要的理由来更改方法属性(第一个选项),我会在 dynamic_method() 中编写一个不错的 if-else 来调用其他函数(第三个选项),或者如果我真的想要花哨,我会像这样用dict 制作一个临时开关盒:

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

【讨论】:

    【解决方案2】:

    我会建议工厂,确实它增加了复杂性,但让我指出几个优点。

    • 对未来变化的灵活性
    • 阅读代码时,仅通过对象的类就更容易理解对象的预期行为
    • 如果您使用带有类型提示(pycharm 或其他)的 IDE,您的编码会更容易,因为 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
    

    【讨论】:

      【解决方案3】:

      我宁愿选择选项 3,因为我觉得它更简洁,但这取决于您需要多少实现变体。选项 4(使用工厂)也不错,但会生成更多代码。另一方面,选项 4 的可扩展性和可维护性要高得多。

      所以,既然你想改变dynamic_method()__init__() 中的行为,你可以这样做:

      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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多