【问题标题】:Dynamically select which subclass to inherit methods from?动态选择从哪个子类继承方法?
【发布时间】:2016-02-04 22:16:20
【问题描述】:

假设我有不同的类提供对不同子系统的访问但具有通用接口。它们都提供相同的方法集,但每个类以不同的方式实现它们(想想使用 foo.write() 写入文件或通过套接字发送数据等)

由于接口相同,我想创建一个能够选择正确类但仅基于构造函数/初始化器参数的类。

在代码上,它看起来像

class Foo(object):
    def who_am_i(self):
        print "Foo"

class Bar(object):
    def who_am_i(self):
        print "Bar"

# Class that decides which one to use and adds some methods that are common to both
class SomeClass(Foo, Bar):
    def __init__(self, use_foo):
        # How inherit methods from Foo -OR- Bar?

在给定__init__ 和/或__new__ 参数的情况下,SomeClass 如何从Foo 继承方法 Bar

目标应该是这样的

>>> some_object = SomeClass(use_foo=True)
>>> some_object.who_am_i()
Foo
>>> another_object = SomeClass(use_foo=False)
>>> another_object.who_am_i()
Bar

是否有一些干净的“pythonic”方式来实现这一点?我不想使用函数来动态定义SomeClass,但我没有找到其他方法来做到这一点。

谢谢!

【问题讨论】:

  • 这听起来像是组合而不是继承的情况,或者可能只是一个吐出FooBar 的工厂函数。
  • SomeClass 实例是否总是使用来自Foo 的方法,或者总是使用来自Bar 的方法,正如在初始化时配置的那样,或者它可能会因调用而异?用这样一个模糊的例子很难解决这个问题,但多重继承似乎不太可能是正确的方法。
  • @jonrsharpe 创建对象后,它将始终使用FooBar
  • @AndréSouto 那么你为什么不根据实例化哪个类而不是调用哪个超类方法来解决这个问题?

标签: python inheritance subclass dynamic-programming subclassing


【解决方案1】:

如 cmets 中所述,这可以通过工厂函数(伪装成类的函数)来完成:

def SomeClass(use_foo):
    if use_foo:
        return Foo()
    else:
        return Bar()

【讨论】:

  • 仅供参考,类工厂通常不会是驼峰式的;参见例如namedtupledefaultdict.
  • @jonrsharpe 我不知道官方的约定是什么,所以我想既然你是假装是一个类,你也可以使用类的约定。
  • 使用工厂函数会使isinstanceissubclass 检查不起作用。
  • 别装班了。您不能将它与 isinstance 一起使用,不能将其子类化,等等。“可以调用以获取对象的事物”只是类的一小部分,但它正是它的本质成为一个函数。只是一个函数。
  • @AndréSouto:涉及怪异继承结构的方法存在更多此类问题。
【解决方案2】:

据我所知,您的继承权完全倒退;而不是您提议的多重继承:

Foo              Bar
- foo code       - bar code

     \          /

      SomeClass(Foo, Bar)
      - common code

您可以使用更简单的单继承模型:

      SomeClass
      - common code

     /          \

Foo(SomeClass)   Bar(SomeClass)
- foo code       - bar code

这使您的问题之一是选择要实例化哪个子类(只需做出一次决定)而不是调用哪个超类方法(可能需要在每个方法调用上进行)。这可以通过以下方式解决:

thing = Foo() if use_foo else Bar()

【讨论】:

  • 什么是额外的Base 类,FooBar 都是其子类,从而产生了一种需要选择性继承的菱形继承模式。
  • @ppperry 如果事实证明是这样,我可能需要修改答案,但到目前为止还没有任何迹象,我很乐意首先提出更简单的建议...
【解决方案3】:

这里可以使用类工厂。请注意使用字典以确保 same 子类实例用于每个基类。

def makeclass(baseclass, classes={}):
    if baseclass not in classes:
        class Class(baseclass):
            pass   # define your methods here
        classes[baseclass] = Class
    return classes[baseclass]

obj1 = makeclass(Foo)(...)
obj2 = makeclass(Bar)(...)

isinstance(obj1, makeclass(Foo)) # True
isinstance(obj1, Foo)            # True
issubclass(makeclass(Foo), Foo)  # True
issubclass(type(obj1), Foo)      # True

你也可以用__missing__ 方法创建一个dict 子类来做同样的事情;它更明确地表明您有一个存储类的容器,但可以按需创建它们:

class ClassDict(dict):
     def __missing__(self, baseclass):
         class Class(baseclass):
             pass   # define your methods here
         self[baseclass] = Class
         return Class

subclasses = ClassDict()
obj1 = subclasses[Foo]
obj2 = subclasses[Bar]

【讨论】:

  • @ppperry 实例或子类是什么?再说一遍,这有什么关系?
  • 在这个例子中没有解析 use_foo 参数。另外,没有办法使用isinstance 说“这是 Foo 类型的 SomeClass 还是 Bar 类型的 SomeClass?”
  • 添加一个use_foo 参数是微不足道的。此外,isinstance 示例在答案中:isinstance(obj1, makeclass(Foo))
【解决方案4】:

从对答案的不一致来看,也许问题就是问题。 jonrsharpe's comment 对这个问题给出了一个有趣的见解:这应该通过继承来解决。

考虑SomeClass定义如下:

# Class that uses Foo or Bar depending on the environment
# Notice it doesn't subclasses either Foo or Bar
class SomeClass(object):
    def __init__(self, use_foo):
        if use_foo:
            self.handler = Foo() 
        else:
            self.handler = Bar() 

    # Makes more sense asking 'Who implements?' instead of 'Who am I?'
    def who_implements(self):
        return self.handler

    # Explicitly expose methods from the handler
    def some_handler_method(self, *args, **kwargs):
        return self.handler.some_handler_method(*args, **kwargs)

    def another_handler_method(self, *args, **kwargs):
        return self.handler.another_handler_method(*args, **kwargs)

如果我们需要获取有关处理程序实现的详细信息,只需获取handler 属性。 SomeClass 子类的其他类甚至不会直接看到处理程序,这实际上是有道理的。

【讨论】:

    【解决方案5】:

    为此可以使用__new__ 方法:

    _foos = {}
    _bars = {}
    class SomeClass(object):
         def __new__(cls,use_foo,*args,**kwargs):
             if use_foo:
                if cls not in _foos:
                    class specialized(cls,Foo):pass
                    _foos[cls] = specialized
               else:
                   specialized = _foos[cls]
             else:
                if cls not in _bars:
                    class specialized(cls,Bar):pass
                    _bars[cls] = specialized
               else:
                   specialized = _bars[cls]
             specialized.__name__ = cls.__name__
             return object.__new__(specialized,*args,**kwargs)
         #common methods to both go here
         pass
    

    与工厂函数相比,它的优点是isinstance(SomeClass(True),SomeClass) 可以工作,并且SomeClass 可以被子类化。

    【讨论】:

    • 我不清楚您的优势如何足以证明这增加了复杂性。鉴于 Python 代码主要是鸭子类型的,因此支持适当的接口通常很重要。
    • @jonrsharpe 这是一个实际的类,而不是一个工厂函数。因此,isinstanceissubclass 检查工作等。
    • ...再次,那又怎样?即使这很重要,类工厂返回的类(这实际上并不是其他答案所建议的)也可以使用例如定义超类。 type(name, bases, dict) 并且与静态创建的没有区别。
    • 在类工厂方法中,没有简单的方法可以说“这个对象是否属于在类工厂中创建的类”。 isinstanceissubclass 允许轻松进行这种检查。
    • 工厂函数在正确实现时与issubclassisinstance 一起工作正常。说“没有简单的方法可以说‘这个对象是否属于在类工厂中创建的类’”是完全错误的。
    猜你喜欢
    • 1970-01-01
    • 2013-07-10
    • 2015-11-16
    • 1970-01-01
    • 1970-01-01
    • 2011-09-14
    • 1970-01-01
    • 2021-07-08
    • 2023-03-23
    相关资源
    最近更新 更多