Potrzebuję praktycznego podejścia do uzyskania wszystkich klas dziedziczonych z klasy podstawowej w Pythonie.
Klasy w nowym stylu (tj. Podklasy z object
, co jest domyślne w Pythonie 3) mają __subclasses__
metodę, która zwraca podklasy:
class Foo(object): pass
class Bar(Foo): pass
class Baz(Foo): pass
class Bing(Bar): pass
Oto nazwy podklas:
print([cls.__name__ for cls in Foo.__subclasses__()])
# ['Bar', 'Baz']
Oto same podklasy:
print(Foo.__subclasses__())
# [<class '__main__.Bar'>, <class '__main__.Baz'>]
Potwierdzenie, że podklasy rzeczywiście są wymienione Foo
jako ich podstawa:
for cls in Foo.__subclasses__():
print(cls.__base__)
# <class '__main__.Foo'>
# <class '__main__.Foo'>
Uwaga: jeśli chcesz otrzymać podklasy, musisz powtórzyć:
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'>}
Zauważ, że jeśli definicja klasy podklasy nie została jeszcze wykonana - na przykład, jeśli moduł podklasy nie został jeszcze zaimportowany - wtedy ta podklasa jeszcze nie istnieje i __subclasses__
nie będzie mogła jej znaleźć.
Wspomniałeś o „podanej nazwie”. Ponieważ klasy Python są pierwszorzędnymi obiektami, nie musisz używać łańcucha z nazwą klasy w miejscu klasy ani nic podobnego. Możesz po prostu użyć tej klasy bezpośrednio i prawdopodobnie powinieneś.
Jeśli masz ciąg znaków reprezentujący nazwę klasy i chcesz znaleźć podklasy tej klasy, to musisz wykonać dwa kroki: znajdź klasę podaną jej nazwą, a następnie znajdź podklasy z __subclasses__
powyższym opisem.
Jak znaleźć klasę na podstawie nazwy, zależy od tego, gdzie chcesz ją znaleźć. Jeśli spodziewasz się znaleźć go w tym samym module co kod, który próbuje zlokalizować klasę, to
cls = globals()[name]
wykona zadanie lub w mało prawdopodobnym przypadku, gdy spodziewasz się go znaleźć u miejscowych,
cls = locals()[name]
Jeśli klasa mogłaby znajdować się w dowolnym module, to ciąg nazwy powinien zawierać w pełni kwalifikowaną nazwę - coś w stylu 'pkg.module.Foo'
zamiast po prostu 'Foo'
. Użyj, importlib
aby załadować moduł klasy, a następnie pobierz odpowiedni atrybut:
import importlib
modname, _, clsname = name.rpartition('.')
mod = importlib.import_module(modname)
cls = getattr(mod, clsname)
Gdy znajdziesz klasę, cls.__subclasses__()
zwróci listę jej podklas.
Jeśli chcesz tylko bezpośrednie podklasy, to
.__subclasses__()
działa dobrze. Jeśli chcesz mieć wszystkie podklasy, podklasy podklas itd., Potrzebujesz funkcji, która to zrobi.Oto prosta, czytelna funkcja, która rekurencyjnie znajduje wszystkie podklasy danej klasy:
źródło
all_subclasses
byćset
eliminowanie duplikatów?A(object)
,B(A)
,C(A)
, iD(B, C)
.get_all_subclasses(A) == [B, C, D, D]
.Najprostsze rozwiązanie w formie ogólnej:
I metoda klasy na wypadek, gdybyś miał jedną klasę, z której dziedziczysz:
źródło
Python 3.6 -
__init_subclass__
Jak wspomniano w innej odpowiedzi, możesz sprawdzić
__subclasses__
atrybut, aby uzyskać listę podklas, ponieważ w Pythonie 3.6 możesz modyfikować tworzenie tego atrybutu, zastępując__init_subclass__
metodę.W ten sposób, jeśli wiesz, co robisz, możesz zastąpić zachowanie
__subclasses__
i pominąć / dodać podklasy z tej listy.źródło
__init_subclass
klasę rodzica.Uwaga: Widzę, że ktoś (nie @unutbu) zmienił odpowiedź, do której się odwołuje, aby nie była już używana
vars()['Foo']
- więc główny punkt mojego postu nie ma już zastosowania.FWIW, oto, o co mi chodziło o odpowiedzi @ unutbu działającej tylko z lokalnie zdefiniowanymi klasami - i że użycie
eval()
zamiastvars()
spowoduje, że będzie działać z dowolną dostępną klasą, nie tylko tymi zdefiniowanymi w bieżącym zakresie.Dla tych, którzy nie lubią używać
eval()
, pokazano również sposób, aby tego uniknąć.Najpierw jest konkretny przykład pokazujący potencjalny problem z użyciem
vars()
:Można to poprawić, przesuwając w
eval('ClassName')
dół do zdefiniowanej funkcji, co ułatwia korzystanie z niej bez utraty dodatkowej ogólności uzyskanej dzięki użyciu,eval()
która w przeciwieństwie do tegovars()
nie jest zależna od kontekstu:Wreszcie jest możliwe, a może nawet w niektórych przypadkach ważne, aby unikać używania
eval()
ze względów bezpieczeństwa, więc oto wersja bez niego:źródło
eval()
- lepiej teraz?Znacznie krótsza wersja umożliwiająca uzyskanie listy wszystkich podklas:
źródło
Z pewnością możemy to łatwo zrobić, mając dostęp do samego obiektu, tak.
Samo podanie jego nazwy jest kiepskim pomysłem, ponieważ może istnieć wiele klas o tej samej nazwie, nawet zdefiniowanych w tym samym module.
Stworzyłem implementację dla innej odpowiedzi , a ponieważ odpowiada ona na to pytanie i jest nieco bardziej elegancka niż inne rozwiązania tutaj, oto ona:
Stosowanie:
źródło
To nie jest tak dobra odpowiedź, jak użycie specjalnej wbudowanej
__subclasses__()
metody klasy, o której wspomina @unutbu, dlatego przedstawiam ją jedynie jako ćwiczenie.subclasses()
Funkcja zdefiniowana zwraca słownika, który odwzorowuje wszystkie nazwy podklasy samych podklasy.Wynik:
źródło
Oto wersja bez rekurencji:
Różni się od innych implementacji tym, że zwraca oryginalną klasę. Jest tak, ponieważ upraszcza kod i:
Jeśli get_subclasses_gen wygląda trochę dziwnie, to dlatego, że został utworzony przez konwersję implementacji rekurencyjnej na ogon w generator pętli:
źródło