Oto zasadniczo hybrydowy wersja @Ignacio Vazquez-Abrams' i odpowiedzi @ aaronasterling, która zachowuje kolejność podklasy na liście. Początkowo pożądane nazwy podklasy (tj strings) są ręcznie umieszczane na liście subs
w dowolnej kolejności, to w każdej podklasy jest zdefiniowana dekorator klasy powoduje odpowiedni łańcuch być zastąpiona przez rzeczywistą podklasy:
class Base(object): # New-style class (i.e. explicitly derived from object).
@classmethod
def register_subclass(cls, subclass):
""" Class decorator for registering subclasses. """
# Replace any occurrences of the class name in the class' subs list.
# with the class itself.
# Assumes the classes in the list are all subclasses of this one.
# Works because class decorators are called *after* the decorated class'
# initial creation.
while subclass.__name__ in cls.subs:
cls.subs[cls.subs.index(subclass.__name__)] = subclass
return cls # Return modified class.
subs = ['Sub3', 'Sub1'] # Ordered list of subclass names.
@Base.register_subclass
class Sub1(Base): pass
@Base.register_subclass
class Sub2(Base): pass
@Base.register_subclass
class Sub3(Base): pass
print('Base.subs: {}'.format(Base.subs))
# Base.subs: [<class '__main__.Sub3'>, <class '__main__.Sub1'>]
Aktualizacja
Dokładnie to samo można również wykonać za pomocą metaklasa-który ma tę zaletę, że eliminuje potrzebę wyraźnie ozdobić każdą podklasę, jak pokazano w mojej oryginalnej odpowiedzi, który jest pokazany powyżej (które akceptowane), jednak sprawia, że wszystko dzieje się automagicznie. Zauważ, że nawet jeśli metaclass '__init__()
jest wywoływany w celu utworzenia każdej podklasy, aktualizuje ona tylko listę subs
, jeśli pojawia się w niej nazwa podklasy, więc początkowa definicja klasy bazowej zawartości listy subs
nadal kontroluje to, co dostaje zastąpiony w nim (zachowując kolejność).
class BaseMeta(type):
def __init__(cls, name, bases, classdict):
if classdict.get('__metaclass__') is not BaseMeta: # Metaclass instance?
# Replace any occurrences of a subclass' name in the class being
# created the class' sub list with the subclass itself.
# Names of classes which aren't direct subclasses will be ignored.
while name in cls.subs:
cls.subs[cls.subs.index(name)] = cls
# Chain to __init__() of the class instance being created after changes.
# Note class instance being defined must be new-style class.
super(BaseMeta, cls).__init__(name, bases, classdict)
# Python 2 metaclass syntax.
class Base(object): # New-style class (derived from built-in object class).
__metaclass__ = BaseMeta
subs = ['Sub3', 'Sub1'] # Ordered list of subclass names.
# Python 3 metaclass syntax.
#class Base(metaclass=BaseMeta):
# subs = ['Sub3', 'Sub1'] # Ordered list of subclass names.
# Note: No need to manually register the (direct) subclasses.
class Sub1(Base): pass
class Sub2(Base): pass
class Sub3(Base): pass
print('Base.subs: {}'.format(Base.subs))
Ważne, aby pamiętać, że istnieje co najmniej jedna subtelna różnica pomiędzy tymi dwoma odpowiedzi, a mianowicie, że pierwszy będzie współpracować z dowolną nazwę klasy, która została zarejestrowana przez @Base.register_subclass()
, czy jego faktycznie podklasą Base
(choć to może być możliwe do zmiany/naprawy).
Wskazuję na to z kilku powodów: Po pierwsze dlatego, że w swoich komentarzach powiedziałeś, że subs
był "zbiorem klas na liście, niektóre z które mogą być jej podklasami ", a co ważniejsze, ponieważ jest to nie sprawa z kodem w mojej aktualizacji, która działa tylko dla podklas Base
, ponieważ skutecznie "automatycznie" rejestruje się za pośrednictwem metaklasy - ale pozostawia wszystko inne na samej liście. Można to uznać za błąd lub funkcję. ;¬)
To jest zły projekt. Łączycie fabrykę (która jest świadoma podklas) z nadklasą (która nie musi być świadoma podklas). Czemu to robić? Dlaczego po prostu nie rozdzielać rzeczy i nie upraszczać? –
@ S.Lott: A co, jeśli tak naprawdę nie ma potrzeby bycia świadomym swoich podklas jako takich. Musi mieć kilka klas na liście, z których niektóre mogą być jej podklasami. – pafcu
@pafcu: "niektóre z nich mogą być jej podklasami".To wciąż zły pomysł - superklasa nigdy nie powinna wiedzieć o jej podklasach. Takie postępowanie narusza zasadę projektowania OO. Nie można już po prostu tworzyć nowych podklas bez zmiany nadklasy. Musisz oddzielić "listę klas" od nadklasy. Jedynym powodem posiadania "listy zajęć" jest stworzenie fabryki. Fabryka to dobra rzecz; nie jest to jednak cecha superklasy. –