Условный оператор
Иногда нужно проверить одновременно не одно, а несколько условий. Например, проверить, является ли данное число четным можно при помощи условия (n % 2 == 0) (остаток от деления n на 2 равен 0), а если необходимо проверить, что два данных целых числа n и m являются четными, необходимо проверить справедливость обоих условий: n % 2 == 0 и m % 2 == 0, для чего их необходимо объединить при помощи оператора and (логическое И): n % 2 == 0 and m % 2 == 0.
В Питоне существуют стандартные логические операторы: логическое И, логическое ИЛИ, логическое отрицание.
Логическое И является бинарным оператором (то есть оператором с двумя операндами: левым и правым) и имеет вид and. Оператор and возвращает True тогда и только тогда, когда оба его операнда имеют значение True.
Логическое ИЛИ является бинарным оператором и возвращает True тогда и только тогда, когда хотя бы один операнд равен True. Оператор “логическое ИЛИ” имеет вид or.
Логическое НЕ (отрицание) является унарным (то есть с одним операндом) оператором и имеет вид not, за которым следует единственный операнд. Логическое НЕ возвращает True, если операнд равен False и наоборот.
Пример. Проверим, что хотя бы одно из чисел a или b оканчивается на 0:
if a % 10 == 0 or b % 10 == 0:
Проверим, что число a — положительное, а b — неотрицательное:
if a > 0 and not (b < 0):
Или можно вместо not (b < 0) записать (b >= 0).
Python: Отрицание
Наряду с логическими операторами И и ИЛИ, часто используется операция «отрицание». Она меняет логическое значение на противоположное. В программировании отрицанию соответствует унарный оператор not :
not True # False not False # True
Например, если есть функция, которая проверяет четность числа, то с помощью отрицания можно выполнить проверку нечетности:
def is_even(number): return number % 2 == 0 print(is_even(10)) # => True print(not is_even(10)) # => False
В примере выше мы добавили not слева от вызова функции и получили обратное действие.
Отрицание — инструмент, с которым можно выражать задуманные правила в коде и не писать новые функции.
Если написать not not is_even(10) , то код сработает даже в таком случае:
print(not not is_even(10)) # => True
В логике двойное отрицание — это отсутствие отрицания:
not not True # True not not False # False print(not not is_even(10)) # => True print(not not is_even(11)) # => False
Теперь вы знаете, что означают операторы И, ИЛИ и not . С их помощью вы сможете задавать составные условия из двух и более логических выражений.
Задание
В этом уроке вам нужно будет реализовать две функции is_palindrome() и is_not_palindrome() , принимающие строку на вход
-
Реализуйте функцию is_palindrome() , которая определяет, является ли слово палиндромом или нет. Палиндром — это слово, которое читается одинаково в обоих направлениях. Слова в функцию могут быть переданы в любом регистре, поэтому сначала нужно привести слово к нижнему регистру: word.lower() .
is_palindrome('шалаш') # true is_palindrome('хекслет') # false is_palindrome('Довод') # true is_palindrome('Функция') # false
is_not_palindrome('шалаш') # false is_not_palindrome('Ага') # false is_not_palindrome('хекслет') # true
Упражнение не проходит проверку — что делать?
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
- Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
В моей среде код работает, а здесь нет
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Мой код отличается от решения учителя
Это нормально , в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Прочитал урок — ничего не понятно
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Полезное
Определения
- Отрицание — логическая операция, которая меняет логическое значение на противоположное.
Разбираемся с not в Python
Привет, Хабр. В преддверии старта курса «Python Developer. Professional» подготовили традиционный перевод полезного материала.
В этой статье из серии про синтаксический сахар в Python я займусь на первый взгляд очень простым синтаксисом, но на самом деле, чтобы разобраться в механике его работы, нужно погрузиться вглубь на несколько слоев. Мы будем говорить о not .
Определение звучит на первый взгляд очень просто:
Оператор not выдает True, если его аргумент False, и False в противоположном случае.
Достаточно просто, не так ли? Но когда вы начинаете разбираться в том, что считать «истинным» или «ложным», вы быстро понимаете, что есть приличное количество вещей, подходящих под эти определения.
(Как и в других статьях этой серии, код на С предназначен для тех, кто хочет пройти путь «по хлебным крошкам», но вы можете пропустить его, если хотите)
Реализация not
Если вы посмотрите на байткод, то заметите единственный опкод, относящийся к not – это UNARY_NOT.
>>> import dis >>> def spam(): not a . >>> dis.dis(spam) 1 0 LOAD_GLOBAL 0 (a) 2 UNARY_NOT 4 POP_TOP 6 LOAD_CONST 0 (None) 8 RETURN_VALUE
Реализация UNARY_NOT по сути вызывает функцию С, которая называется PyObject_IsTrue() и возвращает обратное переданному значение: True для False, False для True.
case TARGET(UNARY_NOT): < PyObject *value = TOP(); int err = PyObject_IsTrue(value); Py_DECREF(value); if (err == 0) < Py_INCREF(Py_True); SET_TOP(Py_True); DISPATCH(); >else if (err > 0) < Py_INCREF(Py_False); SET_TOP(Py_False); DISPATCH(); >STACK_SHRINK(1); goto error; >
Определение того, что такое True
Вся хитрость нашего разбора not начинается с определения того, что такое True. Если посмотреть на реализацию PyObject_IsTrue() на языке Си, станет видно, что существует несколько возможных способов выяснить истинность объекта.
/* Test a value used as condition, e.g., in a for or if statement. Return -1 if an error occurred */ int PyObject_IsTrue(PyObject *v) < Py_ssize_t res; if (v == Py_True) return 1; if (v == Py_False) return 0; if (v == Py_None) return 0; else if (v->ob_type->tp_as_number != NULL && v->ob_type->tp_as_number->nb_bool != NULL) res = (*v->ob_type->tp_as_number->nb_bool)(v); else if (v->ob_type->tp_as_mapping != NULL && v->ob_type->tp_as_mapping->mp_length != NULL) res = (*v->ob_type->tp_as_mapping->mp_length)(v); else if (v->ob_type->tp_as_sequence != NULL && v->ob_type->tp_as_sequence->sq_length != NULL) res = (*v->ob_type->tp_as_sequence->sq_length)(v); else return 1; /* if it is negative, it should be either -1 or -2 */ return (res > 0) ? 1 : Py_SAFE_DOWNCAST(res, Py_ssize_t, int); >
Если разбираться в реализации на С, то правило выглядит так:
- Если True, то True
- Если False, то False
- Если None, то False
- То, что возвращает bool , до тех пор, пока возвращаемый объект является подклассом bool (то, что показывает вызов nb_bool )
- Вызов len() на объекте (то, за что отвечают вызовы mp_length и sq_length ):
- Если больше 0, то True
- В противном случае False
Все правила 1-3 и 6 достаточно понятны, а вот правила 4 и 5 требуют углубления в детали.
Определение специального/волшебного метода bool говорит нам, что метод используется для «реализации проверки истинности значений» и должен возвращать либо True, либо False.
Встроенная функция len() возвращает целое число, представляющее количество элементов в контейнере. Реализация вычисления длины объекта представлена слотами sq_length (длина последовательностей) и mp_length (длина словарей/мэпов).
Легко подумать, что к объекту можно просто обратиться и запросить его длину, но тут есть два слоя.
Первый слой – это специальный/волшебный метод len . Как и следовало ожидать, он «должен возвращать длину объекта, целое число >= 0». Но дело в том, что «целочисленный» не означает int , а означает объект, который вы можете «преобразовать без потерь»… к «целочисленному объекту». И как же выполнить это преобразование?
«Чтобы без потерь преобразовать численный объект в целочисленный», используется специальный/волшебный метод index . В частности, для обработки преобразования используется функция PyNumber_Index() . Эта функция слишком длинная, чтобы ее сюда вставлять, но делает она следующее:
- Если аргумент является экземпляром класса int , вернуть его
- В противном случае вызвать index на объекте
- Если index возвращает конкретный экземпляр класса int , вернуть его (технически возвращать подкласс не рекомендуется, но давайте оставим это в прошлом).
- В противном случае вернуть TypeError .
На уровне Python это делается с помощью operator.index() . К сожалению, здесь не реализуется семантика PyNumber_Index() , поэтому на самом деле с точки зрения not и len , функция работает немного неточно. Если бы она все же была реализована, то выглядела бы так:
Реализация PyNumber_Index() на Python:
def index(obj: Object, /) -> int: """Losslessly convert an object to an integer object. If obj is an instance of int, return it directly. Otherwise call __index__() and require it be a direct instance of int (raising TypeError if it isn't). """ # https://github.com/python/cpython/blob/v3.8.3/Objects/abstract.c#L1260-L1302 if isinstance(obj, int): return obj length_type = builtins.type(obj) try: __index__ = _mro_getattr(length_type, "__index__") except AttributeError: msg = ( f" cannot be interpreted as an integer " "(must be either a subclass of 'int' or have an __index__() method)" ) raise TypeError(msg) index = __index__(obj) # Returning a subclass of int is deprecated in CPython. if index.__class__ is int: return index else: raise TypeError( f"the __index__() method of returned an object of " f"type , not 'int'" )Реализация len()
Еще один интересный факт о реализации len() : она всегда возвращает конкретный int . Так, несмотря на то, что index() или len() могут возвращать подкласс, ее реализация на уровне С через PyLong_FromSsize_t() гарантирует, что всегда будет возвращаться конкретный экземпляр int .
В противном случае len() будет проверять, что возвращают len() и index () , например, является ли возвращаемый объект подклассом int , больше ли значение или равно 0 и т.д. Таким образом, вы можете реализовать len() так:
def len(obj: Object, /) -> int: """Return the number of items in a container.""" # https://github.com/python/cpython/blob/v3.8.3/Python/bltinmodule.c#L1536-L1557 # https://github.com/python/cpython/blob/v3.8.3/Objects/abstract.c#L45-L63 # https://github.com/python/cpython/blob/v3.8.3/Objects/typeobject.c#L6184-L6209 type_ = builtins.type(obj) try: __len__ = _mro_getattr(type_, "__len__") except AttributeError: raise TypeError(f"type does not have a __len__() method") length = __len__(obj) # Due to len() using PyObject_Size() (which returns Py_ssize_t), # the returned value is always a direct instance of int via # PyLong_FromSsize_t(). index = int(_index(length)) if index < 0: raise ValueError("__len__() should return >= 0") else: return indexРеализация operator.truth()
Во многих языках программирования при определении операции 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).
def truth(obj: Any, /) -> bool: """Return True if the object is true, False otherwise. Analogous to calling bool(). """ if obj is True: return True elif obj is False: return False elif obj is None: return False obj_type = type(obj) try: __bool__ = debuiltins._mro_getattr(obj_type, "__bool__") except AttributeError: # Only try calling len() if it makes sense. try: __len__ = debuiltins._mro_getattr(obj_type, "__len__") except AttributeError: # If all else fails. return True else: return True if debuiltins.len(obj) > 0 else False else: boolean = __bool__(obj) if isinstance(boolean, bool): # Coerce into True or False. return truth(boolean) else: raise TypeError( f"expected a 'bool' from .__bool__(), " f"not " )Реализация not
С реализованным оператором operator.truth() , реализовать operator.not_() – дело всего одной лямбды:
lambda a, /: False if truth(a) else TrueИтоговый результат прост, но, чтобы добраться до него, нужно было проделать немало работы.
Как обычно, код из этой статьи можно найти в моем проекте desugar.
ЛОГИЧЕСКИЕ ОПЕРАТОРЫ В PYTHON
Логические операторы используются для объединения нескольких операторов сравнения. Существуют три логических оператора: or (ИЛИ), and (И), not (НЕ).
Логический оператор «or»
Возвращает True, если хотя бы одно из сравнений истинно. Если оба сравнения ложны, то результат операции будет False.
a = 5 > 7 or 2 < 3 print(a)TrueЛогический оператор "and"
Возвращает True, если оба сравнения истинны. Если хотя бы одно из сравнений ложно, то результат операции будет False.
a = 5 > 7 and 2 < 3 print(a)FalseЛогический оператор "not"
Инвертирует результат сравнения. Если сравнение истинно, то результат операции будет False. Если сравнение ложно, то результат операции будет True.
a = not 5 > 7 print(a)TrueСтаршинство логических операций
Первой выполняется операция not, за ней — and. В последнюю очередь выполняется операция or.
Логические операторы младше операторов сравнения.
