bool в Python

Введение

bool наследует от int

https://github.com/python/cpython/blob/main/Doc/c-api/bool.rst

Большинство значений — True

Почти любое значение оценивается как True, если оно имеет какое-либо содержимое.

Любая строка — True, кроме пустых строк.

Любое число — True, кроме 0.

Любой список, кортеж, набор и словарь — True, кроме пустых.

print(bool("testsetup.ru")) print(bool(1961)) print(bool(-273)) print(bool(["cordoba", "fuengirola", "malaga"]))

True True True True

Некоторые значения — False

Существует не так много значений, которые оцениваются как ложные, за исключением пустых значений, таких как (), [], {}, "", число 0 и значение None.

И, конечно, значение False оценивается как ложное.

print(bool(False)) print(bool(None)) print(bool(0)) print(bool("")) print(bool(())) print(bool([])) print(bool({}))

False False False False False False False

if X vs if X is True

В дальнейшем нам пригодится понимание разницы между if X и if X is True

if X is True - это более жёсткое условие.

url = "testsetup.ru" if url: print('if url') if url is True: print('if url is True!')

if url

url это не пустая строка, поэтому if url выполняется как True.

Но url это не True. Тип url - str

Если сделать url пустой строкой, то ни одно из условий не выполнится.

url = "" if url: print('if url') if url is True: print('if url is True!')

 

Если переменная явно задана как True, или значение получено из функции, которая возвращает True, то оба условия выполнятся.

verified = True if verified: print('if verified') if verified is True: print('if verified is True!')

if verified if verified is True!

Явно задать bool для своего объекта

Применим bool() к объектам класса, у которого нет кастомных методов кроме __init__()

class My: def __init__(self, length: int) -> None: self.length = length if __name__ == '__main__': print(bool(My(0))) # True print(bool(My(1))) # True # print(bool(My("a"))) # TypeError: '>' not supported between instances of 'str' and 'int'

True
True

Так как пустой объект пользовательского класса не попадает в группу объектов, которые считаются False мы получили оба раза True

Чтобы явно задать нужное поведение используем магический метод __bool__()

class My: def __init__(self, length: int) -> None: self.length = length def __bool__(self) -> bool: return True if self.length > 0 else False if __name__ == '__main__': print(bool(My(0))) # False print(bool(My(1))) # True print(bool(My(-1))) # False

False True False

Обратите внимание, на то, что можно использовать и противоположную логику. Интуитивно это будет непонятно, но формально никто так делать не запрещает.

class My: def __init__(self, length: int) -> None: self.length = length def __bool__(self) -> bool: return True if self.length <= 0 else False if __name__ == '__main__': print(bool(My(0))) # True print(bool(My(1))) # False

True
False

Из __bool__() можно возвращать только bool

class My: def __init__(self, length: int) -> None: self.length = length def __bool__(self) -> bool: return 0 if __name__ == '__main__': print(bool(My(0))) # False

TypeError: __bool__ should return bool, returned int

Второй магический метод, который можно использовать для той же цели - это __len__()

class My: def __init__(self, length: int) -> None: self.length = length def __len__(self) -> int: return self.length if __name__ == '__main__': print(bool(My(0))) # False print(bool(My(1))) # True print(bool(My(-1))) # ValueError: __len__() should return >= 0

False True

Способ не идентичен предыдущему , например, отрицательные числа в My() уже не передать.

ValueError: __len__() should return >= 0

__len__() должен возвращать int

class My: def __init__(self, length: int) -> None: self.length = length def __len__(self) -> int: return "heihei.ru" # TypeError: 'str' object cannot be interpreted as an integer if __name__ == '__main__': print(bool(My(0))) # False

TypeError: 'str' object cannot be interpreted as an integer

Сравнение с bool

Вспомним главу if X vs if X is True

Пример сравнения с True, который выполнится как True

class My: def __bool__(self) -> bool: print('__bool__ called') return True my = My() if my: print('Done!')

__bool__ called Done!

А следующий пример не выполнится как True, потому что там явно проверяется является ли my константой True.

my является объектом класса My поэтому даже до вызова метода __bool__() дело не дойдёт.

class My: def __bool__(self) -> bool: print('__bool__ called') return True my = My() if my is True: print('Done!')

 

Способ, которого рекомендуется избегать, потому что == вызывает метод __eq__() а не __bool__() поэтому результат может быть непредсказуемым.

Например, если добавить к этому примеру метод __eq__(), который всё время возвращает True, мы получим совершенно другой результат

class My: def __init__(self, length: int) -> None: self.length = length def __bool__(self) -> bool: print('__bool__ called') return True if self.length > 0 else False def __eq__(self, other: object) -> bool: print('__eq__ called') return True my = My(0) if my == True: print('Done!') my = My(-1) if my == True: print('Done!')

__eq__ called Done! __eq__ called Done!

Оба раза сравнение сработало как True, хотя длина меньше либо равна нулю.

Method Resulution Order

# builtins.py Python 3.12.2 # … class bool(int): """ bool(x) -> bool Returns True when the argument x is true, False otherwise. The builtins True and False are the only two instances of the class bool. The class bool is a subclass of the class int, and cannot be subclassed. """ def __and__(self, *args, **kwargs): # real signature unknown """ Return self&value. """ pass def __init__(self, x): # real signature unknown; restored from __doc__ pass def __invert__(self, *args, **kwargs): # real signature unknown """ ~self """ pass @staticmethod # known case of __new__ def __new__(*args, **kwargs): # real signature unknown """ Create and return a new object. See help(type) for accurate signature. """ pass def __or__(self, *args, **kwargs): # real signature unknown """ Return self|value. """ pass def __rand__(self, *args, **kwargs): # real signature unknown """ Return value&self. """ pass def __repr__(self, *args, **kwargs): # real signature unknown """ Return repr(self). """ pass def __ror__(self, *args, **kwargs): # real signature unknown """ Return value|self. """ pass def __rxor__(self, *args, **kwargs): # real signature unknown """ Return value^self. """ pass def __xor__(self, *args, **kwargs): # real signature unknown """ Return self^value. """ pass

print(bool.__mro__)

(<class 'bool'>, <class 'int'>, <class 'object'>)

print(True * 5)

5

print(True + True + 2)

4

print(1 / False)

Traceback (most recent call last): File "C:\python\main.py", line 21, in print(1 / False) ~~^~~~~~~ ZeroDivisionError: division by zero

Видео

Один из важных источников информации, для написания статьи - видео из курса "Лучший курс по Python"

Автор статьи: Андрей Олегович