Есть ли вариант использования для вложенных классов? - PullRequest
3 голосов
/ 19 декабря 2010

Я недавно видел, как несколько человек делали такие вещи здесь, в Stackoverflow:

class A:
    foo = 1

    class B:
        def blah(self):
            pass

Другими словами, у них есть вложенные классы. Это работает (хотя люди, плохо знакомые с Python, похоже, сталкиваются с проблемами, потому что они не ведут себя так, как они думали, что это будет), но я не могу думать о какой-либо причине делать это вообще на любом языке, и уж точно не на Python. Есть ли такой вариант использования? Почему люди делают это? В поисках этого кажется, что это достаточно распространено в C ++, есть ли веская причина?

Ответы [ 8 ]

6 голосов
/ 19 декабря 2010

Основная причина размещения одного класса в другом состоит в том, чтобы не загрязнять глобальное пространство имен вещами, которые используются только внутри одного класса и, следовательно, не принадлежат глобальному пространству имен. Это применимо даже к Python, где глобальное пространство имен является пространством имен конкретного модуля. Например, если у вас есть SomeClass и OtherClass, и им обоим нужно что-то читать специализированным способом, лучше иметь SomeClass.Reader и OtherClass.Reader, а не SomeClassReader и OtherClassReader.

Впрочем, я никогда не сталкивался с этим в C ++. Может быть проблематично контролировать доступ к полям внешнего класса из вложенного класса. И также довольно часто иметь только один публичный класс в модуле компиляции, определенный в заголовочном файле, и некоторые служебные классы, определенные в файле CPP (отличным примером этого является библиотека Qt). Таким образом, они не видны «посторонним», что хорошо, поэтому нет смысла включать их в заголовок. Это также помогает увеличить бинарную совместимость, что в противном случае является проблемой для поддержания. Ну, в любом случае это боль, но намного меньше.

Отличным примером языка, где вложенные классы действительно полезны, является Java. Вложенные классы там автоматически имеют указатель на экземпляр внешнего класса, который их создает (если только вы не объявите внутренний класс как статический). Таким образом, вам не нужно передавать «внешний» их конструкторам, и вы можете обращаться к полям внешнего класса только по их именам.

4 голосов
/ 19 декабря 2010

Позволяет вам контролировать доступ к вложенному классу - например, он часто используется для классов детализации реализации.В C ++ он также имеет преимущества с точки зрения анализа различных вещей и доступа к ним без предварительного объявления.

3 голосов
/ 19 декабря 2010

Я не большой поклонник python, но для меня этот тип решений скорее семантический, чем синтаксический.Если вы реализуете список, класс Node внутри List не сам по себе класс, предназначенный для использования где-либо, а деталь реализации списка.В то же время вы можете иметь Node внутренний класс внутри Tree или Graph.Разрешить ли вам доступ к классу компилятор / интерпретатор - это совсем другое.Программирование - это написание спецификаций, которым может следовать компьютер, и другие программисты могут читать, List.Node более явно в том смысле, что Node является внутренним для List, чем ListNode в качестве класса первого уровня.

2 голосов
/ 19 декабря 2010

Python позволяет вам многое делать с функциями (включая лямбды), для которых в C ++ 03 или Java вам нужен класс (хотя в Java есть анонимные внутренние классы, поэтому вложенный класс не всегда выглядит как ваш пример).Слушатели, посетители, такие вещи.Понимание списка - это своего рода посетитель:

Python:

(foo(x) if x.f == target else bar(x) for x in bazes)

C ++:

struct FooBar {
    Sommat operator()(const Baz &x) const {
        return (x.f == val) ? foo(x) : bar(x);
    }
    FooBar(int val) : val(val) {}
    int val;
};

vector<Sommat> v(bazes.size());
std::transform(bazes.begin(), bazes.end(), v.begin(), FooBar(target));

Вопрос, который затем задают себе программисты на C ++ и Java:«этот маленький класс, который я пишу: должен ли он появляться в той же области видимости, что и большой класс, которому нужно его использовать, или я должен ограничить его областью действия только того класса, который его использует?» [*]

Поскольку вы не хотите публиковать эту вещь или позволять кому-либо полагаться на нее, часто ответом в этих случаях является вложенный класс.В Java могут использоваться частные классы, а в C ++ вы можете ограничить классы TU, и в этом случае вам больше не нужно заботиться о том, в какой области имен происходит имя, поэтому вложенные классы фактически не требуются.Это просто стиль, плюс Java предоставляет некоторый синтаксический сахар.

Как сказал кто-то еще, другой случай - итераторы в C ++.Python может поддерживать итерацию без класса итератора, но если вы пишете структуру данных на C ++ или Java, то вам нужно где-то поместить ошибки.Чтобы следовать стандартному интерфейсу контейнера библиотеки, вы будете иметь вложенную typedef, независимо от того, является ли класс вложенным или нет, поэтому вполне естественно думать, «вложенный класс».

[*] Они также спрашивают себя,«Должен ли я просто написать цикл for?», но давайте предположим случай, когда ответом на него будет нет ...

2 голосов
/ 19 декабря 2010

В некоторых языках вложенный класс будет иметь доступ к переменным, которые находятся в области видимости во внешнем классе.(Аналогично с функциями или с вложенностью класса в функции. Конечно, вложенность функции в классе просто создает метод, который не вызывает удивления.;))

В более технических терминах мы создаем закрытие .

1 голос
/ 14 января 2011

«Вложенные классы» могут означать две разные вещи, которые можно намеренно разделить на три разные категории. Первый из них чисто стилистический, два других используются в практических целях и сильно зависят от языка функций, в котором они используются.

  1. Вложенные определения классов для создания нового пространства имен и / или лучшей организации вашего кода. Например, в Java это достигается за счет использования статических вложенных классов, и это предлагается официальной документацией как способ создания более удобочитаемого и поддерживаемого кода и логической группировки занятия вместе. Однако Zen of Python предлагает меньше вкладывать блоки кода, что препятствует такой практике.

    import this

    В Python вы бы чаще видели классы, сгруппированные по модулям.

  2. Помещение класса в другой класс как часть его интерфейса (или интерфейса экземпляров). Во-первых, этот интерфейс может использоваться реализацией для помощи в создании подклассов, например, представьте вложенный класс HTML.Node, который можно переопределить в подклассе HTML, чтобы изменить класс, используемый для создания новых экземпляров узла. Во-вторых, этот интерфейс может использоваться пользователями класса / экземпляра, хотя это не так полезно, если вы не находитесь в третьем случае, описанном ниже.

    По крайней мере, в Python вам не нужно вкладывать определения, чтобы выполнить какое-либо из них, и это, вероятно, очень редко. Вместо этого вы можете увидеть Node, определенный вне класса, а затем node_factory = Node в определении класса (или метод, предназначенный для создания узлов).

  3. Вложение пространства имен объектов или создание разных контекстов для разных групп объектов. В Java нестатические вложенные классы (называемые внутренние классы) связаны с экземпляром внешнего класса. Это очень полезно, потому что позволяет иметь экземпляры внутреннего класса, которые живут в разных внешних пространствах имен.

    Для Python рассмотрим модуль decimal. Вы можете создавать разные контексты и иметь такие вещи, как разные точности, определенные для каждого контекста. Каждому Decimal объекту может быть назначен контекст при создании. Это достигается так же, как внутренний класс, с помощью другого механизма. Если бы Python поддерживал внутренние классы, а Context и Decimal были вложенными, у вас было бы context.Decimal('3') вместо Decimal('3', context=context).

    Вы можете легко создать метакласс в Python, который позволяет создавать вложенные классы, которые живут внутри экземпляра, вы даже можете заставить его создавать правильные связанные и несвязанные прокси классов, которые правильно поддерживают isinstance, с помощью __subclasscheck__ и __instancecheck__. Тем не менее, это не даст вам ничего по сравнению с другими более простыми способами добиться того же (например, дополнительный аргумент __init__). Это только ограничит то, что вы можете с ним делать, и я обнаружил, что внутренние классы в Java очень запутаны каждый раз, когда мне приходилось их использовать.

1 голос
/ 19 декабря 2010

По крайней мере, в C ++ одним из основных распространенных вариантов использования вложенных классов являются итераторы в контейнерах.Например, гипотетическая реализация может выглядеть примерно так:

class list
{
   public:

   class iterator 
   {
      // implementation code
   };

   class const_iterator
   {
      // implementation code
   };
};

Другой причиной для вложенных классов в C ++ могут быть частные детали реализации, такие как классы узлов для карт, связанные списки и т. Д.

0 голосов
/ 19 декабря 2010

В Python более полезным шаблоном является объявление класса внутри функции или метода.Объявление класса в теле другого класса, как отмечали люди в других ответах, бесполезно - да, оно предотвращает загрязнение пространства имен модуля, но поскольку там вообще есть пространство имен модуля, еще несколько имен на немне беспокойся.Даже если дополнительные классы не предназначены для непосредственного создания пользователями модуля, установка потом в корень модуля сделает их документацию более доступной для других.

Однако класс внутри функции - это совершенно другойсущество: оно «объявляется» и создается каждый раз, когда запускается код, содержащий тело класса.Это дает возможность создавать динамические классы для различного использования - очень простым способом.Например, каждый класс, созданный таким образом, находится в отдельном закрытии и может иметь доступ к различным экземплярам переменных в содержащей функции.

def call_count(func):
    class Counter(object):
       def __init__(self):
           self.counter = 0
       def __repr__(self):
           return str(func)
       def __call__(self, *args, **kw):
           self.counter += 1
           return func(*args, **kw)
    return Counter()

И использовать его на консоли:

>>> @call_count
... def noop(): pass
...
>>> noop()
>>> noop()
>>> noop.counter
2
>>> noop
<function noop at 0x7fc251b0b578>

Таким образом, простой декоратор call_counter может использовать статический класс "Counter", определенный вне функции и получающий func в качестве параметра для своего конструктора - но если вы хотите настроить другие поведения, как в этом примере, сделать repr(func) возвращает представление функции, а не представление класса, так проще сделать..

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...