КАК ДЕЛАТЬ МОДУЛИ

Работа с модулями: создание, подключение инструкциями import и from

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

Каждая программа может импортировать модуль и получить доступ к его классам, функциям и объектам. Нужно заметить, что модуль может быть написан не только на Python, а например, на C или C++.

Подключение модуля из стандартной библиотеки

Подключить модуль можно с помощью инструкции import. К примеру, подключим модуль os для получения текущей директории:

После ключевого слова import указывается название модуля. Одной инструкцией можно подключить несколько модулей, хотя этого не рекомендуется делать, так как это снижает читаемость кода. Импортируем модули time и random.

После импортирования модуля его название становится переменной, через которую можно получить доступ к атрибутам модуля. Например, можно обратиться к константе e, расположенной в модуле math:

Стоит отметить, что если указанный атрибут модуля не будет найден, возбудится исключение AttributeError. А если не удастся найти модуль для импортирования, то ImportError.

Использование псевдонимов

Если название модуля слишком длинное, или оно вам не нравится по каким-то другим причинам, то для него можно создать псевдоним, с помощью ключевого слова as.

Теперь доступ ко всем атрибутам модуля math осуществляется только с помощью переменной m, а переменной math в этой программе уже не будет (если, конечно, вы после этого не напишете import math, тогда модуль будет доступен как под именем m, так и под именем math).

Инструкция from

Подключить определенные атрибуты модуля можно с помощью инструкции from. Она имеет несколько форматов:

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

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

Второй формат инструкции from позволяет подключить все (точнее, почти все) переменные из модуля. Для примера импортируем все атрибуты из модуля sys:

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

Создание своего модуля на Python

Теперь пришло время создать свой модуль. Создадим файл mymodule.py, в которой определим какие-нибудь функции:

Теперь в этой же папке создадим другой файл, например, main.py:

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

Как назвать модуль?

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

Как решать уравнения с модулем: основные правила

Модуль — одна из тех вещей, о которых вроде-бы все слышали, но в действительности никто нормально не понимает. Поэтому сегодня будет большой урок, посвящённый решению уравнений с модулями.

Сразу скажу: урок будет несложный. И вообще модули — вообще тема относительно несложная. «Да конечно, несложная! У меня от неё мозг разрывается!» — скажут многие ученики, но все эти разрывы мозга происходят из-за того, что у большинства людей в голове не знания, а какая-то хрень. И цель этого урока — превратить хрень в знания.:)

Немного теории

Итак, поехали. Начнём с самого важного: что такое модуль? Напомню, что модуль числа — это просто то же самое число, но взятое без знака «минус». Т.е., например, $\left| -5 \right|=5$. Или $\left| -129,5 \right|=129,5$.

Вот так всё просто? Да, просто. А чему тогда равен модуль положительного числа? Тут ещё проще: модуль положительного числа равен самому этому числу: $\left| 5 \right|=5$; $\left| 129,5 \right|=129,5$ и т.д.

Получается любопытная вещь: разные числа могут иметь один тот же модуль. Например: $\left| -5 \right|=\left| 5 \right|=5$; $\left| -129,5 \right|=\left| 129,5 \right|=129,5$. Нетрудно заметить, что это за числа, у которых модули одинаковые: эти числа противоположны. Таким образом, отметим для себя, что модули противоположных чисел равны:

\[\left| -a \right|=\left| a \right|\]

Ещё один важный факт: модуль никогда не бывает отрицательным. Какое бы число мы ни взяли — хоть положительное, хоть отрицательное — его модуль всегда оказывается положительным (или в крайнем случае нулём). Именно поэтому модуль часто называют абсолютной величиной числа.

Кроме того, если объединить определение модуля для положительного и отрицательного числа, то получим глобальное определение модуля для всех чисел. А именно: модуль числа равен самому этому числу, если число положительное (или ноль), либо равен противоположному числу, если число отрицательное. Можно записать это в виде формулы:

Ещё есть модуль нуля, но он всегда равен нулю. Кроме того, ноль — единственное число, которое не имеет противоположного.

Таким образом, если рассмотреть функцию $y=\left| x \right|$ и попробовать нарисовать её график, то получится вот такая «галка»:

График модуля и пример решения уравнения

Из этой картинки сразу видно, что $\left| -m \right|=\left| m \right|$, а график модуля никогда не опускается ниже оси абсцисс. Но это ещё не всё: красной линией отмечена прямая $y=a$, которая при положительных $a$ даёт нам сразу два корня: $<_<1>>$ и $<_<2>>$, но об этом мы поговорим позже.:)

Помимо чисто алгебраического определения, есть геометрическое. Допустим, есть две точки на числовой прямой: $<_<1>>$ и $<_<2>>$. В этом случае выражение $\left| <_<1>>-<_<2>> \right|$ — это просто расстояние между указанными точками. Или, если угодно, длина отрезка, соединяющего эти точки:

Модуль — это расстояние между точками на числовой прямой

Из этого определения также следует, что модуль всегда неотрицателен. Но хватит определений и теории — перейдём к настоящим уравнениям.:)

Основная формула

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

Спокойствие, только спокойствие. Начнём с самых простых вещей. Рассмотрим что-нибудь типа такого:

Итак, модуль$x$ равен 3. Чему может быть равен $x$? Ну, судя по определению, нас вполне устроит $x=3$. Действительно:

А есть ли другие числа? Кэп как бы намекает, что есть. Например, $x=-3$ — для него тоже $\left| -3 \right|=3$, т.е. требуемое равенство выполняется.

Так может, если поискать, подумать, мы найдём ещё числа? А вот обломитесь: больше чисел нет. Уравнение $\left| x \right|=3$ имеет лишь два корня: $x=3$ и $x=-3$.

Теперь немного усложним задачу. Пусть вместо переменной $x$ под знаком модуля тусуется функция $f\left( x \right)$, а справа вместо тройки поставим произвольное число $a$. Получим уравнение:

\[\left| f\left( x \right) \right|=a\]

Ну и как такое решать? Напомню: $f\left( x \right)$ — произвольная функция, $a$ — любое число. Т.е. вообще любое! Например:

\[\left| 2x+1 \right|=5\]

\[\left| 10x-5 \right|=-65\]

Обратим внимание на второе уравнение. Про него сразу можно сказать: корней у него нет. Почему? Всё правильно: потому что в нём требуется, чтобы модуль был равен отрицательному числу, чего никогда не бывает, поскольку мы уже знаем, что модуль — число всегда положительное или в крайнем случае ноль.

А вот с первым уравнением всё веселее. Тут два варианта: либо под знаком модуля стоит положительное выражение, и тогда$\left| 2x+1 \right|=2x+1$, либо это выражение всё-таки отрицательное, и тогда $\left| 2x+1 \right|=-\left( 2x+1 \right)=-2x-1$. В первом случае наше уравнение перепишется так:

\[\left| 2x+1 \right|=5\Rightarrow 2x+1=5\]

И внезапно получается, что подмодульное выражение $2x+1$ действительно положительно — оно равно числу 5. Т.е. мы можем спокойно решать это уравнение — полученный корень будет кусочком ответа:

\[2x+1=5\Rightarrow 2x=4\Rightarrow x=2\]

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

Теперь разберём случай отрицательного подмодульного выражения:

\[\left\< \begin& \left| 2x+1 \right|=5 \\& 2x+1 \lt 0 \\\end \right.\Rightarrow -2x-1=5\Rightarrow 2x+1=-5\]

Опа! Снова всё чётко: мы предположили, что $2x+1 \lt 0$, и в результате получили, что $2x+1=-5$ — действительно, это выражение меньше нуля. Решаем полученное уравнение, при этом уже точно зная, что найденный корень нас устроит:

\[2x+1=-5\Rightarrow 2x=-6\Rightarrow x=-3\]

Итого мы вновь получили два ответа: $x=2$ и $x=3$. Да, объём вычислений оказался малость побольше, чем в совсем уж простом уравнении $\left| x \right|=3$, но принципиально ничего не изменилось. Так может, существует какой-то универсальный алгоритм?

Да, такой алгоритм существует. И сейчас мы его разберём.

Избавление от знака модуля

Пусть нам дано уравнение $\left| f\left( x \right) \right|=a$, причём $a\ge 0$ (иначе, как мы уже знаем, корней нет). Тогда можно избавиться от знака модуля по следующему правилу:

\[\left| f\left( x \right) \right|=a\Rightarrow f\left( x \right)=\pm a\]

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

\[\left| 5x+4 \right|=10\Rightarrow 5x+4=\pm 10\]

Отдельно рассмотрим, когда справа стоит десятка с плюсом, и отдельно — когда с минусом. Имеем:

\[\begin& 5x+4=10\Rightarrow 5x=6\Rightarrow x=\frac<6><5>=1,2; \\& 5x+4=-10\Rightarrow 5x=-14\Rightarrow x=-\frac<14><5>=-2,8. \\\end\]

Вот и всё! Получили два корня: $x=1,2$ и $x=-2,8$. Всё решение заняло буквально две строчки.

Ок, не вопрос, давайте рассмотрим что-нибудь чуть посерьёзнее:

\[\left| 7-5x \right|=13\]

Опять раскрываем модуль с плюсом и минусом:

\[\begin& 7-5x=13\Rightarrow -5x=6\Rightarrow x=-\frac<6><5>=-1,2; \\& 7-5x=-13\Rightarrow -5x=-20\Rightarrow x=4. \\\end\]

Опять пара строчек — и ответ готов! Как я и говорил, в модулях нет ничего сложного. Нужно лишь запомнить несколько правил. Поэтому идём дальше и приступаем с действительно более сложным задачам.

Случай переменной правой части

А теперь рассмотрим вот такое уравнение:

\[\left| 3x-2 \right|=2x\]

Это уравнение принципиально отличается от всех предыдущих. Чем? А тем, что справа от знака равенства стоит выражение $2x$ — и мы не можем заранее знать, положительное оно или отрицательное.

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

А во-вторых, если права часть всё-таки положительна (или равна нулю), то можно действовать точно так же, как раньше: просто раскрыть модуль отдельно со знаком «плюс» и отдельно — со знаком «минус».

Таким образом, сформулируем правило для произвольных функций $f\left( x \right)$ и $g\left( x \right)$ :

\[\left| f\left( x \right) \right|=g\left( x \right)\Rightarrow \left\< \begin& f\left( x \right)=\pm g\left( x \right), \\& g\left( x \right)\ge 0. \\\end \right.\]

Применительно к нашему уравнению получим:

\[\left| 3x-2 \right|=2x\Rightarrow \left\< \begin& 3x-2=\pm 2x, \\& 2x\ge 0. \\\end \right.\]

Ну, с требованием $2x\ge 0$ мы как-нибудь справимся. В конце концов, можно тупо подставить корни, которые мы получим из первого уравнения, и проверить: выполняется неравенство или нет.

Поэтому решим-ка само уравнение:

\[\begin& 3x-2=2\Rightarrow 3x=4\Rightarrow x=\frac<4><3>; \\& 3x-2=-2\Rightarrow 3x=0\Rightarrow x=0. \\\end\]

Ну и какой их этих двух корней удовлетворяет требованию $2x\ge 0$? Да оба! Поэтому в ответ пойдут два числа: $x=<4>/<3>\;$ и $x=0$. Вот и всё решение.:)

Подозреваю, что кто-то из учеников уже начал скучать? Что ж, рассмотрим ещё более сложное уравнение:

Хоть оно и выглядит злобно, по факту это всё то же самое уравнение вида «модуль равен функции»:

\[\left| f\left( x \right) \right|=g\left( x \right)\]

И решается оно точно так же:

С неравенством мы потом разберёмся — оно какое-то уж слишком злобное (на самом деле простое, но мы его решать не будем). Пока лучше займёмся полученными уравнениями. Рассмотрим первый случай — это когда модуль раскрывается со знаком «плюс»:

Ну, тут и ежу понятно, что нужно всё собрать слева, привести подобные и посмотреть, что получится. А получится вот что:

Выносим общий множитель $<^<2>>$ за скобку и получаем очень простое уравнение:

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

Теперь точно так же разберёмся со вторым уравнением, которое получается при раскрытии модуля со знаком «минус»:

Опять то же самое: произведение равно нулю, когда равен нулю хотя бы один из множителей. Имеем:

Ну вот мы получили три корня: $x=0$, $x=1,5$ и $x=<2>/<3>\;$. Ну и что из этого набора пойдёт в окончательный ответ? Для этого вспомним, что у нас есть дополнительное ограничение в виде неравенства:

Как учесть это требование? Да просто подставим найденные корни и проверим: выполняется неравенство при этих $x$ или нет. Имеем:

Таким образом, корень $x=1,5$ нас не устраивает. И в ответ пойдут лишь два корня:

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

Уравнения с двумя модулями

До сих пор мы изучали лишь самые простые уравнения — там был один модуль и что-то ещё. Это «что-то ещё» мы отправляли в другую часть неравенства, подальше от модуля, чтобы в итоге всё свелось к уравнению вида $\left| f\left( x \right) \right|=g\left( x \right)$ или даже более простому $\left| f\left( x \right) \right|=a$.

Но детский сад закончился — пора рассмотреть что-нибудь посерьёзнее. Начнём с уравнений вот такого типа:

\[\left| f\left( x \right) \right|=\left| g\left( x \right) \right|\]

Это уравнение вида «модуль равен модулю». Принципиально важным моментом является отсутствие других слагаемых и множителей: только один модуль слева, ещё один модуль справа — и ничего более.

Кто-нибудь сейчас подумает, что такие уравнения решаются сложнее, чем то, что мы изучали до сих пор. А вот и нет: эти уравнения решаются даже проще. Вот формула:

\[\left| f\left( x \right) \right|=\left| g\left( x \right) \right|\Rightarrow f\left( x \right)=\pm g\left( x \right)\]

Всё! Мы просто приравниваем подмодульные выражения, ставя перед одним из них знак «плюс-минус». А затем решаем полученные два уравнения — и корни готовы! Никаких дополнительных ограничений, никаких неравенств и т.д. Всё очень просто.

Давайте попробуем решать вот такую задачу:

\[\left| 2x+3 \right|=\left| 2x-7 \right|\]

Элементарно, Ватсон! Раскрываем модули:

\[\left| 2x+3 \right|=\left| 2x-7 \right|\Rightarrow 2x+3=\pm \left( 2x-7 \right)\]

Рассмотрим отдельно каждый случай:

\[\begin& 2x+3=2x-7\Rightarrow 3=-7\Rightarrow \emptyset ; \\& 2x+3=-\left( 2x-7 \right)\Rightarrow 2x+3=-2x+7. \\\end\]

В первом уравнении корней нет. Потому что когда это $3=-7$? При каких значениях $x$? «Какой ещё нафиг $x$? Ты обкурился? Там вообще нет $x$» — скажете вы. И будете правы. Мы получили равенство, не зависящее от переменной $x$, и при этом само равенство — неверное. Потому и нет корней.:)

Со вторым уравнением всё чуть интереснее, но тоже очень и очень просто:

\[2x+3=-2x+7\Rightarrow 4x=4\Rightarrow x=1\]

Как видим, всё решилось буквально в пару строчек — другого от линейного уравнения мы и не ожидали.:)

В итоге окончательный ответ: $x=1$.

Ну как? Сложно? Конечно, нет. Попробуем что-нибудь ещё:

Опять у нас уравнение вида $\left| f\left( x \right) \right|=\left| g\left( x \right) \right|$. Поэтому сразу переписываем его, раскрывая знак модуля:

Возможно, кто-то сейчас спросит: «Эй, что за бред? Почему «плюс-минус» стоит у правого выражения, а не у левого?» Спокойно, сейчас всё объясню. Действительно, по-хорошему мы должны были переписать наше уравнение следующим образом:

Затем нужно раскрыть скобки, перенести все слагаемые в одну сторону от знака равенства (поскольку уравнение, очевидно, в обоих случаях будет квадратным), ну и дальше отыскать корни. Но согласитесь: когда «плюс-минус» стоит перед тремя слагаемыми (особенно когда одно из этих слагаемых — квадратное выражение), это как-то более сложно выглядит, нежели ситуация, когда «плюс-минус» стоит лишь перед двумя слагаемыми.

Но ведь ничто не мешает нам переписать исходное уравнение следующим образом:

\[\left| x-1 \right|=\left| <^<2>>-3x+2 \right|\Rightarrow \left| <^<2>>-3x+2 \right|=\left| x-1 \right|\]

Что произошло? Да ничего особенного: просто поменяли левую и правую часть местами. Мелочь, которая в итоге немного упростит нам жизнь.:)

В общем, решаем это уравнение, рассматривая варианты с плюсом и с минусом:

Первое уравнение имеет корни $x=3$ и $x=1$. Второе вообще является точным квадратом:

Поэтому у него единственный корень: $x=1$. Но этот корень мы уже получали ранее. Таким образом, в итоговый ответ пойдут лишь два числа:

Миссия выполнена! Можно взять с полки и скушать пирожок. Там их 2, ваш средний.:)

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

\[\begin& \left| x-1 \right|=\left| <^<2>>-3x+2 \right|; \\& \left| x-1 \right|=\left| \left( x-1 \right)\left( x-2 \right) \right|. \\\end\]

Одно из свойств модуля: $\left| a\cdot b \right|=\left| a \right|\cdot \left| b \right|$ (т.е. модуль произведения равен произведению модулей), поэтому исходное уравнение можно переписать так:

\[\left| x-1 \right|=\left| x-1 \right|\cdot \left| x-2 \right|\]

Как видим, у нас действительно возник общий множитель. Теперь, если собрать все модули с одной стороны, то можно вынести этот множитель за скобку:

\[\begin& \left| x-1 \right|=\left| x-1 \right|\cdot \left| x-2 \right|; \\& \left| x-1 \right|-\left| x-1 \right|\cdot \left| x-2 \right|=0; \\& \left| x-1 \right|\cdot \left( 1-\left| x-2 \right| \right)=0. \\\end\]

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

\[\left[ \begin& \left| x-1 \right|=0, \\& \left| x-2 \right|=1. \\\end \right.\]

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

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

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

Тем не менее, это уравнение решается даже проще, чем то, что мы рассматривали ранее. И если вы поймёте почему, то получите ещё один приём для быстрого решения уравнений с модулями.

Нет, это не опечатка: между модулями именно плюс. И нам нужно найти, при каких $x$ сумма двух модулей равна нулю.:)

В чём вообще проблема? А проблема в том, что каждый модуль — число положительное, либо в крайнем случае ноль. А что будет, если сложить два положительных числа? Очевидно, снова положительное число:

Последняя строчка может натолкнуть на мысль: единственный случай, когда сумма модулей равна нулю — это если каждый модуль будет равен нулю:

А когда модуль равен нулю? Только в одном случае — когда подмодульное выражение равно нулю:

\[x-<^<3>>=0\Rightarrow x\left( 1-<^<2>> \right)=0\Rightarrow \left[ \begin& x=0 \\& x=\pm 1 \\\end \right.\]

\[<^<2>>+x-2=0\Rightarrow \left( x+2 \right)\left( x-1 \right)=0\Rightarrow \left[ \begin& x=-2 \\& x=1 \\\end \right.\]

Таким образом, у нас есть три точки, в которых обнуляется первый модуль: 0, 1 и −1; а также две точки, в которых обнуляется второй модуль: −2 и 1. Однако нам нужно, чтобы оба модуля обнулялись одновременно, поэтому среди найденных чисел нужно выбрать те, которые входят в оба набора. Очевидно, такое число лишь одно: $x=1$ — это и будет окончательным ответом.

Метод расщепления

Что ж, мы уже рассмотрели кучу задач и изучили множество приёмов. Думаете, на этом всё? А вот и нет! Сейчас мы рассмотрим заключительный приём — и одновременно самый важный. Речь пойдёт о расщеплении уравнений с модулем. О чём вообще пойдёт речь? Давайте вернёмся немного назад и рассмотрим какое-нибудь простое уравнение. Например, это:

\[\left| 3x-5 \right|=5-3x\]

В принципе, мы уже знаем, как решать такое уравнение, потому что это стандартная конструкция вида $\left| f\left( x \right) \right|=g\left( x \right)$. Но попробуем взглянуть на это уравнение немного под другим углом. Точнее, рассмотрим выражение, стоящее под знаком модуля. Напомню, что модуль любого числа может быть равен самому числу, а может быть противоположен этому числу:

Собственно, в этой неоднозначности и состоит вся проблема: поскольку число под модулем меняется (оно зависит от переменной), нам неясно — положительное оно или отрицательное.

Но что если изначально потребовать, чтобы это число было положительным? Например, потребуем, чтобы $3x-5 \gt 0$ — в этом случае мы гарантированно получим положительное число под знаком модуля, и от этого самого модуля можно полностью избавиться:

\[3x-5 \gt 0\Rightarrow \left| 3x-5 \right|=3x-5\]

Таким образом, наше уравнение превратится в линейное, которое легко решается:

\[3x-5=5-3x\Rightarrow 6x=10\Rightarrow x=\frac<5><3>\]

Правда, все эти размышления имеют смысл только при условии $3x-5 \gt 0$ — мы сами ввели это требование, дабы однозначно раскрыть модуль. Поэтому давайте подставим найденный $x=\frac<5><3>$ в это условие и проверим:

\[x=\frac<5><3>\Rightarrow 3x-5=3\cdot \frac<5><3>-5=5-5=0\]

Получается, что при указанном значении $x$ наше требование не выполняется, т.к. выражение оказалось равно нулю, а нам нужно, чтобы оно было строго больше нуля. Печалька.:(

Но ничего страшного! Ведь есть ещё вариант $3x-5 \lt 0$. Более того: есть ещё и случай $3x-5=0$ — это тоже нужно рассмотреть, иначе решение будет неполным. Итак, рассмотрим случай $3x-5 \lt 0$:

\[3x-5 \lt 0\Rightarrow \left| 3x-5 \right|=5-3x\]

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

Интересно, при каких таких $x$ выражение $5-3x$ будет равно выражению $5-3x$? От таких уравнений даже Капитан очевидность подавился бы слюной, но мы-то знаем: это уравнение является тождеством, т.е. оно верно при любых значениях переменной!

А это значит, что нас устроят любые $x$. Вместе с тем у нас есть ограничение:

\[3x-5 \lt 0\Rightarrow 3x \lt 5\Rightarrow x \lt \frac<5><3>\]

Другими словами, ответом будет не какое-то отдельное число, а целый интервал:

\[x\in \left( -\infty ;\frac<5> <3>\right)\]

Наконец, осталось рассмотреть ещё один случай: $3x-5=0$. Тут всё просто: под модулем будет ноль, а модуль нуля тоже равен нулю (это прямо следует из определения):

\[3x-5=0\Rightarrow \left| 3x-5 \right|=0\]

Но тогда исходное уравнение $\left| 3x-5 \right|=5-3x$ перепишется следующим образом:

\[0=3x-5\Rightarrow 3x=5\Rightarrow x=\frac<5><3>\]

Этот корень мы уже получали выше, когда рассматривали случай $3x-5 \gt 0$. Более того, это корень является решением уравнения $3x-5=0$ — это ограничение, которое мы сами же и ввели, чтобы обнулить модуль.:)

Таким образом, помимо интервала нас устроит ещё и число, лежащее на самом конце этого интервала:

Объединение корней в уравнениях с модулем

Итого окончательный ответ: $x\in \left( -\infty ;\frac<5> <3>\right]$. Не очень-то привычно видеть такую хрень в ответе к довольно простому (по сути — линейному) уравнению с модулем, правда? Что ж, привыкайте: в том и состоит сложность модуля, что ответы в таких уравнениях могут оказаться совершенно непредсказуемыми.

Куда важнее другое: мы только что разобрали универсальный алгоритм решения уравнения с модуляем! И состоит этот алгоритм из следующих шагов:

  1. Приравнять каждый модуль, имеющийся в уравнении, к нулю. Получим несколько уравнений;
  2. Решить все эти уравнения и отметить корни на числовой прямой. В результате прямая разобьётся на несколько интервалов, на каждом из которых все модули однозначно раскрываются;
  3. Решить исходное уравнение для каждого интервала и объединить полученные ответы.

Вот и всё! Остаётся лишь один вопрос: куда девать сами корни, полученные на 1-м шаге? Допустим, у нас получилось два корня: $x=1$ и $x=5$. Они разобьют числовую прямую на 3 куска:

Разбиение числовой оси на интервалы с помощью точек

Ну и какие тут интервалы? Понятно, что их три:

  1. Самый левый: $x \lt 1$ — сама единица в интервал не входит;
  2. Центральный: $1\le x \lt 5$ — вот тут единица в интервал входит, однако не входит пятёрка;
  3. Самый правый: $x\ge 5$ — пятёрка входит только сюда!

Я думаю, вы уже поняли закономерность. Каждый интервал включает в себя левый конец и не включает правый.

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

На этом урок заканчивается. Скачивайте задачи для самостоятельного решения, тренируйтесь, сравнивайте с ответами — и увидимся в следующем уроке, который будет посвящён неравенствам с модулями.:)

SDK тебе, SDK мне, SDK всем! Как делать SDK и зачем это нужно

Наша компания делает сервис для хранения и обработки данных с промышленных устройств (насосы, буры и прочая промышленная техника). Мы храним данные наших клиентов и предоставляем функционал для их анализа: построение отчетов, графиков и еще много чего.

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

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

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

Пора определяться

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

Что ж, логично. Простыми словами, SDK — это пакет библиотек, для того, чтобы клиент мог легко и быстро начать работать с вашей системой (в данной статье речь пойдет про наш сервис, но всё изложенное в статье применимо и к другим видам SDK) или выполнять однотипные действия.

Но, как и у любого подхода, у «Пути SDK» есть как преимущества, так и недостатки.

Преимущества

Высокая скорость интеграции нового клиента — вашим клиентам нужно писать меньше кода.

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

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

Качество кода — много где любят экономить на тестировании (жалко бюджета, горят сроки и прочие причины). Понятно, что в реальном мире покрыть тестами все участки проекта это учень трудоемкая задача. Но качественно протестировать все модули SDK, а затем использовать их — это путь повышения процента покрытия тестами, что приведет вас к снижению количества ошибок.

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

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

Недостатки

Высокие требования к качеству кода SDK — следствие главного преимущества. Ошибка в SDK породит ошибки во всех системах, его использующих.

Установка ограничений — SDK — это набор библиотек для реализации стандартных сценариев. Иногда разработчики SDK полагают, что кроме реализации одного из предусмотренных сценариев клиенту ничего не потребуется, что клиенту проще сделать все с нуля самостоятельно, чем строить пьедестал из костылей для SDK.

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

Когда SDK действительно нужен

У вас есть несколько стандартных сценариев, которые реализуются заново из раза в раз — собственно, наш случай.

Внутренние разработки — в разных проектах вы используете системы логирования, конфигурирования систем, работу с HttpRequest, БД, файлами? Выработайте внутренний SDK — набор библиотек для внутреннего использования. Вы в любой момент можете расширить функционал SDK, но скорость разработки новых проектов, процент покрытия тестами и документацией вырастет, а порог вхождения новых разработчиков снизится.

Когда SDK скорее всего будет лишним

Сценарии использования не определены или постоянно меняются — оставьте реализацию кастомных решений клиентам и помогите им. Не надо городить вундервафлю, которая будет только мешать. Очень актуально для молодых компаний и стартапов.

Вы не умеете делать качественно — у меня для вас плохая новость: пора учиться. Но отдавать кривое решение клиенту это очень, очень неправильно. Клиентов надо уважать, в конце концов.

Итак, мы определились, что такое SDK, с его преимуществами и недостатками и когда он нам нужен. Если после этого вы поняли, что SDK действительно нужен — приглашаю вас встать на «путь SDK» и разобраться, а каким он должен быть и как его, черт подери, делать?

«А вы любите Lego?» — Модульность

Представим все возможные сценарии использования SDK (вы же уже определились, зачем он вам нужен, правда?) и сделаем по библиотеке на сценарий. Чем не выход? Но это плохой подход, и так мы делать не будем. А будем так:

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

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

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

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

Но не стоить плодить модули как попало, ведь чем больше модулей, тем больше головной боли от их зависимостей! Т.е. важно правильно разбить логику на модули, соблюдая баланс между решением «все в одном» и «на каждую функцию свой модуль».

«А что, так можно было?!» — Универсальность

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

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

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

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

«Родитель 1, Родитель 2, Дети[ ]» — Именование

И тем не менее… Правильное именование модулей, классов, свойств и методов сильно помогут тем, кто будут с вашим SDK работать. Пример, не требующих комментариев:

Kinect 2.0 SDK example

Всё ясно из названий классов и методов. А если есть автодополнение кода в вашей IDE, то зачастую можно и в документацию не заглядывать, если и так все понятно.

«Уверен, если бы Смерть знала, что такое бюрократия, люди бы никогда не умирали, вечно стоя в очереди. » — Документация

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

Мы выбрали самый простой путь — summary + articles. Мы добавляем Xml атрибуты для методов и классов, которые светятся в intellisense как подсказки. Используя Docfx мы строим документацию по этим атрибутам и получаем подробную и удобную документацию, которую дополняет статьями, описывающими сценарии использования и примеры.

«— Чтобы чисто было! — Как я буду вилкой-то чистить?» — Тестирование

Что можно сказать про тестирование в рамках обсуждения SDK… Must have! Лучшим решением будет TDD (несмотря на то, что я негативно отношусь к данному подходу, в данном случае я решил использовать именно его). Да, долго. Да, нудно. Но зато в будущем вы не повеситесь от постоянных падений SDK на стороне и следствий этого падения.

Основной сок ситуации заключается в том, что отдавая SDK клиенту вы теряете контроль: вы не можете быстро пофиксить ошибку, сложно эту самую ошибку найти, да и выглядеть в такой ситуации вы будете достаточно глупо. Поэтому — тестируйте. Тестируйте лучше. И еще раз. И, на всякий случай, протестируйте ваши тесты. И тесты тестов. Так, что-то я увлекся, но важность тестирования SDK, надеюсь, понятна.

«Жертва, которая не могла противостоять своему прошлому, была поглощена им» — Логи

Поскольку вы отдаете SDK сторонней компании, в следствие чего теряете контроль над ситуацией, в случае ошибки (на этапе тестирования вы все-так решили «и так сойдёт», да?) вас ждет достаточно долгий и болезненный процесс поиск этой самой ошибки. Именно тут вам на помощь придут логи.

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

«Alarm! Achtung! Attention!» — Ошибки

Долго размышляя на тему ошибок я пришел к интересному выводу — ни один метод в вашем SDK не должен отдавать ошибку, не описанную в документации. Согласитесь, очень неприятно, когда вы подключаете стороннюю библиотеку для работы с HttpRequest, а она вываливает на вас какой-нибудь NullPointerException и StackTrace, который уводит в недра библиотеки. И вам приходиться погружаться в эти самые «недра», пытаясь понять, насколько глубока кроличья нора, и в чем, собственно, проблема.

Поэтому я предлагаю следующее решение — декларируйте закрытый список возможных исключений и документируйте их. Но, т.к. нельзя быть увереннным, что вы предусмотрели все, оберните метод в try-catch, а пойманную ошибку — в задекларируему. Например, ConfigurationException, который будет содержать InnerException — пойманную ошибку. Это позволит стороннему разработчику поймать все возможные ошибки, но в случае чего быстро разобраться в чем дело.

Версии или «как не укусить себя за хвост»

Во избежание проблем в будущем крайне рекомендую использовать строгое версионирование. Выберете подходящую вам систему построения версий и используйте ее. Но если новая версия библиотеки не имеет обратной совместимости — это необходимо указать. Как это разруливать — думать вам. Но подумать об этом точно стоит.

«Паровозик, который смог» — Deploy

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

  • билдит релиз
  • кладет все библиотеки в архив
  • билдит свежую версию документации (docfx)
  • указывает версию релиза в документации и в названии архива
  • кладет всё самое свеженькое в гит-репозиторий
  • WebApp на MS Azure подтягивает свежий коммит по гит хуку и публикует изменения

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

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

«-А так можешь? — Фигня. Смотри как надо!» — Примеры & toolkit

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

Заключение

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

Спасибо за прочтение, буду рад вашим комментариям. Надеюсь, эта статья будет для вас полезной.