[python] Comment modifier dynamiquement la classe de base des instances lors de l'exécution?


Answers

J'ai aussi lutté avec cela et j'ai été intrigué par votre solution, mais Python 3 nous l'enlève:

AttributeError: attribute '__dict__' of 'type' objects is not writable

J'ai en fait un besoin légitime pour un décorateur qui remplace la superclasse (unique) de la classe décorée. Il faudrait une description trop longue à inclure ici (j'ai essayé, mais je n'ai pas pu obtenir une longueur raisonnable et une complexité limitée - elle est apparue dans le contexte de l'utilisation par de nombreuses applications Python d'un serveur d'entreprise basé sur Python où différentes applications nécessitaient des variations légèrement différentes d'une partie du code.)

La discussion sur cette page et d'autres similaires ont indiqué que le problème de l'assignation à __bases__ ne se produit que pour les classes sans super-classe définie (ie, dont la seule super-classe est l'objet). J'ai été capable de résoudre ce problème (pour Python 2.7 et 3.2) en définissant les classes dont j'avais besoin de remplacer la superclasse comme sous-classes d'une classe triviale:

## T is used so that the other classes are not direct subclasses of object,
## since classes whose base is object don't allow assignment to their __bases__ attribute.

class T: pass

class A(T):
    def __init__(self):
        print('Creating instance of {}'.format(self.__class__.__name__))

## ordinary inheritance
class B(A): pass

## dynamically specified inheritance
class C(T): pass

A()                 # -> Creating instance of A
B()                 # -> Creating instance of B
C.__bases__ = (A,)
C()                 # -> Creating instance of C

## attempt at dynamically specified inheritance starting with a direct subclass
## of object doesn't work
class D: pass

D.__bases__ = (A,)
D()

## Result is:
##     TypeError: __bases__ assignment: 'A' deallocator differs from 'object'
Question

Cet article présente un extrait montrant l'utilisation de __bases__ pour modifier dynamiquement la hiérarchie d'héritage de certains codes Python, en ajoutant une classe à une collection de classes existante dont elle hérite. Ok, c'est difficile à lire, le code est probablement plus clair:

class Friendly:
    def hello(self):
        print 'Hello'

class Person: pass

p = Person()
Person.__bases__ = (Friendly,)
p.hello()  # prints "Hello"

Autrement dit, Person n'hérite pas de Friendly au niveau de la source, mais cette relation d'héritage est ajoutée dynamiquement au moment de l'exécution en modifiant l'attribut __bases__ de la classe Person. Cependant, si vous modifiez Friendly et Person pour être de nouvelles classes de style (en héritant de l'objet), vous obtenez l'erreur suivante:

TypeError: __bases__ assignment: 'Friendly' deallocator differs from 'object'

Un peu de Google sur cela semble indiquer des incompatibilités entre les classes de style nouveau et anciennes en ce qui concerne la modification de la hiérarchie d'héritage à l'exécution. Plus précisément: "Les objets de classe nouveau style ne prennent pas en charge l'affectation à leur attribut de base " .

Ma question, est-il possible de faire fonctionner l'exemple Friendly / Person ci-dessus en utilisant des classes de style nouveau dans Python 2.7+, éventuellement en utilisant l'attribut __mro__ ?

Disclaimer: Je réalise pleinement que c'est un code obscur. Je me rends compte que dans les vrais trucs de code de production comme celui-ci ont tendance à être illisibles, c'est une expérience purement pensée, et pour les funzies apprendre quelque chose sur la façon dont Python traite les problèmes liés à l'héritage multiple.




Droit de la batte, toutes les mises en garde de jouer avec la hiérarchie de classe dynamiquement sont en vigueur.

Mais si cela doit être fait alors, apparemment, il y a un hack qui contourne le "deallocator differs from 'object" issue when modifying the __bases__ attribute pour les nouvelles classes de style.

Vous pouvez définir un objet de classe

class Object(object): pass

Qui dérive une classe du type métaclasse intégré. Voilà, maintenant vos nouvelles classes de style peuvent modifier les __bases__ sans aucun problème.

Dans mes tests, cela a très bien fonctionné car toutes les instances existantes (avant de changer l'héritage) et ses classes dérivées ont ressenti l'effet du changement, y compris la mro à jour de leur mro .




Les réponses ci-dessus sont bonnes si vous avez besoin de changer une classe existante à l'exécution. Cependant, si vous cherchez à créer une nouvelle classe qui hérite d'une autre classe, il existe une solution beaucoup plus propre. J'ai eu cette idée de https://.com/a/21060094/3533440 , mais je pense que l'exemple ci-dessous illustre mieux un cas d'utilisation légitime.

def make_default(Map, default_default=None):
    """Returns a class which behaves identically to the given
    Map class, except it gives a default value for unknown keys."""
    class DefaultMap(Map):
        def __init__(self, default=default_default, **kwargs):
            self._default = default
            super().__init__(**kwargs)

        def __missing__(self, key):
            return self._default

    return DefaultMap

DefaultDict = make_default(dict, default_default='wug')

d = DefaultDict(a=1, b=2)
assert d['a'] is 1
assert d['b'] is 2
assert d['c'] is 'wug'

Corrigez-moi si je me trompe, mais cette stratégie me semble très lisible, et je l'utiliserais dans le code de production. Ceci est très similaire à des foncteurs dans OCaml.






Related