Привет, Хабр. В преддверии старта курса «Python Developer. Professional» подготовили традиционный перевод полезного материала. Также приглашаем всех желающих посетить открытый вебинар на тему «Визуализация данных с помощью matplotlib». В этой статье из серии про синтаксический сахар в Python я займусь на первый взгляд очень простым синтаксисом, но на самом деле, чтобы разобраться в механике его работы, нужно погрузиться вглубь на несколько слоев. Мы будем говорить о not. Определение звучит на первый взгляд очень просто: Оператор not выдает True, если его аргумент False, и False в противоположном случае. Достаточно просто, не так ли? Но когда вы начинаете разбираться в том, что считать «истинным» или «ложным», вы быстро понимаете, что есть приличное количество вещей, подходящих под эти определения. (Как и в других статьях этой серии, код на С предназначен для тех, кто хочет пройти путь «по хлебным крошкам», но вы можете пропустить его, если хотите) Если вы посмотрите на байткод, то заметите единственный опкод, относящийся к not – это UNARY_NOT.  Байткод для not a: Реализация UNARY_NOT по сути вызывает функцию С, которая называется PyObject_IsTrue() и возвращает обратное переданному значение: True для False, False для True. Реализация опкода UNARY_NOT из Python/ceval.c: Вся хитрость нашего разбора not начинается с определения того, что такое True. Если посмотреть на реализацию PyObject_IsTrue() на языке Си, станет видно, что существует несколько возможных способов выяснить истинность объекта. Реализация PyObject_IsTrue(): Если разбираться в реализации на С, то правило выглядит так: Если True, то True Если False, то False Если None, то False То, что возвращает bool, до тех пор, пока возвращаемый объект является подклассом bool (то, что показывает вызов nb_bool) Вызов len() на объекте (то, за что отвечают вызовы mp_length и sq_length): Если больше 0, то True В противном случае False Если ничего из вышеперечисленного не подходит, то True Все правила 1-3 и 6 достаточно понятны, а вот правила 4 и 5 требуют углубления в детали. bool Определение специального/волшебного метода bool говорит нам, что метод используется для «реализации проверки истинности значений» и должен возвращать либо True, либо False. len() Встроенная функция len() возвращает целое число, представляющее количество элементов в контейнере. Реализация вычисления длины объекта представлена слотами sq_length (длина последовательностей) и mp_length (длина словарей/мэпов). Легко подумать, что к объекту можно просто обратиться и запросить его длину, но тут есть два слоя. len  Первый слой – это специальный/волшебный метод len. Как и следовало ожидать, он «должен возвращать длину объекта, целое число >= 0». Но дело в том, что «целочисленный» не означает int, а означает объект, который вы можете «преобразовать без потерь»… к «целочисленному объекту». И как же выполнить это преобразование? index «Чтобы без потерь преобразовать численный объект в целочисленный», используется специальный/волшебный метод index. В частности, для обработки преобразования используется функция PyNumber_Index(). Эта функция слишком длинная, чтобы ее сюда вставлять, но делает она следующее: Если аргумент является экземпляром класса int, вернуть его В противном случае вызвать index на объекте Если index возвращает конкретный экземпляр класса int, вернуть его (технически возвращать подкласс не рекомендуется, но давайте оставим это в прошлом). В противном случае вернуть TypeError. На уровне Python это делается с помощью operator.index(). К сожалению, здесь не реализуется семантика PyNumber_Index(), поэтому на самом деле с точки зрения not и len, функция работает немного неточно. Если бы она все же была реализована, то выглядела бы так: Реализация PyNumber_Index() на Python: Еще один интересный факт о реализации len(): она всегда возвращает конкретный int. Так, несмотря на то, что index() или len() могут возвращать подкласс, ее реализация на уровне С через PyLong_FromSsize_t() гарантирует, что всегда будет возвращаться конкретный экземпляр int. В противном случае len() будет проверять, что возвращают len() и index (), например, является ли возвращаемый объект подклассом int, больше ли значение или равно 0 и т.д. Таким образом, вы можете реализовать len() так: Во многих языках программирования при определении операции not, распространенной идиомой является превращение объекта в его сравнительное логическое значение с помощью передачи его в not дважды – через not not. Первый раз, чтобы получить инвертированное логическое значение, и второй раз, чтобы инвертировать инверсию, и получить логическое значение, которое вы хотели изначально. В Python нам не нужна эта идиома. Спасибо bool() (а конкретно bool.new()), за то, что у нас есть вызов функции, который мы можем использовать для получения конкретного логического значения, а именно operator.truth(). Если вы посмотрите на этот метод, то увидите, что он использует PyObject_IsTrue() для определения логического значения объекта. Посмотрев slot_nb_bool (), вы увидите, что в конечном итоге он делает то же, что и PyObject_IsTrue(). То есть, если мы можем реализовать аналог PyObject_IsTrue(), то можем определить, какое логическое значение имеет объект.  По старой схеме и с тем, что мы узнали только что, мы можем реализовать operator.truth() для этой логики (я предпочитаю не реализовывать bool, поскольку не хочу реализовывать все его численные функции, и не придумал хорошего способа сделать True и False с нуля, которые наследовались бы от 1 и 0 на чистом Python). Реализация operator.truth(): С реализованным оператором operator.truth(), реализовать operator.not_() – дело всего одной лямбды: Итоговый результат прост, но, чтобы добраться до него, нужно было проделать немало работы. Как обычно, код из этой статьи можно найти в моем проекте desugar.  Узнать подробнее о курсе «Python Developer. Professional». Смотреть открытый вебинар на тему «Визуализация данных с помощью matplotlib». Редактор