LINUX.ORG.RU

Определение публичного API в Python

 , , ,


1

2

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

  • Приватный = «я не предполагаю, что сторонние пользователи будут использовать эти интерфейсы, я не уверен в стабильности этого интерфейса, не хочу показывать его во вне и хочу зарезервировать за собой право менять интерфейс без предупреждения»
  • Публичный = «я предполагаю, что сторонние пользователи будут использовать эти интерфейсы, я буду стараться их не ломать, или, по крайней мере, ломать gracefully, например через deprecation warning-и, release note-ы или иные инструменты»

Теперь возникает технический и организационный вопрос, как выделить публичные интерфейсы, и тут есть варианты:

  1. Через соглашение об именовании приватных сущностей, например _my_variable или _MyClass. Плюсы:
  • Описано в PEP-8

Минусы:

  • По-умолчанию, я предполагаю свой класс «приватным» в контексте библиотеки, т.к. лучше сначала всё закрыть и запретить, и только потом разрешить/открыть малую часть. Это значит, что 90% моих классов, которые по сути приватны, будут начинаться с _? Код превратится в кашу!
  1. При помощи __all__ Плюсы:
  • Описано в PEP

Вопросы:

  • __all__ говорит «это можно импортировать», но не говорит, где импортировать - для внутреннего использования внутри библиотеки или для внешнего пользователем. Вариантом решения тут могло бы быть именование package, начиная с _, например:
mylib/__init__.py
mylib/_gears/__init__.py  # <-- тут __all__ = ['X']
mylib/mylib.py  # <-- тут from ._gears import X

и тогда код вида from mylib import Y будет явно публичным, но from mylib._gears import X - «ошибочным», т.к. мы явно залезаем в приватную часть либы.

Минусы/вопросы:

  • Так кто-то еще делает? Выглядит велосипедно немного?…
  1. Есть версия, что «The RealDefinition™ is that whatever we include in the docs is public, otherwise not.» Плюсы:
  • Просто и понятно

Минусы:

  • Требует документации
  • Недостаточно формально. Если с приватными пакетами можно явно сказать «у вас в импорте подчеркивание, сами виноваты», то тут надо «а где это описано в документации? вы уверены, что можете это импортировать? давайте читать доки…»

NOTE: другие языки или изменения языка программирования как способ решить задачу не рассматривается. Пожалуйста, не советуйте, этого! Это вопрос про Python.

  • 1 эквивалентно 3, за вычетом «требует документации». Будет нормальная дока - используй 3, не будет - 1. Можешь и то, и другое использовать.

  • Бери нормальный язык вместо пистона.

anonymous ()

Пиши на C, а публичный интерфейс экспортируй в питон

cobold ★★★★ ()

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

Ещё я иногда пишу «публичные» функции или классы-обёртски, которые выношу отдельно, их нужно трогать, остальное нет.

WitcherGeralt ★★ ()
Последнее исправление: WitcherGeralt (всего исправлений: 1)

Во-первых, не надо противопоставлять - __all__ и _* не взаимоисключающи.

Во-вторых, надо помнить что ни один из описанных способов не даёт никаких гарантий. Можно импортировать символ с подчёркиванием, а __all__ вообще влияет только на from foo import *.

Поэтому я бы ориентировался прежде всего на документацию в том или ином виде - если уж ты заботишься об однозначном выделении и стабильности публичного API, то прежде всего должен думать о ней, а не об __all__ и подчёркиваниях. В крайнем случае (а скорее даже, первым делом) можно на видном месте написать формальную полиси (например: «публичным API считается только то что перечислено в __all__ из mymodule/__init__.py, остальным пользоваться запрещено») - так ты чётко определишь границы дозволенного для пользователей, сможешь сразу слать лесом любителей залезать куда не надо и потом жаловаться и сам себе развяжешь руки потому что внутри проекта можешь вообще не заморачиваться с __all__ и подчёркиваниями.

А так, я конечно эти ваши PEP’ы не читал, но моё личное мнение - подчёркивания имеют смысл лишь внутри классов (protected члены) и отдельных файлов (символы не используются за пределами файла). Именовать сами файлы с _ я бы не стал ибо не понятно какой у них scope - вся библиотека или один уровень каталогов, тем более что символы могут реэкспортироваться многократно.

slovazap ★★★★★ ()
Ответ на: комментарий от omegatype

Использую flake8 и mypy, не замечал чтобы они что-то про него писали, при том что у меня много где __all__ либо отсутствует, либо не соответствует действительности (т.е. было про что написать). Возможно всё-таки они что-то пишут когда используется import *, чего я никогда не делаю, так что изначальный постулат «влияет только на from foo import *» верен.

Вообще про линтеры я забыл написать - кто-то из этих двух точно ругается на импортирование внешнего не реэкспортированного символа (т.е. который в исходном модуле импортируется как import foo, а не import foo as foo). Не знаю где это задокументировано, но похоже что это тоже вариант слабого скрытия символов. Т.е. не надо беспокоиться за то что утекут приватные импорты и, например, импортировать их с подчёркиванием уродуя код.

Учитывая это, получается что можно самодокументировано скрыть всё приватное API почти без костылей, примерно так:

  1. Всё что mylib.internal / mylib.private - внутреннее для библиотеки (при этом в именах лишние подчёркивания там не нужны)
  2. Всё что начинается с подчёркивания - внутреннее для своего класса или файла
  3. Всё что импортируется без as - внутреннее для файла

1 и 2 видно в коде, 3 энфорсится линтером.

slovazap ★★★★★ ()
Ответ на: комментарий от anonymous

контроллеры, веб, бэк, фронт, гуй.. ничё себе нишевый! ты что, ламер что ли?

anonymous ()
Ответ на: комментарий от anonymous

В случае с Go ты забудешь об этих проблемах и узнаешь о новых)

dimuska139 ★★ ()

При указанном контексте однозначно через наличие/отсутствие документации. Потому что понятие «не ломать» как правило трактуется как «не менять задекларированное поведение». А задекларировать его можно только в документации.

Недостаточно формально.

Утверждение «данная функция недокументирована» не менее однозначно, чем «данная функция начинается с подчёркивания».

monk ★★★★★ ()
Ответ на: комментарий от monk

А задекларировать его можно только в документации.

«Code is the doc (c)» в некотором смысле, нет?

«данная функция недокументирована» не менее однозначно, чем «данная функция начинается с подчёркивания».

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

omegatype ★★★ ()
Последнее исправление: omegatype (всего исправлений: 1)
Ответ на: комментарий от omegatype

«Code is the doc (c)» в некотором смысле, нет?

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

линтеры уже распознают и помогают со значительной частью доступа «не туда»

Линтеру никто не мешает и проверять наличие документации. В случае питона это просто вызов функции doc().

Ещё можно сразу документировать декоратором @public.

monk ★★★★★ ()
Ответ на: комментарий от omegatype

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

линтер - не первичен по сравнению с документацией.

подчёркивания в названиях переменных - это только условность.

питон - хреновый язык - факт

anonymous ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.