Czy jest korzyść z definiowania klasy wewnątrz innej klasy w Pythonie?


106

Mówię tutaj o klasach zagnieżdżonych. Zasadniczo mam dwie klasy, które modeluję. Klasa DownloadManager i klasa DownloadThread. Oczywistą koncepcją OOP jest tutaj kompozycja. Jednak kompozycja niekoniecznie oznacza zagnieżdżanie, prawda?

Mam kod, który wygląda mniej więcej tak:

class DownloadThread:
    def foo(self):
        pass

class DownloadManager():
    def __init__(self):
        dwld_threads = []
    def create_new_thread():
        dwld_threads.append(DownloadThread())

Ale teraz zastanawiam się, czy jest sytuacja, w której zagnieżdżanie byłoby lepsze. Coś jak:

class DownloadManager():
    class DownloadThread:
        def foo(self):
            pass
    def __init__(self):
        dwld_threads = []
    def create_new_thread():
        dwld_threads.append(DownloadManager.DownloadThread())

Odpowiedzi:


122

Możesz to zrobić, gdy klasa „wewnętrzna” jest jednorazowa, która nigdy nie będzie używana poza definicją klasy zewnętrznej. Na przykład, aby użyć metaklasy, czasami jest to przydatne

class Foo(object):
    class __metaclass__(type):
        .... 

zamiast oddzielnie definiować metaklasę, jeśli używasz jej tylko raz.

Jedynym innym razem, gdy użyłem takich klas zagnieżdżonych, użyłem klasy zewnętrznej tylko jako przestrzeni nazw do zgrupowania razem kilku blisko powiązanych klas:

class Group(object):
    class cls1(object):
       ...

    class cls2(object):
       ...

Następnie z innego modułu możesz zaimportować Group i odnieść się do nich jako Group.cls1, Group.cls2 itp. Można jednak argumentować, że możesz osiągnąć dokładnie to samo (być może w mniej zagmatwany sposób), używając modułu.


13
-Mógłbym- argumentować, że w drugim przypadku, zdecydowanie lepiej byłoby, gdybyś korzystał z modułu lub pakietu, które są zaprojektowane jako przestrzenie nazw.
Matthew Trevor,

5
To fantastyczna odpowiedź. Prosty, na temat i wspomina o alternatywnym sposobie korzystania z modułów. +1
CornSmith

5
Dla przypomnienia, dla tych, którzy uparcie odmawiają lub nie mogą zorganizować swojego kodu w hierarchiczny pakiet i odpowiednie moduły, użycie klas jako przestrzeni nazw może być czasem lepsze niż nieużywanie czegokolwiek.
Acumenus,

19

Nie znam Pythona, ale twoje pytanie wydaje się bardzo ogólne. Zignoruj ​​mnie, jeśli jest specyficzny dla Pythona.

Zagnieżdżanie klas dotyczy zakresu. Jeśli uważasz, że jedna klasa będzie miała sens tylko w kontekście innej, to ta pierwsza jest prawdopodobnie dobrym kandydatem na klasę zagnieżdżoną.

Jest to powszechny wzorzec tworzenia klas pomocniczych jako prywatnych, zagnieżdżonych klas.


9
Dla przypomnienia, koncepcja privateklas nie ma tak dużego zastosowania w Pythonie, ale domniemany zakres użycia nadal ma zastosowanie.
Acumenus,

7

Istnieje inne zastosowanie klasy zagnieżdżonej, gdy chce się skonstruować klasy dziedziczone, których rozszerzone funkcje są zawarte w określonej klasie zagnieżdżonej.

Zobacz ten przykład:

class foo:

  class bar:
    ...  # functionalities of a specific sub-feature of foo

  def __init__(self):
    self.a = self.bar()
    ...

  ...  # other features of foo


class foo2(foo):

  class bar(foo.bar):
    ... # enhanced functionalities for this specific feature

  def __init__(self):
    foo.__init__(self)

Zauważ, że w konstruktorze programu foo, linia self.a = self.bar()skonstruuje a, foo.bargdy konstruowany obiekt jest w rzeczywistości fooobiektem, i foo2.barobiektem, gdy konstruowany obiekt jest w rzeczywistości foo2obiektem.

Gdyby zamiast tego klasa barzostała zdefiniowana poza klasą foo, podobnie jak jej odziedziczona wersja (którą można by nazwać bar2na przykład), to zdefiniowanie nowej klasy foo2byłoby znacznie bardziej bolesne, ponieważ konstruktor klasy musiałby foo2mieć pierwszą linię zastąpioną przez self.a = bar2(), co oznacza ponowne napisanie całego konstruktora.


6

Naprawdę nie ma z tego żadnych korzyści, chyba że masz do czynienia z metaklasami.

klasa: zestaw naprawdę nie jest tym, za co myślisz. To dziwny zakres i robi dziwne rzeczy. To naprawdę nie daje klasy! To po prostu sposób na zebranie pewnych zmiennych - nazwy klasy, baz, małego słownika atrybutów i metaklasy.

Nazwa, słownik i bazy są przekazywane do funkcji, która jest metaklasą, a następnie przypisywane do zmiennej „nazwa” w zakresie, w którym znajdowała się klasa: zestaw.

To, co można uzyskać, mieszając metaklasy, a nawet zagnieżdżając klasy w klasach standardowych, jest trudniejsze do odczytania, trudniejsze do zrozumienia kodu i dziwnych błędów, które są strasznie trudne do zrozumienia bez dokładnego zrozumienia, dlaczego `` klasa '' zakres jest zupełnie inny niż każdy inny zakres Pythona.


3
Nie zgadzam się: Zagnieżdżanie klas wyjątków jest bardzo przydatne, ponieważ użytkownicy Twojej klasy mogą sprawdzać niestandardowe wyjątki bez konieczności wykonywania bałaganu w imporcie. Np.except myInstanceObj.CustomError, e:
RobM

2
@Jerub, dlaczego to jest złe?
Robert Siemer

5

Możesz używać klasy jako generatora klas. Jak (w niektórych kodach poza mankietem :)

class gen(object):
    class base_1(object): pass
    ...
    class base_n(object): pass

    def __init__(self, ...):
        ...
    def mk_cls(self, ..., type):
        '''makes a class based on the type passed in, the current state of
           the class, and the other inputs to the method'''

Czuję, że kiedy będziesz potrzebować tej funkcjonalności, będzie to dla Ciebie bardzo jasne. Jeśli nie musisz robić czegoś podobnego, prawdopodobnie nie jest to dobry przypadek użycia.


1

Nie, kompozycja nie oznacza zagnieżdżania. Zagnieżdżona klasa miałaby sens, jeśli chcesz ją bardziej ukryć w przestrzeni nazw klasy zewnętrznej.

W każdym razie nie widzę praktycznego zastosowania do zagnieżdżania w twoim przypadku. Uczyniłoby to kod trudniejszym do odczytania (zrozumienia), a także zwiększyłoby wcięcie, co spowodowałoby, że wiersze byłyby krótsze i bardziej podatne na pękanie.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.