Python boolean not 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); >

Если разбираться в реализации на С, то правило выглядит так:

  1. Если True, то True
  2. Если False, то False
  3. Если None, то False
  4. То, что возвращает bool , до тех пор, пока возвращаемый объект является подклассом bool (то, что показывает вызов nb_bool )
  5. Вызов len() на объекте (то, за что отвечают вызовы mp_length и sq_length ):
    1. Если больше 0, то True
    2. В противном случае False

    Все правила 1-3 и 6 достаточно понятны, а вот правила 4 и 5 требуют углубления в детали.

    Определение специального/волшебного метода bool говорит нам, что метод используется для «реализации проверки истинности значений» и должен возвращать либо True, либо False.

    Встроенная функция len() возвращает целое число, представляющее количество элементов в контейнере. Реализация вычисления длины объекта представлена слотами sq_length (длина последовательностей) и mp_length (длина словарей/мэпов).

    Легко подумать, что к объекту можно просто обратиться и запросить его длину, но тут есть два слоя.

    Первый слой – это специальный/волшебный метод len . Как и следовало ожидать, он «должен возвращать длину объекта, целое число >= 0». Но дело в том, что «целочисленный» не означает int , а означает объект, который вы можете «преобразовать без потерь»… к «целочисленному объекту». И как же выполнить это преобразование?

    «Чтобы без потерь преобразовать численный объект в целочисленный», используется специальный/волшебный метод index . В частности, для обработки преобразования используется функция PyNumber_Index() . Эта функция слишком длинная, чтобы ее сюда вставлять, но делает она следующее:

    1. Если аргумент является экземпляром класса int , вернуть его
    2. В противном случае вызвать index на объекте
    3. Если index возвращает конкретный экземпляр класса int , вернуть его (технически возвращать подкласс не рекомендуется, но давайте оставим это в прошлом).
    4. В противном случае вернуть 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.

    Источник

    What is “not True” in Python?

    Python's Not And True Keywords

    In this article, we all going to talk about the not True concept in Python. Generally, it is not a boring lecture rather we are just polishing our basics.

    Operators and keywords in Python

    Python has a large set of operators. That is why, unlike any other programming language, it relatively has lesser complex syntax. Here is a list of operators in Python:

    1. + : Plus
    2. – : Minus
    3. = : assignment operator
    4. == : equal to operator
    5. != : not equal to
    6. >= : greater tha n or equal to
    7. % : modulus
    8. // : floor division
    9. -= : decrement
    10. += : increment
    11. /= : divide
    12. %= : modulus

    These support arithmetic operations but most importantly we have some other ones for logical operations:

    Also, take a look at the boolean operations:

    We can use logical operators either like keywords or as it is. But, in Python, we do not have any operator for not or complement. Obviously, there is “!=” but it is suitable for small operations. For complex manipulations, we can make things simpler using the “not” keyword.

    Significance of “not” in Python

    This example is alone sufficient to prove how “not” is useful:

    Predict whether the while loop will run or not

    condition = not True while(condition): print("Hello world")

    The code will not run. The while loop iterates the code if and only if the condition inside its parenthesis is True. Here the condition is not True means it is False. If you run the small snippet in IDLE this will also give output as False.

    So, this is the significance of a not operator.

    Significance of “True” in Python

    True is a boolean operator in Python. The significance is that one can set flags, run a loop and do many more things with it. Let us see an example:

    Print “hello” n number of times on the screen.

    Hello Hello Hello Hello . . Runtime Error occurred

    The last message is “Runtime Error occurred”. This means that when we run the loop infinitely using True and there is no loop control statement then it goes on executing that piece of code n number of times. This is something to note.

    Using not and True together

    Here we will build a code that checks each number and prints out whether it is prime or not.

    num = int(input("Enter a number: ")) isPrime = not True num_sqrt = int(num**0.5) if(num > 1): for i in range(2, num_sqrt + 1): if (num % i == 0): isPrime = True break if (isPrime == (not True)): print("%d is prime" %(num)) else: print("%d is composite" %(num)) else: print("%d is composite" %(num))
    >>> Enter a number: 39 39 is not prime >> Enter a number: 17 17 is prime

    Explanation:

    1. First, take the input for num.
    2. Then set a variable of name isPrime. This is just an indicator that initially indicates a value that is not True.
    3. Then we take the square root of the number.
    4. Then we put a condition that if the number is greater than 1. It runs a loop that iterates from 2 to (square root of number +1).
    5. Then for each iteration, we check whether the number is divisible by itself. If it is then the indicator isPrime is set to True. This means the number is prime.
    6. If this is not the case then number is composite.

    Here not True works together as False. The main motive to explain is is that we can use it in place of False.

    Conclusion

    In this way, we can use the concept of not True together So, hope this article is helpful and we came to know that we can flexibly use concepts of Python for our benefit.

    Источник

    Читайте также:  Work with lists in python
Оцените статью