web-dev-qa-db-fra.com

Une classe dérivée a-t-elle automatiquement tous les attributs de la classe de base?

Il ne semble pas y avoir de bonne documentation en ligne à ce sujet: si je crée une classe dérivée, aura-t-elle automatiquement tous les attributs de la classe de base? Mais à quoi sert la BaseClass.__init(), devez-vous également la faire avec d'autres méthodes de classe de base? BaseClass.__init__() a-t-il besoin d'arguments? Si vous avez des arguments pour votre classe de base __init__(), sont-ils également utilisés par la classe dérivée, devez-vous définir explicitement les arguments sur la __init__() de la classe dérivée, ou les définir sur BaseClass.__init__() à la place?

25
liamel1i

Si vous implémentez __init__ Dans une classe dérivée de BaseClass, il écrasera la méthode héritée __init__ Et ainsi BaseClass.__init__ Ne sera jamais appelé. Si vous devez appeler la méthode __init__ Pour BaseClass (comme c'est normalement le cas), alors c'est à vous de le faire, et c'est fait explicitement en appelant BaseClass.__init__, Normalement à partir du nouveau implémenté la méthode __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()

Cela provoquera l'erreur suivante:

AttributeError: 'Bar' object has no attribute 'a'

Ainsi, la méthode do_something A été héritée comme prévu, mais cette méthode nécessite que l'attribut a ait été défini, ce qu'il n'est jamais parce que __init__ A également été remplacé. Nous contournons cela en appelant explicitement Foo.__init__ À partir de Bar.__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()

qui imprime 10 comme prévu. Foo.__init__ Dans ce cas attend un seul argument qui est une instance de Foo (qui par convention est appelée self).

Normalement, lorsque vous appelez une méthode sur une instance d'une classe, l'instance de classe est transmise automatiquement comme premier argument. Les méthodes sur une instance d'une classe sont appelées méthodes liées . bar.do_something Est un exemple de méthode liée (et vous remarquerez qu'elle est appelée sans aucun argument). Foo.__init__ Est une méthode non liée car elle n'est pas attachée à une instance particulière de Foo, donc le premier argument, une instance de Foo, doit être passée explicitement.

Dans notre cas, nous passons self à Foo.__init__, Qui est l'instance de Bar qui a été passée à la méthode __init__ Dans Bar . Puisque Bar hérite de Foo, les instances de Bar sont également des instances de Foo, donc en passant self à Foo.__init__ est autorisée.

Il est probable que la classe dont vous héritez requiert ou accepte plus d'arguments qu'une simple instance de la classe. Ils sont traités comme vous le feriez avec n'importe quelle méthode que vous appelez à partir de __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()

qui afficherait 20.

Si vous essayez d'implémenter une interface qui expose entièrement tous les arguments d'initialisation de la classe de base via votre classe héritée, vous devrez le faire explicitement. Cela se fait généralement avec les arguments * args et ** kwargs (les noms sont par convention), qui sont des espaces réservés pour tous les autres arguments qui ne sont pas explicitement nommés. L'exemple suivant utilise tout ce dont j'ai discuté:

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()

Dans ce cas, l'argument c est défini sur 40, car il s'agit du premier argument de Bar.__init__. Le deuxième argument est ensuite incorporé dans les variables args et kwargs (le * et ** est une syntaxe spécifique qui dit développer la liste/Tuple ou le dictionnaire en arguments séparés lors du passage à une fonction/méthode ), et est transmis à Foo.__init__.

Cet exemple montre également que toute méthode écrasée doit être appelée explicitement si c'est ce qui est requis (comme do_something Est dans ce Cas).

Un dernier point, vous verrez souvent super(ChildClass, self).method() (où ChildClass est une classe enfant arbitraire) utilisée au lieu d'un appel à la méthode BaseClass explicitement. La discussion de super est une toute autre question, mais il suffit de dire que, dans ces cas, il est généralement utilisé pour faire exactement ce qui est fait en appelant BaseClass.method(self). En bref, super délègue l'appel de méthode à la classe suivante dans l'ordre de résolution de la méthode - le MRO (qui en héritage unique est la classe parente). Voir documentation sur super pour plus d'informations.

60
Henry Gomersall

Si je crée une classe dérivée, aura-t-elle automatiquement tous les attributs de la classe de base?

Attributs de classe, oui. Attributs d'instance, non (simplement parce qu'ils n'existent pas lors de la création de la classe), sauf s'il n'y a pas de __init__ dans la classe dérivée, auquel cas la base sera appelée à la place et définira les attributs d'instance.

BaseClass .init () a-t-il besoin d'arguments?

Dépend de la classe et de son __init__ Signature. Si vous appelez explicitement Base.__init__ dans la classe dérivée, vous devrez au moins passer self comme premier argument. Si tu as

class Base(object):
    def __init__(self):
        # something

alors il est assez évident qu'aucun autre argument n'est accepté par le __init__. Si vous aviez

class Base(object):
    def __init__(self, argument):
        # something

alors vous devez passer argument lorsque vous appelez base __init__. Pas de science fusée ici.

Si vous avez des arguments pour votre classe de base init (), sont-ils également utilisés par la classe dérivée, devez-vous définir explicitement les arguments sur la classe dérivée init () ou définissez-les sur BaseClass .init () à la place?

Encore une fois, si la classe dérivée n'a pas __init__, la base 1 sera utilisée à la place.

class Base(object):
    def __init__(self, foo):
        print 'Base'

class Derived(Base):
    pass

Derived()   # TypeError
Derived(42) # prints Base

Dans les autres cas, vous devez en prendre soin d'une manière ou d'une autre. Que vous utilisiez *args, **kwargs et simplement passer des arguments non modifiés à la classe de base, ou copier la signature de la classe de base, ou fournir des arguments ailleurs, dépend de ce que vous essayez d'accomplir.

11
Cat Plus Plus