无法使用基类来创建其子类的实例

时间:2015-03-13 04:38:30

标签: python class inheritance python-3.x subclass

以下是我所拥有的代码的简化版本:

class Base:
    def __new__(klass, *args):
        N = len(args)
        try:
            return [Sub0, Sub1, Sub2][N](*args)
        except IndexError:
            raise RuntimeError("Wrong number of arguments.") from None

class Sub0(Base): pass
class Sub1(Base): pass
class Sub2(Base): pass

此代码不起作用。我理解它不起作用的原因是我的基类定义依赖于子类的定义,而子类又依赖于基类。

我想要完成的是创建一个API(我自己),我可以在其中执行以下操作:

obj = Base(1,2)
assert type(obj) == Sub2
obj = Base()
assert type(obj) == Sub0
assert isinstance(obj, Base)

我可能会被问到为什么,确切地说,我希望能够编写这样的代码。答案是它似乎对我正在进行的项目有用。但是,我正在考虑放弃子类,而是这样做:

obj = Base(1,2)
assert obj.type == "Sub2"
obj = Base()
assert obj.type == "Sub0"
assert isinstance(obj, Base)

作为一个相对缺乏经验的程序员,我仍然想弄清楚我应该为我的特定问题做些什么。

然而,这个问题的焦点是:如果在某些情况下以这种方式使用基类和子类是有意义的,我怎样才能按照我描述的方式进行这项工作?另一方面,如果它绝对不会 - 或者至少,很少 - 有意义尝试这样做(在Python中,我的意思是 - Python与其他语言不同),为什么这是真的?

4 个答案:

答案 0 :(得分:2)

你的设计并没有特别的意义......一般来说,基类知道它的祖先有点奇怪。信息流应该相反。您的代码的特殊问题是您致电:

Base(1, 2)

调用Base.__new__。它挑出了一个子类(在这个例子中是Sub2),然后(基本上)做了:

return Sub2(1, 2)

但是,这会调用Sub2.__new__恰好是Base.__new__,因此您会遇到递归问题。

更好的设计是忘记让Base选择子类...按照正常情况制作课程:

class Sub0:
    ...

class Sub1:
    ...

class Sub2:
    ...

然后让工厂函数执行Base现在正在做的事情。

# This might be a bad name :-)
_NARG_CLS_MAP = (Sub0, Sub1, Sub2) 

def factory(*args):
    n = len(args)
    try:
        return _NARG_CLS_MAP[n](*args)
    except IndexError as err:
        raise RuntimeError('...') from err

这可以避免任何奇怪的递归和奇怪的继承树问题。

答案 1 :(得分:1)

这是一个简短的答案,因为我在手机上。 首先,查看this

现在,虽然你可能会找到一种方法(在c ++中有一种方法不确定python)但这是一个糟糕的设计。你不应该有这样的问题。父母不能依赖其子女。你错过了继承点。这就像是说父母级车依赖于它的孩子:Bentley或Mazeratti。现在,我可以想到你可能有的两种情景:

  1. 如果两个类实际上彼此依赖(一个对象是LIKE另一个),那么两者都可以有另一个基础,它将包含两者的共享部分。

  2. 如果两者都没有类基(一个对象是另一个对象的PART),则根本不需要继承。

  3. 简而言之,它确实取决于你的问题,试着解决这个问题的原因。您应该更改对象的设计。

答案 2 :(得分:1)

猜测你的需求,几乎就像你想为一个类有多个构造函数,每个构造函数都有不同数量的参数,即多个参数调度。您可以使用multipledispatch模块重载__init__,例如:

from multipledispatch import dispatch

class Thing(object):

    @dispatch(object)
    def __init__(self, arg1):
        """Thing(object): create a Thing with single argument."""
        print "Thing.__init__(): arg1 %r" % (arg1)

    @dispatch(object, object)
    def __init__(self, arg1, arg2):
        """Thing(object): create a Thing with two arguments."""
        print "Thing.__init__(): arg1 %r, arg2 %r" % (arg1, arg2)

    @dispatch(object, object, object)
    def __init__(self, arg1, arg2, arg3):
        """Thing(object): create a Thing with three arguments."""
        print "Thing.__init__(): arg1 %r, arg2 %r, arg3 %r" % (arg1, arg2, arg3)

    def normal_method(self, arg):
        print "Thing.normal_method: arg %r" % arg

Thing.__init__(): arg1 1
>>> thing = Thing('a', 2)
Thing.__init__(): arg1 'a', arg2 2
>>> thing = Thing(1, 2, 'three')
Thing.__init__(): arg1 1, arg2 2, arg3 'three'
>>> thing = Thing()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/mhawke/virtualenvs/urllib3/lib/python2.7/site-packages/multipledispatch/dispatcher.py", line 235, in __call__
    func = self.resolve(types)
  File "/home/mhawke/virtualenvs/urllib3/lib/python2.7/site-packages/multipledispatch/dispatcher.py", line 184, in resolve
    (self.name, str_signature(types)))
NotImplementedError: Could not find signature for __init__: <>
>>> thing = Thing(1, 2, 3, 4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/mhawke/virtualenvs/urllib3/lib/python2.7/site-packages/multipledispatch/dispatcher.py", line 235, in __call__
    func = self.resolve(types)
  File "/home/mhawke/virtualenvs/urllib3/lib/python2.7/site-packages/multipledispatch/dispatcher.py", line 184, in resolve
    (self.name, str_signature(types)))
NotImplementedError: Could not find signature for __init__: <int, int, int, int>

因此,如果您实际上不需要单独的子类,只需要不同的构造方法,可能可以成为您的解决方案。

答案 3 :(得分:0)

在阅读其他答案并再多思考一下时,我想出了一个方法来完成我打算按照问题中的描述去做的事情。但是,我不一定认为这是一个好的设计;我会把它留给那些比我更有经验的人。

此方法使用abstract base classes or abc module中的功能覆盖isinstance,因此它的行为方式符合我们的要求。

from abc import ABCMeta

class RealBase: 
    '''This is the real base class; it could be an abc as well if desired.'''
    def __init__(*args):
        pass

class Sub0(RealBase): pass
class Sub1(RealBase): pass
class Sub2(RealBase): pass

class Base(metaclass = ABCMeta):
    '''Base is both an abstract base class AND a factory.'''
    def __new__(klass, *args):
        N = len(args)
        try:
            return [Sub0, Sub1, Sub2][N](*args)
        except IndexError:
            raise RuntimeError("Wrong number of arguments.") from None

#Now register all the subclasses of RealBase with the Base abc
for klass in RealBase.__subclasses__():
    Base.register(klass)

if __name__ == "__main__":
    obj = Base(1,2)
    assert type(obj) == Sub2
    obj = Base()
    assert type(obj) == Sub0
    assert isinstance(obj, Base)

虽然我不确定,但我相信我上面使用的设计方法是合理的,this kind of thing is what the abc module seems to have been created for in the first place(提供a way to overload the isinstance() and issublcass()功能)。