2010-11-12 10 views
31

mam niektóre klasy wygląda tak:Przedłożenie deklaracji klas?

class Base: 
    subs = [Sub3,Sub1] 
    # Note that this is NOT a list of all subclasses! 
    # Order is also important 

class Sub1(Base): pass 
class Sub2(Base): pass 
class Sub3(Base): pass 
... 

Teraz to się nie powiedzie, ponieważ SUB1 i SUB3 nie są zdefiniowane, gdy Base.subs jest. Ale oczywiście nie mogę wstawić podklas przed Bazą. Czy istnieje sposób przekazywania dalej - deklarowanie klas w języku Python? Chcę pracować z isinstance, więc typy w subs rzeczywiście muszą być takie same jak później zadeklarowane podklasy, nie wystarczy, że mają taką samą nazwę i inne właściwości.

Jeden sposób obejścia: Base.subs = [Sub3,Sub1]po podklasy zostały zdefiniowane, ale nie lubię konieczności dzielenia mojej klasy w ten sposób.

Edycja: Dodano informację o kolejności

+4

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ć? –

+1

@ 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

+2

@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. –

Odpowiedz

10

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ę. ;¬)

3

Edit: powodu dodanego wymogu aby ja całkowicie przerobione moją odpowiedź. Korzystam również z dekoratora klasowego, który został użyty tutaj pierwszy przez @Ignacio Vazquez-Abrams.

Edit2: Kod obecnie testowane i niektóre głupota nieznacznie skorygowane


class Base(object): 
    subs = [] 

    @classmethod 
    def addsub(cls, before=None): 
     def inner(subclass): 
      if before and before in cls.subs: 
       cls.subs.insert(cls.subs.index(before), subclass) 
      else: 
       cls.subs.append(subclass) 
      return subclass 
     return inner 

@Base.addsub() 
class Sub1(Base): 
    pass 

class Sub2(Base): 
    pass 

@Base.addsub(before=Sub1) 
class Sub3(Base): 
    pass 

+0

W takim przypadku trudno jest określić kolejność listy Base.subs. To zależy od kolejności, w jakiej napisałem kod, który jest podatny na błędy. Base.subs jest także własnością klasy Base, więc wolałbym ją zdefiniować zamiast rozkładać na kilka klas. – pafcu

+0

Tak, a kod łamie się w interesujący sposób, jeśli ktoś nie zdaje sobie sprawy, że porządek w klasach ma znaczenie (zwykle nie). Łatwo jest przypadkowo zmienić kolejność, np. refaktoryzacja lub oczyszczenie kodu. – pafcu

+0

Nie widziałem problemu, w którym ** powinien ** mieć znaczenie. Być może powinieneś pomyśleć o a) być bardziej konkretnym w generowaniu i utrzymywaniu porządku lub b) nie zależnie od porządku w ogóle. Dlaczego miałoby to mieć znaczenie? – knitti

11

Napisz dekorator, który dodaje go do rejestru w Base.

class Base(object): 
    subs = [] 

    @classmethod 
    def addsub(cls, scls): 
    cls.subs.append(scls) 

... 

@Base.addsub 
class Sub1(Base): 
    pass 

class Sub2(Base): 
    pass 

@Base.addsub 
class Sub3(Base): 
    pass 
+0

Niestety, zapomniałem podać, że zamówienie jest ważne (jest to lista "preferowanych" typów). Posiadanie tego zależy od kolejności kodu jest dość niebezpieczne. Sądzę, że mógłbym dodać parametr do addsub dla tego. Nadal jednak istnieje problem z zawartością Base.subs jest zdefiniowany w każdym miejscu. – pafcu

0
class Foo: 
    pass 

class Bar: 
    pass 

Foo.m = Bar() 
Bar.m = Foo() 
+0

To było wykluczone w pytaniu. Definicja Foo jest podzielona. – Oddthinking

+0

Nie, nie to ... –

1

Chciałbym tylko określenie podklasy strun i mają nieuniknione dekorator zastąpić ciągi z klas, które nazwać je. Zdefiniowałbym również dekorator na metaclassie, ponieważ uważam, że jest to bardziej zgodne z celem: modyfikujemy zachowania klasowe i tak samo, jak modyfikuje się zachowanie obiektu, modyfikując jego klasę, modyfikując zachowanie klasy, modyfikując jego metaklass.

class BaseMeta(type): 

    def register(cls, subcls): 
     try: 
      i = cls.subs.index(subcls.__name__) 
     except ValueError: 
      pass 
     else: 
      cls.subs[i] = subcls 
     finally: 
      return cls 


class Base(object): 
    __metaclass__ = BaseMeta 
    subs = ['Sub3', 'Sub1'] 

@Base.register 
class Sub1(Base): pass 

@Base.register 
class Sub2(Base): pass 

@Base.register 
class Sub3(Base): pass 

print Base.subs 

This wyjścia:

[<class '__main__.Sub3'>, <class '__main__.Sub1'>] 
+0

To wydaje się bardzo ciekawe podejście, ponieważ mogę określić elementy base.subs w jednym miejscu. W razie potrzeby znacznie łatwiej jest je zmienić. – pafcu

+0

Jaki jest sens używania metaklasu tutaj? – martineau

+0

@martineau, Chodzi o to, że zmieniamy zachowanie klasy. Dokonanie tego poprzez zmianę samej klasy jest w istocie małpowaniem łatającym obiekt. Powodem zmiany klasy jest zmiana zachowania jej instancji - nie zmiana własnego zachowania. Robisz to zmieniając klasę, której _it_ jest instancją, a mianowicie jej metaclass. Nie programujemy już w C++, klasy też są obiektami :) Również usunąłem swój snarky komentarz do twojej odpowiedzi. Właściwie nie widziałem komentarzy skierowanych do mnie z jakiegoś powodu. – aaronasterling

3

Jestem prawie pewien, że to powinno zadziałać. Po prostu przypisz atrybut klasy zależnej. Jest to również znacznie mniej skomplikowane.

class Base:pass 

class Sub1(Base): pass 
class Sub2(Base): pass 
class Sub3(Base): pass 

Base.subs = [Sub3,Sub1] 
print(Sub1.subs) 
#[<class __main__.Sub3 at 0x0282B2D0>, <class __main__.Sub1 at 0x01C79810>] 
+0

W PO szczegółowo wspomniano, dlaczego szukał innego sposobu, aby to zrobić. – martineau

+0

Komplikowanie rzeczy niepotrzebnie, bez ważnego powodu, jest najbardziej anty pytonowym zachowaniem, jakie mogę wymyślić :-) –

3

Nie ma sposobu, aby bezpośrednio deklarują forward-referencje w Pythonie, ale istnieje kilka obejścia, z których niektóre są uzasadnione:

1) dodać podklasy ręcznie po są zdefiniowane.

- Pros: easy to do; Base.subs is updated in one place 
    - Cons: easy to forget (plus you don't want to do it this way) 

przykład:

class Base(object): 
    pass 

class Sub1(Base): 
    pass 

class Sub2(Base): 
    pass 

class Sub3(Base): 
    pass 

Base.subs = [sub3, sub1] 

2) Tworzenie Base.subs z str wartości i użyć class decorator zastąpić rzeczywiste podklasy (może to być metoda klasy na bazie lub funkcję - I "pokazuję wersję funkcji, chociaż prawdopodobnie użyłbym tej wersji metody).

- Pros: easy to do 
- Cons: somewhat easy to forget; 

przykład:

def register_with_Base(cls): 
    name = cls.__name__ 
    index = Base.subs.index(name) 
    Base.subs[index] = cls 
    return cls 

class Base(object): 
    subs = ['Sub3', 'Sub1'] 

@register_with_Base 
class Sub1(Base): 
    pass 

class Sub2(Base): 
    pass 

@register_with_Base 
class Sub3(Base): 
    pass 

) Tworzenie Base.subs z str wartości, i sposobu, który wykorzystuje Base.subs wykonać zmianę.

- Pros: no extra work in decorators, no forgetting to update `subs` later 
- Cons: small amount of extra work when accessing `subs` 

Przykład:

class Base(object): 
    subs = ['Sub3', 'Sub1'] 
    def select_sub(self, criteria): 
     for sub in self.subs: 
      sub = globals()[sub] 
      if #sub matches criteria#: 
       break 
     else: 
      # use a default, raise an exception, whatever 
     # use sub, which is the class defined below 

class Sub1(Base): 
    pass 

class Sub2(Base): 
    pass 

class Sub3(Base): 
    pass 

użyłbym opcja sam, jak zachowuje funkcjonalność i dane w jednym miejscu. Jedyne, co musisz zrobić, to zachować aktualność (i oczywiście napisać odpowiednie podklasy).