派生类是否自动具有基类的所有属性?
网上似乎没有很好的文档来解释这个问题:如果我创建了一个派生类,它会自动拥有基类的所有属性吗?那BaseClass.__init()
是干什么用的呢?你还需要对其他基类的方法做同样的事情吗?BaseClass.__init__()
需要参数吗?如果你的基类的__init__()
有参数,那么这些参数在派生类中也会用到吗?你需要在派生类的__init__()
中显式地设置这些参数,还是应该在BaseClass.__init__()
中设置呢?
2 个回答
如果我创建一个派生类,它会自动拥有基类的所有属性吗?
类属性是会的,但实例属性就不一定了(因为实例属性在类创建时并不存在)。不过,如果派生类没有定义__init__
方法,那么就会调用基类的__init__
,这样实例属性就会被设置。
BaseClass.init()需要参数吗?
这要看类和它的__init__
方法的定义。如果你在派生类中明确调用Base.__init__
,那么至少需要把self
作为第一个参数传进去。如果你有
class Base(object):
def __init__(self):
# something
那么很明显__init__
不接受其他参数。如果你有
class Base(object):
def __init__(self, argument):
# something
那么在调用基类的__init__
时,你就必须传入argument
。这并不复杂。
如果你的基类
init
()有参数,派生类也会用到这些参数吗?你需要在派生类的init
()中显式设置这些参数,还是直接设置给BaseClass.init()?
同样,如果派生类没有__init__
,就会使用基类的那个。
class Base(object):
def __init__(self, foo):
print 'Base'
class Derived(Base):
pass
Derived() # TypeError
Derived(42) # prints Base
在其他情况下,你需要以某种方式处理这个问题。你可以选择使用*args, **kwargs
,直接将参数传递给基类,或者复制基类的参数定义,或者从其他地方提供参数,这取决于你想要实现的目标。
如果你在一个从 BaseClass 继承的类中实现了 __init__
方法,那么这个方法会覆盖掉从 BaseClass 继承来的 __init__
方法,这样 BaseClass.__init__
就不会被调用了。如果你需要调用 BaseClass 的 __init__
方法(这通常是需要的),那么你得自己手动去调用它,通常是在你新实现的 __init__
方法里调用 BaseClass.__init__
。
class Foo(object):
def __init__(self):
self.a = 10
def do_something(self):
print self.a
class Bar(Foo):
def __init__(self):
self.b = 20
bar = Bar()
bar.do_something()
这样会导致以下错误:
AttributeError: 'Bar' object has no attribute 'a'
所以,do_something
方法是如预期那样被继承了,但这个方法需要属性 a
被设置,而因为 __init__
方法被覆盖了,所以 a
从来没有被设置。我们可以通过在 Bar.__init__
中显式调用 Foo.__init__
来解决这个问题。
class Foo(object):
def __init__(self):
self.a = 10
def do_something(self):
print self.a
class Bar(Foo):
def __init__(self):
Foo.__init__(self)
self.b = 20
bar = Bar()
bar.do_something()
这样就会打印出 10
,正如预期的那样。这里的 Foo.__init__
期望接收一个参数,这个参数是 Foo
的一个实例(通常我们称它为 self
)。
通常,当你在一个类的实例上调用方法时,这个类的实例会自动作为第一个参数传入。类实例的方法被称为 绑定方法。bar.do_something
就是一个绑定方法的例子(你会注意到它是没有任何参数被传入的)。而 Foo.__init__
是一个 未绑定方法,因为它没有和特定的 Foo
实例绑定,所以第一个参数,即 Foo
的实例,需要显式传入。
在我们的例子中,我们将 self
传给 Foo.__init__
,这个 self
是传入 Bar
的 __init__
方法的 Bar
实例。由于 Bar
继承自 Foo
,所以 Bar
的实例也是 Foo
的实例,因此将 self
传给 Foo.__init__
是允许的。
你继承的类可能需要或接受的不仅仅是类的实例作为参数。这些参数的处理方式和你在 __init__
中调用的任何方法是一样的:
class Foo(object):
def __init__(self, a=10):
self.a = a
def do_something(self):
print self.a
class Bar(Foo):
def __init__(self):
Foo.__init__(self, 20)
bar = Bar()
bar.do_something()
这将打印出 20
。
如果你想实现一个接口,完全暴露基类的所有初始化参数,你需要显式地做到这一点。通常是通过 *args 和 **kwargs 这两个参数来实现(这些名字是约定俗成的),它们是所有未显式命名的参数的占位符。以下示例使用了我讨论过的所有内容:
class Foo(object):
def __init__(self, a, b=10):
self.num = a * b
def do_something(self):
print self.num
class Bar(Foo):
def __init__(self, c=20, *args, **kwargs):
Foo.__init__(self, *args, **kwargs)
self.c = c
def do_something(self):
Foo.do_something(self)
print self.c
bar = Bar(40, a=15)
bar.do_something()
在这个例子中,参数 c
被设置为 40,因为它是传给 Bar.__init__
的第一个参数。第二个参数则被包含在 args
和 kwargs
变量中(* 和 ** 是特定的语法,表示在传递给函数/方法时将列表/元组或字典展开为单独的参数),并传递给 Foo.__init__
。
这个例子还强调了任何被覆盖的方法如果需要被调用,都必须显式调用(就像这里的 do_something
)。
最后一点,你会经常看到 super(ChildClass, self).method()
(其中 ChildClass
是某个任意的子类)被用来代替显式调用 BaseClass
的方法。关于 super
的讨论是另一个话题,但可以简单说,在这些情况下,它通常用于实现和调用 BaseClass.method(self)
一样的功能。简而言之,super
将方法调用委托给方法解析顺序中的下一个类 - MRO(在单继承中就是父类)。有关更多信息,请查看 super 的文档。