Есть ли преимущество в определении класса внутри другого класса в Python?


108

Я говорю о вложенных классах. По сути, у меня есть два класса, которые я моделирую. Класс DownloadManager и класс DownloadThread. Очевидная концепция ООП здесь - композиция. Однако композиция не обязательно означает вложенность, верно?

У меня есть код, который выглядит примерно так:

class DownloadThread:
    def foo(self):
        pass

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

Но теперь мне интересно, есть ли ситуация, когда вложение было бы лучше. Что-то вроде:

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

Ответы:


125

Возможно, вы захотите сделать это, когда «внутренний» класс является одноразовым, который никогда не будет использоваться вне определения внешнего класса. Например, чтобы использовать метакласс, иногда бывает удобно сделать

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

вместо того, чтобы определять метакласс отдельно, если вы используете его только один раз.

Единственный раз, когда я использовал подобные вложенные классы, я использовал внешний класс только как пространство имен, чтобы сгруппировать кучу тесно связанных классов вместе:

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

    class cls2(object):
       ...

Затем из другого модуля вы можете импортировать Group и ссылаться на них как на Group.cls1, Group.cls2 и т. Д. Однако кто-то может возразить, что вы можете сделать то же самое (возможно, менее запутанным способом), используя модуль.


13
Я бы сказал, что во втором случае вам - определенно - лучше было бы использовать модуль или пакет, которые созданы как пространства имен.
Мэтью Тревор,

5
Это фантастический ответ. Просто, по существу, и упоминается альтернативный метод использования модулей. +1
CornSmith

5
Просто для записи, для тех , кто упорно не хочет или не может организовать свой код в иерархический пакет и соответствующие модули, используя классы как пространство имен могут иногда быть лучше не использовать что - либо.
Acumenus

20

Я не знаю Python, но ваш вопрос кажется очень общим. Не обращайте на меня внимания, если это относится к Python.

Вложенность классов зависит от области видимости. Если вы думаете, что один класс будет иметь смысл только в контексте другого, то первый, вероятно, является хорошим кандидатом на роль вложенного класса.

Это распространенный шаблон, когда вспомогательные классы превращаются в частные вложенные классы.


9
Отметим, что концепция privateклассов не так уж и применима в Python, но подразумеваемая область использования определенно применима.
Acumenus

7

Есть еще одно использование вложенного класса, когда нужно создать унаследованные классы, расширенные функции которых инкапсулированы в конкретный вложенный класс.

См. Этот пример:

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)

Обратите внимание, что в конструкторе fooстрока self.a = self.bar()будет создавать, foo.barкогда конструируемый объект фактически является fooобъектом, и foo2.barобъект, когда конструируемый foo2объект фактически является объектом.

Если вместо этого класс barбыл определен вне класса foo, а также его унаследованная версия (которая могла бы вызываться, bar2например), тогда определение нового класса foo2было бы намного более болезненным, потому что конструктору foo2нужно было бы заменить свою первую строку на self.a = bar2(), что подразумевает переписывание всего конструктора.


6

В этом нет никакой пользы, кроме случаев, когда вы имеете дело с метаклассами.

class: suite на самом деле не то, что вы думаете. Это странный прицел, и он делает странные вещи. Это действительно даже не класс! Это просто способ собрать некоторые переменные - имя класса, основы, небольшой словарь атрибутов и метакласс.

Имя, словарь и основы передаются функции, которая является метаклассом, а затем присваивается переменной name в области, в которой находился class: suite.

То, что вы можете получить, возясь с метаклассами и действительно вкладывая классы в стандартные классы, сложнее для чтения кода, сложнее для понимания кода и странных ошибок, которые ужасно трудно понять, не зная, почему этот `` класс '' Область видимости полностью отличается от любой другой области действия Python.


3
Не согласен: вложение классов исключений очень полезно, потому что пользователи вашего класса могут проверять ваши пользовательские исключения без необходимости выполнять беспорядочный импорт. Напримерexcept myInstanceObj.CustomError, e:
RobM

2
@Jerub, почему это плохо?
Роберт Симер

5

Вы можете использовать класс как генератор классов. Вроде (в некоторых код без манжеты :)

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'''

Я чувствую, что когда вам понадобится эта функция, вам будет очень понятно. Если вам не нужно делать что-то подобное, вероятно, это не лучший вариант использования.


1

Нет, композиция не означает вложенность. Было бы разумно иметь вложенный класс, если вы хотите больше скрыть его в пространстве имен внешнего класса.

Во всяком случае, практического смысла вложения в вашем случае не вижу. Это затруднило бы чтение (понимание) кода, а также увеличило бы отступ, что сделало бы строки короче и более склонными к расщеплению.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.