web-dev-qa-db-fra.com

Les fonctions __init__ de la classe Mixin ne sont-elles pas appelées automatiquement?

Je voudrais utiliser un Mixin pour toujours ajouter des fonctionnalités init à mes classes enfants qui héritent chacune de différentes classes de base d'API. Plus précisément, je voudrais créer plusieurs classes enfants différentes qui héritent de l'une de ces différentes classes de base fournies par l'API et de la seule Mixin, qui aura toujours le code d'initialisation Mixin exécuté de la même manière, sans réplication de code. Cependant, il semble que la fonction __init__ de la classe Mixin ne soit jamais appelée, sauf si je l'appelle explicitement dans la fonction __init__ de la classe Child, qui est loin d'être idéale. J'ai construit un cas de test simple:

class APIBaseClassOne(object):
    def __init__(self, *args, **kwargs):
        print (" base ")

class SomeMixin(object):
    def __init__(self, *args, **kwargs):
        print (" mixin before ")
        super(SomeMixin, self).__init__(*args, **kwargs)
        print (" mixin after ")

class MyClass(APIBaseClassOne):
    pass

class MixedClass(MyClass, SomeMixin):
    pass

Comme vous pouvez le voir dans la sortie suivante, l'initialisation de la fonction Mixin n'est jamais appelée:

>>> import test
>>> test.MixedClass()
 base
<test.MixedClass object at 0x1004cc850>

Existe-t-il un moyen de faire cela (faire appeler une fonction init dans un Mixin) sans écrire chaque classe enfant pour invoquer explicitement la fonction init du Mixin? (c'est-à-dire sans avoir à faire quelque chose comme ça dans chaque classe :)

class MixedClass(MyClass, SomeMixin):
    def __init__(*args, **kwargs):
        SomeMixin.__init__(self, *args, **kwargs)
        MyClass.__init__(self, *args, **kwargs) 

Btw, si toutes mes classes enfants héritaient de la même classe de base, je me rends compte que je pourrais créer une nouvelle classe moyenne qui hérite de la classe de base et du mixin et la conserver DRY de cette façon. Cependant, ils héritent de différentes classes de base avec des fonctionnalités communes (classes Django Field, pour être précis).

54
Ben Roberts

Désolé d'avoir vu ça si tard, mais

class MixedClass2(SomeMixin, MyClass):
    pass

>>> m = MixedClass2()
 mixin before 
 base 
 mixin after

Le modèle dont parle @Ignacio s'appelle l'héritage multiple coopératif, et c'est génial. Mais si une classe de base n'est pas intéressée à coopérer, faites-en la deuxième base et votre mixin la première. La __init__() du mixin (et tout ce qu'il définit) sera vérifiée avant la classe de base, suivant MRO de Python.

Cela devrait résoudre la question générale, bien que je ne sois pas sûr qu'il gère votre utilisation spécifique. Les classes de base avec des métaclasses personnalisées (comme Django) ou avec des décorateurs étranges (comme la réponse de @ martineau;) peuvent faire des choses folles.

42
Matt Luongo

Demandez à la classe de base d'invoquer super().__init__() même s'il s'agit d'une sous-classe de object. De cette façon, toutes les méthodes __init__() seront exécutées.

class BaseClassOne(object):
    def __init__(self, *args, **kwargs):
        super(BaseClassOne, self).__init__(*args, **kwargs)
        print (" base ")
23

Python n'effectue aucun appel implicite aux méthodes __init__ Des super-classes d'une classe, mais il est possible de le faire automatiquement. Une façon consiste à définir une métaclasse pour votre (vos) classe (s) mixte (s) qui crée ou étend la méthode __init__ De la classe mixte afin qu'elle appelle toutes les fonctions __init__ De toutes les bases répertoriées dans l'ordre dans lequel elles ont été répertoriés.

Une deuxième façon consiste à utiliser un décorateur de classe, comme indiqué dans la section Edit ci-dessous.

Utilisation d'une métaclasse:

class APIBaseClassOne(object):  # API class (can't be changed)
    def __init__(self, *args, **kwargs):
        print('  APIBaseClassOne.__init__()')

class SomeMixin(object):
    def __init__(self, *args, **kwargs):
        print('  SomeMixin.__init__()')

class MixedClassMeta(type):
    def __new__(cls, name, bases, classdict):
        classinit = classdict.get('__init__')  # Possibly None.

        # Define an __init__ function for the new class.
        def __init__(self, *args, **kwargs):
            # Call the __init__ functions of all the bases.
            for base in type(self).__bases__:
                base.__init__(self, *args, **kwargs)
            # Also call any __init__ function that was in the new class.
            if classinit:
                classinit(self, *args, **kwargs)

        # Add the local function to the new class.
        classdict['__init__'] = __init__
        return type.__new__(cls, name, bases, classdict)

class MixedClass(APIBaseClassOne, SomeMixin):
    __metaclass__ = MixedClassMeta  # important
    # If exists, called after the __init__'s of all the direct bases.
    def __init__(self, *args, **kwargs):
        print('  MixedClass.__init__()')

print('MixedClass():')
MixedClass()

Production:

MixedClass():
  APIBaseClassOne.__init__()
  SomeMixin.__init__()
  MixedClass.__init__()

Modifier

Voici comment accomplir la même chose avec un décorateur de classe (nécessite Python 2.6+):

class APIBaseClassOne(object):  # API class (can't be changed)
    def __init__(self, *args, **kwargs):
        print('  APIBaseClassOne.__init__()')

class SomeMixin(object):
    def __init__(self, *args, **kwargs):
        print('  SomeMixin.__init__()')

def mixedomatic(cls):
    """ Mixed-in class decorator. """
    classinit = cls.__dict__.get('__init__')  # Possibly None.

    # Define an __init__ function for the class.
    def __init__(self, *args, **kwargs):
        # Call the __init__ functions of all the bases.
        for base in cls.__bases__:
            base.__init__(self, *args, **kwargs)
        # Also call any __init__ function that was in the class.
        if classinit:
            classinit(self, *args, **kwargs)

    # Make the local function the class's __init__.
    setattr(cls, '__init__', __init__)
    return cls

@mixedomatic
class MixedClass(APIBaseClassOne, SomeMixin):
    # If exists, called after the __init__'s of all the direct base classes.
    def __init__(self, *args, **kwargs):
        print('  MixedClass.__init__()')

print('MixedClass():')
MixedClass()

Notes

Pour Python <2.6, utilisez MixedClass = mixedomatic(MixedClass) en suivant la définition de classe.

Dans Python 3 la syntaxe pour spécifier les métaclasses est différente, donc au lieu de:

class MixedClass(APIBaseClassOne, SomeMixin):
    __metaclass__ = MixedClassMeta  # important

montré ci-dessus, vous devez utiliser:

class MixedClass(APIBaseClassOne, SomeMixin, metaclass=MixedClassMeta):

La version décoratrice de classe fonctionnera telle quelle dans les deux versions.

18
martineau