web-dev-qa-db-fra.com

Python, Substitution d'une méthode de classe héritée

J'ai deux classes, Field et Background. Ils ressemblent un peu à ceci:

class Field( object ):
    def __init__( self, a, b ):
        self.a = a
        self.b = b
        self.field = self.buildField()

    def buildField( self ):
        field = [0,0,0]
        return field

class Background( Field ):
    def __init__( self, a, b, c ):
        super(Background, self).__init__( a, b )
        self.field = self.buildField( c )

    def buildField( self, c ):
        field = [c]
        return field

a, b, c = 0, 1, 2
background = Background( a, b, c )

Cette erreur pointe vers buildField() de Field:

"TypeError: buildField() takes exactly 2 arguments (1 given)."

Je m'attendais à ce que Background init () soit appelé en premier. Pour passer "a, b" aux champs init (), champ pour assigner a et b puis pour assigner une liste avec trois 0 dedans au champ. Ensuite, pour que Background's init () continue, pour ensuite appeler son propre buildField () et remplacer self.field par une liste contenant c.

Il semble que je ne comprenne pas parfaitement super (), mais je n'ai pas pu trouver de solution à mon problème après avoir examiné des problèmes d'héritage similaires sur le Web et ici.

Je m'attendais à un comportement comme c ++ où une classe peut remplacer une méthode héritée. Comment puis-je y parvenir ou quelque chose de similaire.

La plupart des problèmes que j'ai rencontrés à ce sujet étaient des personnes utilisant des doubles soulignements. Mon expérience avec l'héritage avec super utilise la classe héritée init () pour simplement passer différentes variables à la super classe. Rien impliquant d'écraser quoi que ce soit.

43
andb.

Du point de vue C++, il pourrait y avoir deux idées fausses ici.

Tout d'abord, une méthode avec le même nom et une signature différente ne la surcharge pas comme en C++. Si l'un de vos objets Background essaie d'appeler buildField sans argument, la version d'origine de Field ne sera pas appelée - elle a été complètement masquée.

Le deuxième problème est que si une méthode définie dans la superclasse appelle buildField, la version de la sous-classe sera appelée. En python, les méthodes toutes sont liées dynamiquement, comme une méthode C++ virtual.

Des champs __init__ s'attendait à avoir affaire à un objet qui avait une méthode buildField ne prenant aucun argument. Vous avez utilisé la méthode avec un objet qui a une méthode buildField prenant un argument.

Le problème avec super est qu'il ne change pas le type de l'objet, vous ne devez donc pas modifier la signature des méthodes que les méthodes de la superclasse pourraient appeler.

21
Ian Clelland

Je m'attendais à ce que Background init () soit appelé. Pour passer "a, b" aux champs init (), Champ pour affecter a et b

Jusqu'ici tout va bien.

puis pour affecter une liste avec trois 0 dans le champ.

Ah. C'est là que nous obtenons l'erreur.

    self.field = self.buildField()

Même si cette ligne apparaît dans Field.__init__, self est une instance de Background. donc self.buildField trouve la méthode BackgroundbuildField, pas Field.

Puisque Background.buildField Attend 2 arguments au lieu de 1,

self.field = self.buildField()

déclenche une erreur.


Alors, comment pouvons-nous dire à Python d'appeler la méthode Field de buildField au lieu de Background?

Le but de name mangling (nommer un attribut avec des soulignements doubles) est de résoudre ce problème exact.

class Field(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b
        self.field = self.__buildField()

    def __buildField(self):
        field = [0,0,0]
        return field

class Background(Field):
    def __init__(self, a, b, c):
        super(Background, self).__init__(a, b)
        self.field = self.__buildField(c)

    def __buildField(self, c):
        field = [c]
        return field

a, b, c = 0, 1, 2
background = Background(a, b, c)

Le nom de la méthode __buildField Est "modifié" en _Field__buildField Dans Field donc dans Field.__init__,

    self.field = self.__buildField()

appelle self._Field__buildField(), qui est la méthode __buildField de Field. De même,

    self.field = self.__buildField(c)

dans Background.__init__ appelle la méthode __buildField de Background.

41
unutbu

Je m'attendais à ce que Background init () soit appelé

En fait, Background init() est appelée ..

Mais jetez un oeil à votre classe d'arrière-plan ..

class Background( Field ):
    def __init__( self, a, b, c ):
        super(Background, self).__init__( a, b )
        self.field = self.buildField( c )

Ainsi, la première instruction de __init__ Invoque la méthode super class(Field) init .. et passe le self comme argument .. Maintenant, ce self est en fait un référence de Background class ..

Maintenant dans votre classe Field: -

class Field( object ):
    def __init__( self, a, b ):

        print self.__class__  // Prints `<class '__main__.Background'>`
        self.a = a
        self.b = b
        self.field = self.buildField()

Votre méthode buildField() appelle en fait celle de la classe Background. En effet, la self est ici une instance de la classe Background (Essayez d'imprimer self.__class__ dans votre méthode __init__ de Field class) .. Comme vous l'avez passée en invoquant la méthode __init__, à partir de la classe Background ..

C'est pourquoi vous obtenez une erreur ..

L'erreur "TypeError: buildField () prend exactement 2 arguments (1 donné).

Comme vous ne transmettez aucune valeur. Ainsi, seule la valeur transmise est le self implicite.

16
Rohit Jain

La super(Background, self).__init__( a, b ) invoquera:

def __init__( self, a, b ):
    self.a = a
    self.b = b
    self.field = self.buildField()

dans Field. Cependant, self fait ici référence à l'instance background, et self.buildField() appelle en fait buildField() de Background, c'est pourquoi vous obtenez cette erreur.


Il semble que votre code devrait être mieux écrit comme:

class Field( object ):
    def __init__( self, a, b ):
        self.a = a
        self.b = b
        self.field = Field.buildField()

    @classmethod
    def buildField(cls):
        field = [0,0,0]
        return field

class Background( Field ):
    def __init__( self, a, b, c ):
        super(Background, self).__init__(a, b)
        self.field = Background.buildField(c)

    @classmethod
    def buildField(cls,c):
        field = [c]
        return field

a, b, c = 0, 1, 2
background = Background( a, b, c )

Si vous ne pouvez pas permettre au constructeur de base de terminer, cela signale que la conception est défectueuse.

Il est donc préférable de séparer buildField() pour appartenir à la classe en utilisant classmethod décorateur ou staticmethod , si vous devez appeler ces méthodes dans votre constructeur.

Cependant, si votre constructeur de classe de base n'invoque aucune méthode d'instance de l'intérieur, vous pouvez alors remplacer en toute sécurité n'importe quelle méthode de cette classe de base.

3
K Z

Overriding dont on parle mais ça me semble chaining constructors or (methods)

Et cela ressemble aussi à écraser propriétés:

Laissez-moi expliquer:

  • Une propriété nommée champ sera initialisée comme [0,0,0]. @property les décorateurs sont plus en forme.

  • Ensuite, Background class écraser cette propriété.


Solution rapide et sale

Je ne connais pas votre logique métier mais parfois en contournant le __init__ la méthode m'a donné plus de contrôle:

#!/usr/bin/env python

class Field( object ):
    def __init__( self, a, b ):
        self.a = a
        self.b = b
        self.field = self.buildField()

    def buildField( self ):
        field = [0,0,0]
        return field

class Background( Field ):
    def __init__( self, a, b, c ):
        # super(Background, self).__init__( a, b )
        # Unfortunately you should repeat or move initializing a and b
        # properties here
        self.a = a
        self.b = b

        self.field = self.buildField( c )

    def buildField( self, c ):
        # You can access super class methods 
        assert super(Background, self).buildField() == [0,0,0]
        field = [c]
        return field


a, b, c = 0, 1, 2
bg = Background(a,b,c)
assert bg.field == [2]

Utilisation des propriétés

A une syntaxe plus propre.

#!/usr/bin/env python

class Field( object ):

    @property
    def field(self):
        return [0,0,0]


    def __init__( self, a, b ):
        self.a = a
        self.b = b


class Background( Field ):
    def __init__( self, a, b, c ):
        super(Background, self).__init__( a, b )
        self.c = c

        assert  (self.a, self.b, self.c) == (0,1,2)  # We assigned a and b in 
                                                   # super class's __init__ method

        assert super(Background, self).field == [0,0,0]
        assert self.field == [2]

    @property
    def field(self):
        return [self.c]


a, b, c = 0, 1, 2

background = Background( a, b, c )

print background.field
1
guneysus