if not x в Python: почему это не всегда проверка на None
Проверка if not x кажется безобидной, но она срабатывает на пустом списке, нуле и пустой строке. Разбираем, где это ломает логику и как писать условия так, чтобы код делал то, что вы имели в виду.

Функция принимает параметр limit, вы пишете if not limit: и подставляете значение по умолчанию. Всё работает, пока кто-то не передаёт limit=0 — и получает поведение, как будто ничего не передал. Ошибка не падает, тесты на положительных числах зелёные, а в проде выгрузка внезапно берёт дефолтный лимит вместо нуля. Причина — в том, как Python приводит объекты к булеву значению в условиях. Разберём, что именно проверяет if not x, где это расходится с намерением и как писать условия однозначно. Актуально для Python 3.x, поведение здесь стабильно между версиями.
Что на самом деле проверяет условие
Когда вы пишете if x:, интерпретатор не проверяет, что x «существует» или «не равен None». Он вызывает приведение объекта к булеву типу — через метод __bool__, а если его нет, через __len__. Результат и определяет ветку.
Так называемые ложные (falsy) значения в Python — это не только None и False. Вот полный набор для встроенных типов:
NoneFalse- числовой ноль любого типа:
0,0.0,0j,Decimal(0),Fraction(0, 1) - пустые последовательности:
'',[],(),b'',range(0) - пустые коллекции:
{},set(),frozenset()
Всё остальное — истинно. Непустая строка '0' — истинна. Строка из пробела ' ' — истинна. Объект без __bool__ и __len__ — всегда истинен.
if not xотвечает на вопрос «x пустой или нулевой?», а не «x отсутствует?». Пока эти вопросы совпадают, код работает. Проблемы начинаются там, где ноль и пустота — валидные данные.
Где это ломается на практике
Значение по умолчанию для параметра
Классическая ловушка. Вы хотите: если аргумент не передали — подставь дефолт.
# Python 3.x
def fetch(rows=None):
# Задумано: None -> берём 100. Но not 0 тоже True
if not rows:
rows = 100
return rows
print(fetch()) # 100 — верно
print(fetch(50)) # 50 — верно
print(fetch(0)) # 100 — а вот это сюрприз, ждали 0
Правильная проверка «значение не передали» — сравнение с None через is:
def fetch(rows=None):
if rows is None:
rows = 100
return rows
print(fetch(0)) # 0 — теперь корректно
Оператор is проверяет идентичность объекта, а не его истинность. None в интерпретаторе существует в единственном экземпляре, поэтому rows is None — надёжная проверка «здесь именно None, а не пустой список и не ноль».
Пустая коллекция против отсутствия коллекции
Функция возвращает список найденных записей. Пустой список — валидный результат «ничего не нашли». А None может означать «поиск не выполнялся».
result = search(query)
# Схлопывает два разных случая в один
if not result:
handle_empty()
# Разделяет их
if result is None:
handle_not_searched()
elif len(result) == 0:
handle_nothing_found()
Если разница между «не искали» и «нашли пусто» для вашей логики неважна — if not result уместен и короче. Важно принимать это решение осознанно, а не по привычке.
Проверка ключа словаря
Ещё одно частое место. Значение по ключу может легально быть нулём или пустой строкой.
config = {'timeout': 0, 'retries': 3}
# Ошибка: timeout=0 попадёт в ветку 'ключа нет'
if not config.get('timeout'):
timeout = 30
# Верно: разделяем 'ключа нет' и 'ключ есть, значение 0'
if config.get('timeout') is None:
timeout = 30
else:
timeout = config['timeout']
Как выбрать проверку
Перед тем как писать условие, ответьте на один вопрос: что именно вы отсекаете — отсутствие значения или его пустоту/ноль. Дальше таблица.
| Намерение | Как писать | Что попадёт в ветку |
|---|---|---|
| Значение не передали / это None | if x is None: | только None |
| Значение передали (что угодно, кроме None) | if x is not None: | всё, включая 0, '', [] |
| Коллекция пуста | if len(x) == 0: или if not x: (если None невозможен) | пустые коллекции |
| Строка пустая или из пробелов | if not x.strip(): | '', ' ' |
| Число равно нулю | if x == 0: | только ноль, но не None |
| Значение ложно в широком смысле | if not x: | None, 0, пустые, False — всё сразу |
Правило простое: is None — когда вас интересует наличие объекта; not x — когда вас интересует его «содержательность» и вы готовы к тому, что ноль и пустота уйдут в ту же ветку.
Логические операторы возвращают не bool
Ещё один источник неожиданностей — and и or в Python не возвращают True/False. Они возвращают один из операндов.
# or возвращает первый истинный операнд или последний
name = user_input or 'anonymous'
print('' or 'anonymous') # 'anonymous'
print('Ivan' or 'anonymous') # 'Ivan'
print(0 or 'anonymous') # 'anonymous' — ноль тоже ложен
# and возвращает первый ложный операнд или последний
print('a' and 'b') # 'b'
print('' and 'b') # ''
Приём x or default — короткая замена дефолту, но у него та же болезнь, что у if not x: 0 or 'anonymous' даёт 'anonymous'. Для строк ввода это обычно нормально, для чисел — ловушка.
С Python 3.8 появился walrus-оператор :=1, но он не про истинность, а про присваивание внутри выражения. Он не решает проблему falsy-значений сам по себе.
Цепочки сравнений — то, чего нет в других языках
Python разрешает if 0 < x < 100:, и это именно математический смысл «x между 0 и 100», а не (0 < x) < 100, как было бы в C или JavaScript. Каждый операнд в цепочке вычисляется один раз. Это удобно, но не путайте с побитовыми операторами: & и | — это не and/or, у них другой приоритет и другая семантика.
Типичные ошибки
- Сравнение с None через
==. Пишитеx is None, а неx == None. Пользовательский класс может переопределить__eq__так, чтоx == Noneвернёт неожиданный результат. Линтеры (например, flake8) помечают== Noneкак ошибку E711. if x == True.Избыточно и опасно:1 == Trueистинно, а2 == True— нет, хотяif 2:сработает. Пишите простоif x:.if not xдля чисел, где ноль валиден. Счётчики, координаты, суммы, таймауты — везде, где ноль имеет смысл, проверка на истинность обманет.- Мутабельный дефолт вместо None.
def f(items=[])создаёт один список на все вызовы. Правильно —def f(items=None)и внутриif items is None: items = [].
Что делать дальше
Пройдитесь по коду поиском if not и or default вокруг числовых и коллекционных параметров — это места, где ноль и пустота молча уходят в чужую ветку. Включите в линтере правило про == None. Если пользуетесь аннотациями типов, отметьте необязательные параметры как Optional[int] — статический анализатор вроде mypy подскажет, где вы забыли обработать None отдельно от нуля. Разница между «отсутствует» и «пусто» — это не педантизм, а граница, на которой чаще всего живут тихие баги.
Проверка рекламы на риски: Что в макете вызовет вопросы ФАС: превосходные степени, сравнения, обещания результата, маркировка.
Спросить за 40 ₽Источники: Python docs — Truth Value Testing, Python docs — Boolean operations: and, or, not
Частые вопросы
А если у меня Python 2 — там так же?
Почему нельзя просто везде писать is None и не думать?
if not x и if x is None дают разный результат — как быстро проверить, что именно у меня?
Оператор or для дефолта — это плохая практика?
Что за метод __bool__ и когда он вызывается?
Влияет ли это на производительность — is None против not x?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.