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

时间: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()功能)。

相关问题