J'ai besoin d'une approche de travail pour obtenir toutes les classes héritées d'une classe de base en Python.
Les classes de nouveau style (c'est-à-dire sous-classées à partir de object
, qui est la valeur par défaut en Python 3) ont une __subclasses__
méthode qui retourne les sous-classes:
class Foo(object): pass
class Bar(Foo): pass
class Baz(Foo): pass
class Bing(Bar): pass
Voici les noms des sous-classes:
print([cls.__name__ for cls in Foo.__subclasses__()])
# ['Bar', 'Baz']
Voici les sous-classes elles-mêmes:
print(Foo.__subclasses__())
# [<class '__main__.Bar'>, <class '__main__.Baz'>]
Confirmation que les sous-classes sont bien listées Foo
comme base:
for cls in Foo.__subclasses__():
print(cls.__base__)
# <class '__main__.Foo'>
# <class '__main__.Foo'>
Notez que si vous voulez des sous-classes, vous devrez récuser:
def all_subclasses(cls):
return set(cls.__subclasses__()).union(
[s for c in cls.__subclasses__() for s in all_subclasses(c)])
print(all_subclasses(Foo))
# {<class '__main__.Bar'>, <class '__main__.Baz'>, <class '__main__.Bing'>}
Notez que si la définition de classe d'une sous-classe n'a pas encore été exécutée - par exemple, si le module de la sous-classe n'a pas encore été importé - alors cette sous-classe n'existe pas encore et __subclasses__
ne la trouvera pas.
Vous avez mentionné "étant donné son nom". Étant donné que les classes Python sont des objets de première classe, vous n'avez pas besoin d'utiliser une chaîne avec le nom de la classe à la place de la classe ou quelque chose comme ça. Vous pouvez simplement utiliser la classe directement, et vous devriez probablement le faire.
Si vous avez une chaîne représentant le nom d'une classe et que vous souhaitez rechercher les sous-classes de cette classe, il y a deux étapes: recherchez la classe en fonction de son nom, puis recherchez les sous-classes avec __subclasses__
comme ci-dessus.
Comment trouver la classe à partir du nom dépend de l'endroit où vous vous attendez à le trouver. Si vous vous attendez à le trouver dans le même module que le code qui essaie de localiser la classe, alors
cls = globals()[name]
ferait le travail, ou dans le cas peu probable où vous vous attendez à le trouver chez les locaux,
cls = locals()[name]
Si la classe peut être dans n'importe quel module, alors votre chaîne de nom doit contenir le nom complet - quelque chose comme 'pkg.module.Foo'
au lieu de juste 'Foo'
. Utilisez importlib
pour charger le module de la classe, puis récupérez l'attribut correspondant:
import importlib
modname, _, clsname = name.rpartition('.')
mod = importlib.import_module(modname)
cls = getattr(mod, clsname)
Quoi que vous trouviez la classe, cls.__subclasses__()
retournerait alors une liste de ses sous-classes.
Si vous voulez juste des sous-classes directes,
.__subclasses__()
fonctionne très bien. Si vous voulez toutes les sous-classes, sous-classes de sous-classes, etc., vous aurez besoin d'une fonction pour le faire pour vous.Voici une fonction simple et lisible qui recherche récursivement toutes les sous-classes d'une classe donnée:
la source
all_subclasses
être unset
pour éliminer les doublons?A(object)
,B(A)
,C(A)
etD(B, C)
.get_all_subclasses(A) == [B, C, D, D]
.La solution la plus simple sous forme générale:
Et une méthode de classe dans le cas où vous avez une seule classe dont vous héritez:
la source
Python 3.6 -
__init_subclass__
Comme d'autres réponses l'ont mentionné, vous pouvez vérifier l'
__subclasses__
attribut pour obtenir la liste des sous-classes, car python 3.6 vous pouvez modifier cette création d'attribut en remplaçant la__init_subclass__
méthode.De cette façon, si vous savez ce que vous faites, vous pouvez remplacer le comportement de
__subclasses__
et omettre / ajouter des sous-classes de cette liste.la source
__init_subclass
sur la classe du parent.Remarque: je vois que quelqu'un (pas @unutbu) a changé la réponse référencée afin qu'elle n'utilise plus
vars()['Foo']
- donc le point principal de mon message ne s'applique plus.FWIW, voici ce que je voulais dire à propos de la réponse de @ unutbu ne fonctionnant qu'avec des classes définies localement - et que l'utilisation
eval()
au lieu de levars()
ferait fonctionner avec n'importe quelle classe accessible, pas seulement celles définies dans la portée actuelle.Pour ceux qui n'aiment pas utiliser
eval()
, un moyen est également indiqué pour l'éviter.Voici d'abord un exemple concret démontrant le problème potentiel de l'utilisation
vars()
:Cela pourrait être amélioré en déplaçant le
eval('ClassName')
bas dans la fonction définie, ce qui rend son utilisation plus facile sans perdre la généralité supplémentaire obtenue en utilisanteval()
ce qui contrairement àvars()
n'est pas contextuel:Enfin, il est possible, et peut-être même important dans certains cas, d'éviter d'utiliser
eval()
pour des raisons de sécurité, voici donc une version sans:la source
eval()
- mieux maintenant?Une version beaucoup plus courte pour obtenir une liste de toutes les sous-classes:
la source
Nous pouvons certainement le faire facilement en ayant accès à l'objet lui-même, oui.
Donner simplement son nom est une mauvaise idée, car il peut y avoir plusieurs classes du même nom, même définies dans le même module.
J'ai créé une implémentation pour une autre réponse , et comme elle répond à cette question et qu'elle est un peu plus élégante que les autres solutions ici, la voici:
Usage:
la source
Ce n'est pas une aussi bonne réponse que d'utiliser la méthode spéciale de
__subclasses__()
classe intégrée mentionnée par @unutbu, donc je la présente simplement comme un exercice. Lasubclasses()
fonction définie renvoie un dictionnaire qui mappe tous les noms de sous-classe aux sous-classes elles-mêmes.Production:
la source
Voici une version sans récursivité:
Cela diffère des autres implémentations en ce qu'il renvoie la classe d'origine. En effet, cela rend le code plus simple et:
Si get_subclasses_gen a l'air un peu bizarre, c'est parce qu'il a été créé en convertissant une implémentation récursive en un générateur de boucles:
la source