79 votes

Python: modification dynamique des classes de base à l'exécution. Comment?

Cet article est un extrait montrant l'utilisation de l' __bases__ de changement dynamique de la hiérarchie d'héritage de code Python, par l'ajout d'une classe à une des classes existantes collection de classes dont il hérite. Ok, c'est dur à lire, le code est probablement le plus clair:

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

class Person: pass

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

C'est, Person n'héritent pas de Friendly au niveau de la source, mais plutôt cette relation d'héritage est ajouté dynamiquement à l'exécution, par la modification de l' __bases__attribut de la classe Personne. Toutefois, si vous modifiez Friendly et Person à de nouvelles classes de style (par inherting de l'objet), vous obtenez l'erreur suivante:

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

Un peu de Googling sur ce qui semble indiquer certaines incompatibilités entre un style nouveau et vieux style de classes en ce qui concerne la modification de la hiérarchie d'héritage au moment de l'exécution. Plus précisément: "New-style des objets de la classe ne prennent pas en charge l'affectation de leurs bases attribut"

Ma question, est-il possible de faire de la ci-dessus Amicale/Personne exemple de travail à l'aide de nouvelles classes en Python 2.7+, éventuellement par l'utilisation de l' __mro__ d'attribut?

Avertissement: j'ai pleinement conscience que ce n'est pas le code. J'ai pleinement conscience que la production réelle du code des astuces comme cette tendance à la frontière de illisible, c'est une pure expérience de pensée, et pour funzies d'apprendre quelque chose sur la façon Python traite de questions relatives à l'héritage multiple.

41voto

Adam Parkin Points 2154

Ok, encore une fois, ce n'est pas quelque chose que vous devrait le faire normalement, ceci est à titre informatif seulement.

Où Python recherche d'une méthode sur une instance de l'objet est déterminé par l' __mro__ attribut de la classe qui définit un objet (la M méthode de R esolution O rder attribut). Ainsi, si l'on pouvait modifier l' __mro__ de Person, nous aimerions obtenir le comportement désiré. Quelque chose comme:

setattr(Person, '__mro__', (Person, Friendly, object))

Le problème est qu' __mro__ est un attribut lecture seule, et donc setattr ne fonctionne pas. Peut-être que si vous êtes un Python gourou, il y a un moyen de contourner cela, mais clairement, je manque de statut de gourou que je ne peux pas penser à un.

Une solution possible est de simplement redéfinir la classe:

def ModifyPersonToBeFriendly():
    # so that we're modifying the global identifier 'Person'
    global Person

    # now just redefine the class using type(), specifying that the new
    # class should inherit from Friendly and have all attributes from
    # our old Person class
    Person = type('Person', (Friendly,), dict(Person.__dict__)) 

def main():
    ModifyPersonToBeFriendly()
    p = Person()
    p.hello()  # works!

Ce que ce n'est pas faire est de modifier tout créé précédemment Person cas avoir l' hello() méthode. Par exemple (juste en modifiant main()):

def main():
    oldperson = Person()
    ModifyPersonToBeFriendly()
    p = Person()
    p.hello()  
    # works!  But:
    oldperson.hello()
    # does not

Si les détails de l' type d'appel ne sont pas clairs, alors lisez la e-satis "excellente réponse sur" Ce qui est une métaclasse en Python?'.

25voto

Mitchell Model Points 406

J'ai eu du mal avec ça aussi, et il a été intrigué par votre solution, mais Python 3, il enlève de nous:

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

En fait, j'ai un besoin légitime d'un décorateur qui remplace le (seul) de la superclasse de la salle de classe. Il nécessiterait une trop longue description de l'inclure ici (j'ai essayé, mais ne pouvais pas le faire raisonnablement une longueur et une complexité limitée -- il est venu dans le cadre de l'utilisation par de nombreux Python applications d'un Python d'entreprise basées sur le serveur où différentes applications nécessaires des variations légèrement différentes d'une partie du code.)

La discussion sur cette page, et d'autres, comme il a fourni des conseils que le problème de l'affectation d' __bases__ se produit uniquement pour les classes sans super-classe définie (c'est à dire, dont la seule super-classe est un objet). J'ai été en mesure de résoudre ce problème (pour Python 2.7 et 3.2) en définissant les classes dont la super-classe j'avais besoin de le remplacer comme étant des sous-classes de trivial classe:

## 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 ' + self.name())

    @classmethod
    def name(self):
        return "A"

## ordinary inheritance
class B(A):
    @classmethod
    def name(self):
        return "B"

## dynamically specified inheritance
class C(T):
    @classmethod
    def name(self):
        return "C"

A()
B()
C.__bases__ = (A,)
C()

## Output is:
##     Creating instance of A
##     Creating instance of B
##     Creating instance of C
##

## attempt at dynamically specified inheritance starting with a direct subclass
## of object doesn't work
class D:
    @classmethod
    def name(self):
        return "D"

D.__bases__ = (A,)
D()

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

6voto

akaRem Points 1215

Je ne peux pas garantir les conséquences, mais que ce code fait ce que vous voulez à py2.7.2.

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

class Person(object): pass

# we can't change the original classes, so we replace them
class newFriendly: pass
newFriendly.__dict__ = dict(Friendly.__dict__)
Friendly = newFriendly
class newPerson: pass
newPerson.__dict__ = dict(Person.__dict__)
Person = newPerson

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

Nous savons que c'est possible. Cool. Mais nous ne l'utilisons jamais!

3voto

Sam Gulve Points 11

Droit de la chauve-souris, toutes les mises en garde de vous embêter avec la hiérarchie de classes dynamiquement sont en vigueur.

Mais si cela doit être fait, puis, apparemment, il y a un hack pour obtenir autour de la "deallocator diffère de l '"objet" problème lors de la modification de l' __assiettes__ attribut pour le nouveau style de classes.

Vous pouvez définir un objet de classe

Classe d'objet(object): pass

Qui dérive d'une classe de la métaclasse "type". Voilà, maintenant votre nouveau style de classes peuvent modifier l' __assiettes__ sans aucun problème.

Dans mes tests, cela fonctionne très bien comme tous les existants (avant de changer l'héritage)ses instances et ses classes dérivées sentit l'effet de la modification, y compris leurs mro obtenir des mises à jour.

Pour plus de détails, veuillez voir le rapport de bug évoqué par mgilson

2voto

mgilson Points 92954

Après un peu de recherche sur Google, le rapport de bogue Python suivant semblait pertinent ... http://bugs.python.org/issue672115

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X