Si vous souhaitez utiliser l'héritage pour résoudre votre problème, vous devez commencer par écrire un fichier de type AngryDog
que vous pouvez utiliser pour construire des instances saines.
L'étape suivante consisterait à ajouter un from_dog
méthode de classe Il s'agit peut-être de quelque chose comme cela :
from dataclasses import dataclass, asdict
@dataclass(frozen=True)
class AngryDog(Dog):
bite: bool = True
@classmethod
def from_dog(cls, dog: Dog, **kwargs):
return cls(**asdict(dog), **kwargs)
Mais en suivant ce modèle, vous serez confronté à un cas particulier, que vous avez vous-même déjà signalé par l'intermédiaire de l'option whatever
paramètre. Lorsque l'on rappelle le Dog
tout constructeur de InitVar
manquera dans un asdict
puisqu'il n'est pas un membre à part entière de la classe. En fait, tout ce qui se passe dans la classe de données d'une __post_init__
, qui est l'endroit où InitVars
peut entraîner des bogues ou un comportement inattendu.
S'il ne s'agit que de choses mineures, comme le filtrage ou la suppression de paramètres connus du cls
et qu'il n'est pas prévu que la classe mère change, vous pouvez simplement essayer de le gérer en from_dog
. Mais il n'existe conceptuellement aucun moyen de fournir une solution générale pour ce type de problème. from_instance
problème.
Composition fonctionnerait sans problème du point de vue de l'intégrité des données, mais pourrait être peu idiomatique ou maladroit compte tenu de l'objet exact de l'opération. Une telle extension de chien ne serait pas utilisable à la place d'une véritable instance de chien, mais nous pourrions lui donner la bonne forme au cas où cela serait nécessaire :
class AngryDogExtension:
def __init__(self, dog, bite=True):
self.dog = dog
self.bite = bite
def __getattr__(self, item):
"""Will make instances of this class bark like a dog."""
return getattr(self.dog, item)
Utilisation :
# starting with a basic dog instance
>>> dog = Dog(name='pluto', blabla=1, whatever=['a', 'b'])
>>> dog_e = AngryDogExtension(d)
>>> dog_e.bite # no surprise here, just a regular member
True
>>> dog_e.name # this class proxies its dog member, so no need to run `dog_e.dog.name`
pluto
Mais en fin de compte, le fait est que isinstance(dog_e, Dog)
renverra False
. Si vous vous engagez à faire ce retour d'appel True
Il existe des astuces avancées pour vous aider et faire en sorte que tous ceux qui héritent de votre code vous détestent :
class AngryDogDoppelganger(Dog):
def __init__(self, bite, **kwargs):
if "__dog" in kwargs:
object.__setattr__(self, "__dog", kwargs["__dog"])
else:
object.__setattr__(self, "__dog", Dog(**kwargs))
object.__setattr__(self, "bite", bite)
@classmethod
def from_dog(cls, dog, bite=True):
return cls(bite, __dog=dog)
def __getattribute__(self, name):
"""Will make instances of this class bark like a dog.
Can't use __getattr__, since it will see its own instance
attributes. To have __dog work as a proxy, it needs to be
checked before basic attribute lookup.
"""
try:
return getattr(object.__getattribute__(self, "__dog"), name)
except AttributeError:
pass
return object.__getattribute__(self, name)
Utilisation :
# starting with a basic dog instance
>>> dog = Dog(name='pluto', blabla=1, whatever=['a', 'b'])
# the doppelganger offers a from_instance method, as well as
# a constructor that works as expected of a subclass
>>> angry_1 = AngryDogDoppelganger.from_dog(dog)
>>> angry_2 = AngryDogDoppelganger(name='pluto', blabla=1, whatever=['a', 'b'], bite=True)
# instances also bark like at dog, and now even think they're a dog
>>> angry_1.bite # from subclass
True
>>> angry_1.name # looks like inherited from parent class, is actually proxied from __dog
pluto
>>> isinstance(angry_1, Dog) #
True
La plupart des méthodes d'ajout de données, comme le __repr__
Cependant, le système ne fonctionnera pas, notamment en ce qui concerne l'insertion d'instances de doublons dans des éléments tels que dataclass.asdict
ou même simplement vars
- à utiliser à ses propres risques.