2013-08-30 3 views
13

Этот вопрос строится на this one asked earlier, а также this one, но адресован более тонкий момент, в частности, что считается «внутренним классом»?ведущий символ подчеркивания в именах классов Python

Вот моя ситуация. Я создаю библиотеку Python для управления файлами MS Office, в которых многие из классов не предназначены для создания извне, но многие из их методов являются важными частями API. Например:

class _Slide(object): 
    def add_shape(self): 
     ... 

_Slide не будет построен снаружи, в качестве конечного пользователя библиотеки вы получите новый слайд с помощью вызова Presentation.add_slide(). Однако, как только у вас есть слайд, вы обязательно захотите позвонить add_shape(). Таким образом, этот метод является частью API, но конструктор - нет. Эта ситуация возникает десятки раз в библиотеке, потому что только класс Presentation имеет внешний/API-конструктор.

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

Проблема в том, у меня есть только два обозначения различать по крайней мере три различные ситуации:

  1. Конструктор и «открытый/API» методы класса все доступны для использования.
  2. Конструктор не должен использоваться, но методы «public/API» этого класса.
  3. Класс действительно является внутренним, не имеет публичного API, и я оставляю за собой право изменять или удалять его в будущей версии.

Отметить, что с точки зрения коммуникации существует конфликт между явным выражением внешнего API (аудитория конечного пользователя библиотеки) и внутренним API (аудиторией разработчиков). Для конечного пользователя вызов _Slide() является verboten/at-own-risk. Однако он является счастливым членом внутреннего API и отличается от _random_helper_method(), который должен вызываться только с _Slide().

У вас есть точка зрения на этот вопрос, который может мне помочь? Является ли конвенция диктуем, что я использую свои боеприпасы «одного лидера-подчёркивания» в именах классов, чтобы бороться за ясность в API конечных пользователей, или я могу чувствовать себя хорошо о том, чтобы зарезервировать его для общения со мной и другими разработчиками о том, когда API класса действительно закрытый и не используемый, например, объектами вне модуля, в котором он живет, потому что это может быть изменена деталь реализации.


UPDATE: После нескольких лет дальнейших размышлений, я устроилась в конвенцию с использованием подчеркивания в именах классов, которые не предназначены для быть доступны как класс вне их модуля (файл). Такой доступ обычно заключается в создании экземпляра объекта этого класса или для доступа к методу класса и т. Д.

Это обеспечивает пользователей модуля (часто сами, разумеется :) базовый индикатор: «Если имя класса с лидирующим знаком подчеркивания появляется в заявлении на импорт, вы делаете что-то не так ». (Единичные тестовые модули являются исключением из этого правила, такой импорт часто может появляться в модульных тестах для «внутреннего» класса.)

Обратите внимание, что это доступ к классу .Доступ к объекту этого класса (типа) вне модуля, возможно, предоставлен фабрикой или любым другим, прекрасно подходит и, возможно, ожидается. Я думаю, что это неспособность отличить классы от объектов, созданных ими, привело к моей первоначальной путанице.

Это соглашение также имеет преимущество в том, чтобы не включать эти классы при составлении заявления from module import *. Хотя я никогда не использую их в своем коде и не рекомендую их избегать, это подходящее поведение, потому что эти идентификаторы классов не предназначены для того, чтобы быть частью интерфейса модуля.

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

+0

Мне потребовалось немного времени, чтобы решить, что это не должно считаться в основном основанным на мнениях. Однако формулировка может быть улучшена. – user2357112

+0

После вызова 'Presentation.add_slide()', как открывается доступ к слайду? Если с помощью '_Slide()' экземпляра, который пользователь имеет, '_Slide' является фактически публичным классом. – martineau

+0

@martineau: Я думаю, что это в значительной степени ответ. Вывод, который я пришел после того, как вы упомянули об этом, состоит в том, что класс является внутренним, если его часть (конструктор, методы) не используется * вне его модуля *. И что ведущее подчеркивание в именах классов должно использоваться для классов, соответствующих этому описанию. Если вы ответите, я приму это. – scanny

ответ

3

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

Это верно, несмотря на то, что экземпляры будут создаваться косвенно, вызвав метод add_slide()Presentation, поскольку вызывающий пользователь будет свободен (и, более вероятно, необходим), чтобы вызвать методы экземпляра для последующего управления им. На мой взгляд, действительно частный класс будет только получить доступ к методам класса, который «принадлежал» ему.

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

0

__all__ = ['Presentation'] в модуле скрывает все классы от таких функций, как help() кроме класса презентации. Но разработчики все еще имеют доступ к конструкторам вызовов и методам извне.

4

Я думаю, что ответ martineau - хороший, безусловно, самый простой и, без сомнения, самый pythonic.

Это, однако, ни в коем случае не единственный вариант.

Часто используемая техника заключается в определении публичных методов как части интерфейса; например, zope.interface.Interface широко используется в скрученной структуре. Современный питон, вероятно, будет использовать abc.ABCMeta для такого же эффекта. По существу, общедоступный метод Presentation.add_slide документируется как возвращающий некоторый экземпляр AbstractSlide, который имеет более общие методы; но так как невозможно создать экземпляр AbstractSlide напрямую, нет никакого общего способа его создания.

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

Другая опция; поскольку единственный общедоступный способ создания экземпляров класса слайдов - Presentation.add_slide; вы можете сделать так, чтобы буквально был конструктором. Такие, вероятно, потребуют некоторых метаклассов-шенанеганов, что-то вроде этого.

from functools import partial 

class InstanceConstructor(type): 
    def __get__(cls, instance, owner): 
     if instance is not None: 
      return partial(cls, instance) 
     return cls 

И просто определить add_slide поведение в __init__ класса:

>>> class Presentation(object): 
...  def __init__(self): 
...   self.slides = [] 
... 
...  class add_slide(object): 
...   __metaclass__ = InstanceConstructor 
... 
...   def __init__(slide, presentation, color): 
...    slide.color = color 
...    presentation.slides.append(slide) 
... 
>>> p = Presentation() 
>>> p.add_slide('red') 
<instance_ctor.add_slide object at ...> 
>>> p.slides 
[<instance_ctor.add_slide object at ...>] 
>>> p.slides[0].color 
'red' 
+0

Я думаю, что это лучший ответ. Мой единственный вопрос заключается в том, что я считаю, что использование 'self' для экземпляра презентации в' Presentation.add_slide .__ init__' является путаным. Я предлагаю либо разрешить 'self' быть слайдом (как в обычном, не вложенном классе), либо полностью отказаться от соглашения о себе, и перейти к' def __init __ (слайд, презентация, цвет) '. – Blckknght

+1

Спасибо за вашу поддержку, но мое второе предложение действительно больше развлечение. Я бы * не рекомендовал. Либо сделайте класс общедоступным, как в принятом ответе, или используйте абстрактный класс для публичного интерфейса. – SingleNegationElimination

1

Что такое «внутренний» класс условны, именно поэтому PEP не определяют но если вы подвергаете их своим пользователям (предоставляя методы, возвращающие этот класс, или полагайтесь на то, что этот класс прошел), это не совсем «внутреннее».

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

Другим было бы обернуть те аспекты этого класса, которые вы хотите открыть в классе, который создает и управляет этими экземплярами, другими словами делает это по-настоящему внутренним классом. Если вам нужно только разоблачить один метод (скажем, add_shape()), это может быть очень разумным. Если вы хотите, чтобы большая часть интерфейса для этого класса была выставлена, тем не менее, это начинает казаться неуклюжим.

Наконец, вы можете просто четко документировать, что пользователям не следует создавать экземпляры этого класса, но иметь доступ к частям интерфейса. Это сбивает с толку, но может быть вариантом.

Мне нравится первый выбор: я сам представляю класс для пользователя вашего модуля и использую его конструктор и реализацию деструктора, чтобы убедиться, что он всегда правильно связан с другими классами, которые должны знать об этом. Затем пользователи могут делать такие вещи, как подкласс, эту вещь и методы обертывания, которые они хотели бы вызвать в любом случае, и убедитесь, что ваши объекты остаются на связи.

Смежные вопросы