Занимательная математика, очаровательный python. Эпизод 3: Финальный аккорд

Safoyeth 17 Дек 2013

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


Давайте первым делом внимательно рассмотрим наш код и поймём чего хотим. В принципе, у нас небольшой модуль, который помещается в 200 строчек кода и можно было бы ничего не менять, но, чисто теоретически, есть вероятность, что мы будем расширять функционал и дописывать ещё функции. Кроме того, хотелось бы сделать всё как-то более компактно. Именно для этого мы и разделим все наши функции на несколько типов. Первый тип - факториалы - factorial, superfactorial, hyperfactorial, doublefactorial, primorial. Эти функции достаточно просты и их мы трогать практически не будем. Вторая группа функций - обратные факториалы - antifactorial, antisuperfactorial, antihyperfactorial, antidoublefactorial и antiprimorial. Если очень внимательно на них посмотреть, то можно увидеть, что в каждой из них мы делаем практически одно и тоже. Мы последовательно делим данное число на что-то. Третья группа - факторионы - isFactorion, isSuperfactorion, isHyperfactorion, isDoublefactorion, isPrimorion, isKFactorion подвергнется самому большому изменению. Опять же, сейчас эти функции достаточно просты, но мы создадим ещё одну, четвертую группу функций - k-факторионы, куда войдут isKFactorion, isKSuperfactorion, isKHyperfactorion, isKDoublefactorion, isKPrimorion, через которую мы и выразим впоследствии обычные факторионы (которые являются частным случаем k-факторионов). Наконец, мы создадим ещё одну группу функций, шестую, которая будет проверять, является ли число факториалом или суперфакториалом etc. Оставшиеся функции isPrime и sequence составят последнюю группу.

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

мы вынесем этот код в декоратор. Давайте удалим нашу функцию-декоратор constant, она больше не понадобится, и импортируем wraps:

Зачем нужна wraps я надеюсь вы в курсе из прочитанного на хабре 🙂

Теперь собственно о декораторах. Сначала мы объявляем функцию-декоратор, затем с помощью @wraps мы декорируем внутреннюю функцию, в которой и происходит проверка. После чего возвращаем внутреннюю функцию. Выглядит это так:

Теперь нам нужно удалить все инструкции if else из наших функций и декорировать их, приведя к такому виду:

И всё у нас было бы замечательно и круто, но как только мы попытаемся выполнить функцию isKFactorion, мы получим ошибку. Дело в том, что функция wrapper принимает только один аргумент, а функция isKFactorion пытается передать ей два. Более того, опять же, чисто теоретически, давайте представим что когда-нибудь в будущем у нас появятся функции, которые будут иметь большее число аргументов типа int, к примеру, пять. Выход напрашивается - использовать *args. Соответственно, меняется и логика wrapper, теперь мы должны проверять не один аргумент x, а все.  Следует также помнить, что декоратор можно использовать только один раз, то есть если мы попытаемся во время исполнения функции isKFactorion обратиться во второй раз к функции isInteger, то получим в ответ None. К счастью, нам не придется усложнять и писать много кода, мы просто чуть-чуть его подправим.

Всю магию здесь берут на себя функции all и any. Логика простая: если все значения в списке переданных аргументов выполняют условия (возвращают True), то вернуть функцию со всеми аргументами, иначе возбудить TypeError, в котором либо просто вернуть аргументы, если хотя бы один из них не выполняет часть условия (возвращает False), иначе объявить его отрицательным. Я не стал усложнять, но, конечно, с точки зрения полезности, лучше бы вместо простого перечисления аргументов в этом исключении, было бы вернуть их типы. Возможно, когда-нибудь, я и займусь этим, но сейчас мне показалось вполне достаточным то, что я написал.

Теперь, если мы попробуем выполнить isKFactorion, никаких неожиданных ошибок не будет. На этом заканчиваем с isInteger. Мы должны задекорировать все функции, кроме sequence. Перед написанием следующего, самого масштабного декоратора, давайте обратим внимание как раз на sequence. В данный момент если мы попробуем выполнить такой код

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

Всё довольно просто. Функция sequence принимает 4 параметра - саму функцию, первое и последнее значения (как и раньше) и параметр reverse, который по умолчанию мы ставим в False. Далее мы делаем то же, что и раньше, а затем проверяем, есть ли значение False в нашей секвенции. Если есть, то мы проходим по последовательности и выбираем индексы тех значений, которые возвращают True. Так как мы допускаем, что последовательность может начинаться не с 0, то к индексу мы должны прибавить valueFrom. Если же False нет в нашей последовательности, то мы просто присваиваем переменной result полученную последовательность и переходим к последнему шагу. На последнем шаге функция возвращает result если reverse = False, как стоит по умолчанию, либо же вернет result[::-1], то есть обратную последовательность.  Теперь мы сможем, допустим, получить последовательность простых чисел от 0 до 100 просто выполнив sequence(isPrime, valueTo = 100).

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

Теперь описание всего этого кошмара)). Наша функция-декоратор принимает два аргумента - argument, который будет принимать значения None, super, hyper, double и prime в зависимости от функции, которую мы будем декорировать. Второй аргумент entity принимает значения True и False и он будет отвечать за функции типа isFactorial, isHyperfactorial etc. Внутри функции manager мы определим функцию decorator, которая и будет принимать в качестве аргумента ту функцию, которую нужно декорировать.  Дальше как обычно @wraps и wrapper, в которой и будет вся магия. Первым делом мы обработаем единицу. Для всех обратных функций, единица соответствует паре значений 0 и 1. Поэтому просто вернем [0, 1] если значение равно 1, при любом аргументе и только если entity равно False. Таким образом wrapper ”понимает”, что декорируются обратные функции. Если же entity = True, а значит мы декорируем функции alla isFactorial, то необходимо вернуть True, так как 1 является и факториалом и суперфакториалом etc. Далее мы должны пройти по последовательности каких-то значений, последовательно деля данное число на каждый элемент этой последовательности. Здесь нам на помощь придёт инлайновая запись. Мы можем непосредственно в момент выполнения функции определить последовательность вот такой записью seq if conditions else seq2 if condions2 else seq3 if conditions3 else seq4, где seq - последовательность, conditions - условия. Соответственно и в нашей функции определяются следующие последовательности [z for z in range(2, sys.getrecursionlimit()) if z % 2 == 0] и [z for z in range(2, sys.getrecursionlimit()) if z % 2 != 0] для чётных и нечётных значений соответственно для функции antidoublefactorial; sequence(isPrime, 2, sys.getrecursionlimit()) для простых чисел для функции antiprimorial; и, наконец, range(2, sys.getrecursionlimit()+1) для всех остальных функций.

Следующий шаг - деление. Опять же эта задача легко решается с помощью инлайновой записи. Мы делим либо на факториал, либо на суперфакториал каждого числа, если функция superfactorial или hyperfactorial соответственно, либо же просто делим на число в остальных случаях (так как последовательности заданы). Дальше уже знакомая проверка. Если получившееся число больше 1.0 и не равно ей, то продолжаем, иначе если наша функция isFactorial, isHyperfactorial etc., то вернуть False, если же мы ищем обратную функцию, то вернуть ValueError. Если же значение равно 1.0, то дальше если аргументом является prime, то мы просто делаем всё то же самое, что делали с функцией antiprimorial - код полностью идентичен коду функции antiprimorial, начиная с инструкции values = [each] и до break. И финальным аккордом мы возвращаем True, если entity = True, то есть если мы хотели знать, является ли число факториалом/суперфакториалом/гиперфакториалом/двойным факториалом или праймориалом, или возвращаем число - значение обратного факториала/суперфакториала/гиперфакториала/двойного факториала или праймориала. Далее последовательно возвращаем function(x), wrapper и decorator.

Теперь всё, что нам нужно - либо переписать, либо заново создать десять функций в таком виде (на примере antifactorial и isSuperfactorial)

Значительная часть кода исчезла! Теперь всё делает наш декоратор, функция же только возвращает значение. Поскольку мы установили значения аргументов по умолчанию None, мы можем вызвать @manager() без аргументов. Также хочу обратить внимание, что сначала мы декорируем все функции посредством @isInteger, то есть сначала мы проверяем валидность данных и только потом отправляем их @manager.

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

Здесь всё довольно просто и не требует какого-то сверхкомментирования. Теперь осталось только переписать и создать все k-функции. Я не буду этого делать, в этом уж совсем нет никакой сложности.

Последнее, что нам осталось - выразить факторион как частный случай k-факториона, при k = 1. Это достигается одной строчкой кода:

Вот и всё! Остальные функции также переписываются аналогичным образом. Единственные функции, которые мы не тронули - isPrime и primorial мы никак не будем модифицировать, только обернем их нашим декоратором @isInteger. На этом всё! Исходный код модуля, который я назвал extendedmath можно взять здесь и модифицировать как кому удобно, например создать строки документации. В будущем мы обязательно ещё вернёмся к нему и создадим ещё не одну занимательно-математическую функцию!

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.




Добавить комментарий

Сообщить об опечатке

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