Каверзные вопросы по Python

    Мне кажется, в каждом языке программирования есть моменты, которые требуют повышенной концентрации внимания или больше практики для своего понимания. Python в этом плане не исключение, и сегодня я расскажу вам о нескольких каверзных вопросах, с которыми вы можете столкнуться как в повседневной разработке, так и в ходе прохождения собеседования.



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


    Обратите внимание, что первичное объяснение не всегда корректно. Чтобы получить правильный ответ, дочитывайте пример до конца.


    Первый пример


    Очень короткий. Про операторы и порядок их вычислений.


    11 > 0 is True

    С ходу можно интерпретировать это выражение следующим образом:


    • Приоритет операторов сравнения и is одинаков.
    • 11 > 0 — это True.
    • Упрощаем выражение, получая True is True.
    • Всё выражение в итоге станет True.

    На самом же деле это выражение вернёт False.


    Вот еще пара похожих выражений.


    0 < 0 == 0             # False
    1 in range(2) == True  # False

    Также обратите внимание, что расстановка скобок изменит результат.


    (11 > 0) is True         # True
    (0 < 0) == 0             # True
    (1 in range(2)) == True  # True

    Как всегда, никакой магии в этих примерах нет. Приведенные выражения — это chained comparisons, которые следует читать так: a op1 b op2 c ... y opN z эквивалентно a op1 b and b op2 c and ... y opN z.


    Итак, ответ: исходное выражение эквивалентно (11 > 0) and (0 is True), что, очевидно, является ложью.


    Остался вопрос про скобки? Расстановка скобок превращает выражение в обычное, не chained comparisons. То есть благодаря скобкам приоритет смещается на выражение в них, оно вычисляется первым, а затем выполняется вторая операция.


    Второй пример


    В отличие от остальных примеров, незнание следующего факта вряд ли приведёт к ошибке в коде, но знать о таких вещах, мне кажется, как минимум любопытно.


    a = 123
    b = 123
    a == b
    a is b

    Двойное равно проверяет объекты на равенство (и очевидно, что 123 == 123). А оператор is проверяет, что переменные ссылаются на один и тот же объект. a и b — разные объекты, поэтому a is b вернёт False.


    На самом деле в Python есть оптимизация, касающаяся небольших int-ов (от -5 до 256 включительно). Эти объекты загружаются в память интерпретатора при его запуске. Получается небольшой кеш. Из-за этого объект получается один, и результат будет True.


    Аналогичный пример для числа > 256 сработает ожидаемо:


    a = 257
    b = 257
    a == b  # True
    a is b  # False

    Второй пример. Продолжение


    Давайте попробуем копнуть глубже и посмотрим на следующий пример:


    def test():
        a = 257
        b = 257
        print(a is b)
    test()

    257 не входит в кеш, и должно отобразиться False.


    Отличие от предыдущего примера в том, что тут за счёт функции все инструкции интерпретатору подаются единым блоком. Чтобы понять, что происходит, давайте обратимся к байткоду этой функции:


    import dis
    dis.dis(test)

    Мы увидим следующие инструкции:


      2           0 LOAD_CONST               1 (257)
                  2 STORE_FAST               0 (a)
    
      3           4 LOAD_CONST               1 (257)
                  6 STORE_FAST               1 (b)
    
      4           8 LOAD_GLOBAL              0 (print)
                 10 LOAD_FAST                0 (a)
                 12 LOAD_FAST                1 (b)
                 14 COMPARE_OP               8 (is)
                 16 CALL_FUNCTION            1
                 18 POP_TOP
                 20 LOAD_CONST               0 (None)
                 22 RETURN_VALUE

    Предпоследняя колонка — это аргументы для операций. Для LOAD_CONST — это индекс в массиве констант. Поскольку для обеих операций LOAD_CONST подаётся один и тот же индекс, в байткоде у нас лишь один объект, отвечающий за число 257.


    Получается, что интерпретатор способен на подобные оптимизации: код предварительно анализируется, и некоторые константы переиспользуются (float-ы тоже, но не tuple-ы).
    Итак, исходный код выведет True.


    Третий пример


    Этот вопрос однажды встретился мне на собеседовании. Он про классы и методы.


    class C:
        a = lambda self: self.b()
    
        def __init__(self):
            self.b = lambda self: None
    
    c = C()
    c.a()

    Здесь стоит внимательно пройти цепочку вызовов, отличая методы класса от обычных функций.


    Вспоминаем, что вызов метода применительно к экземпляру класса c.method() — это то же самое, что вызов метода применительно к классу с первым аргументом в качестве экземпляра: C.method(c).


    Теперь проверим, во что превратились параметры a и b класса C.


    type(c.a)  # <class 'method'>
    type(c.b)  # <class 'function'>

    Параметр а превратился в метод класса, такой же, как при определении метода внутри класса через def a(self).


    А вот b — это обычная функция, потому что она присваивается атрибуту экземпляра класса, а не определяется (как a) в момент создания класса.


    Получается, что при вызове c.a() мы получаем C.a(c). Тут в качестве аргумента self в метод валидно передастся экземпляр класса. Далее внутри a вызывается функция b. Поскольку это обычная функция, то «автоматической» передачи экземпляра в качестве первого аргумента не произойдёт. И получается, что функция b вызовется без аргументов. Но она требует аргумент! Ведь она задана как lambda self: None. Не обращайте внимание, что аргумент называется self. Это сделано для дополнительного запутывания.


    Итак, ответ:


    Traceback (most recent call last):
      File "<input>", line 1, in <module>
      File "<input>", line 2, in <lambda>
    TypeError: <lambda>() missing 1 required positional argument: 'self'

    Это происходит потому, что функции b не передан аргумент.


    Четвёртый пример


    Он про определение переменных в замыкании. Взят из списка хитрых вопросов с toptall:


    def create_multipliers():
        return [lambda x : i * x for i in range(5)]
    
    for multiplier in create_multipliers():
        print(multiplier(2))

    Кажется, ничего сложного. create_multipliers вернёт список из 5 функций (назовём их list_lamba_f). Каждая list_lamba_f будет умножать свой аргумент на свой индекс в результирующем массиве.


    Получается, что на экране мы увидим:


    0
    2
    4
    6
    8

    Дальнейший разбор предполагает, что вам знакомо замыкание (closure) при использовании вложенных функций (nested functions).


    Свои коррективы в наивное объяснение выше вносит позднее связывание. Согласно ему, значение переменной из замыкания (это переменная i) вычисляется в тот момент, когда вызывается внутренняя функция (наши list_lamba_f).


    Получается, что значение i в list_lamba_f вычисляется в момент вызова multiplier(2) в пятой строчке. Но в этот момент create_multipliers уже отработала целиком. и значение i — это 4. То есть для всех list_lamba_f значение i равно 4.


    Итак, ответ:


    8
    8
    8
    8
    8

    Надеюсь, вам было интересно и понятно. С удовольствием почитаю комментарии с вопросами и задачами, которые кажутся вам полезными для понимания Python и просто занятными. Только не пишите ответ сразу!

    ДомКлик
    Место силы

    Комментарии 45

      +20
      Надеюсь, вам было интересно
      Было интересно. Но главное, чтобы эти вопросы никто не брал на вооружение, чтобы насиловать мозг людям на собеседованиях.
        +1
        Думаю, всегда кто-то будет вооружаться подобным. Несмотря на общее «подобрение» процессов в собесах фанаты садиз… ой, стресс-тестирования, дурацких вопросов и прочего токсика по-любому еще остались и где-то прячутся.
          0

          Кстати, не такие уж это и дурацкие вопросы для собеседования на некоторые позиции. Просто если человек глубоко понимает ответы на них, или способен рассуждениями и пониманием внутренней кухни питона дойти до правильного ответа после небольшого намёка, то человек знает питон и его не нужно спрашивать про какие-то тривиальные вещи.
          Знание отвтетов на эти вопросы, кстати, не говорит о том насколько продуктивный перед вами программист или насколько качественнй он будет писать код, однако глубину понимания языка эти вопросу покажут.

            0

            Такие вопросы мне всё-таки больше нравятся, чем "сколько окон в Москве?" :)
            Ещё однажды просили посчитать сколько в день расходуется деревянных палочек для мороженого во всех макдональдсах столицы. Понимаю, что интересует подход и обоснование методологии, но тут точно можно подобрать задачи на "подумать" из предметной области.

              0

              Наиболее интересен вопрос про скобки, наверное :) и ответ — так писать нельзя и если увижу пойду проверять(читать документацию).
              Даже в sql с его ограниченным набором регулярно можно увидеть op1 and op2 and… and op3 or op4… и призадуматься а того ли хотел автор запроса...

            +3
            очень полезно вынести из всего этого три вывода 1) читайте import this — не просто так написано 2) в сложных условиях скобки — наименьшее зло 3) is используйте для объектов — собственно непонятно, а зачем его использовать для простых типов. Ну и самое главное — все эти хитрые задачки демонстрируют не полезные приемы, а как не надо писать код, ибо если вам с первого взгляда непонятно, что он делает — он явно хреновый.
              +2

              Ну, одно дело — просто сказать "Не делай так", а другое — объяснить, почему. Ну и потом далее по списку:


              Сначала ты не знаешь, что нельзя делать то-то.
              Потом знаешь, что нельзя делать то-то.
              Потом ты понимаешь, что иногда таки можно делать то-то.
              Ну, а потом ты понимаешь, что помимо того-то существует еще шестьдесять шесть способов добиться желаемого, и все из них практически равноправны.
              Когда тебя спрашивают "как мне добиться желаемого", ты быстро перебираешь в уме эти шестьдесять шесть способов, прикидываешь то общее, что в них есть, вздыхаешь и говоришь: "вообще-то, главное — гармония."
              На вопрос обиженных учеников: "а как ее добиться?", ты говоришь: "никогда не делайте то-то".
                0

                Про объекты, из того что пришло в голову, можно к примеру проверять что элементы некоторого множества получены в одном блоке кода (да странная идея но мало ли) при этом только для питона(? а может нет) целочисленные объекты в определённом диапазоне будут вести себя по другому, и исходя из примера про print даже в разработчиках языка(точнее интерпретатора, и ещё бы проаерить как оно будет в компилируемой версии, нет единого мнения о том как оно должно быть...

                +17
                Отличный детектор токсичных собеседований.
                  –2
                  .
                    +3
                    Т.е. в вашей компании придётся разбираться с таким кодом? Я вам перезвоню.
                    PS: единственное, что тут надо знать: что всё это подозрительная муть, которую надо переписать на всякий случай.
                      +1

                      Вообще-то, если компания растит своих джунов, то с таким кодом так или иначе придётся сталкиваться на ревью. Нечего нос воротить.

                        0
                        Когда лямбды начали присваивать переменным класса, у меня закончилось уже второе ведро. Такой код будет весь подсвечен линтером. А про тесты и мультипроцессы можно забыть.
                          +1

                          Дык молодёжь тоже не лыком шита! Опыта мало, но соображалка свежая и работает быстро, юношеский максимализм и беспечность, детская наивность. Жизнь не трепала пятничными релизами с восстановлением базы на все выходные и неаккуратными миграциями с потерей клиентских данных, которая обнаруживается только спустя пару недель…
                          Вот и спрашивает такая кроха: что такое хорошо, а что такое плоха…
                          Надо же не отмахиваться, а четко внятно объяснить что к чему или уж более менее адекватно послать в нужное место документации, хотя бы.
                          Ответ вида: "незнаю почему, но так делать нельзя, еще дет так говаривал..." считаю непрофессиональным. А это значит, что синьор девелопер обязан ориентироваться во всех этих тонкостях, а при поиске такого на собеседовании вполне нормально спросить пар утаких вопросов.


                          Важно только понимать, что невозможно всё знать и помнить, главное уметь думать, искать и разбираться. Так что ответ или отсутствие ответа кандидата по каждому из таких вопросов еще ни о чем не говорит, нужно смотреть в комплексе.

                      +3
                      Ну первый вопрос не выглядит каверзным. Лично я на него не ответил, но лишь потому что я никогда не использовал Питон хоть сколько-нибудь серьезно. Но если бы он входил в мой стек — несомненно, знал бы, поскольку это (насколько я теперь уже вижу) вполне логичная, документированная и используемая фича.

                      А вот второй пример очень похож на баг языка. Оптимизация, которая меняет поведение — это вы серьезно?
                        0
                        да потому что так никто не пишет. Пишут 'a==1', а не ' a is 1'. Это явная попытка нарваться на нестандартное поведение.
                          +2
                          Не оправдание. Всё равно странный дизайн языка.
                          Непонятно: 1 — это все-таки объект или примитив?
                          Если примитив, то почему этот is в принципе работает?
                          Если объект, то какого черта используются такие ломающие оптимизации?
                            –3

                            Это объект. В питоне всё — объект. Однако есть мутабельные и иммутабельные объекты. В данном случаее если сделать "логичнее", то получится "абсурднее". Ну, в смысле, можно сделать отдельную фазу работы транслятора, в которой вес одинаковые константы будут приводиться к одной, но это придётся делать только чтобы устранить "странное" поведение при нецелевом использовании оператора.


                            Это особенность языка, и проявляется она неочевидным образом при нецелевом использовании инструментов. В любом языке, да и вообще в любой отрасли найдутся такие пограничные серые зоны: сравните шуруп забитый молотком и гвоздь закрученный отвёрткой.

                          +1

                          Никто никогда не проверяет значения через is, потому что is проверяет идентичность ссылок, а не значений. Он делает то же самое, что и id(x) == id(y). Через is делают быструю и надежную проверку на None, False, True и т. д.

                            0
                            Проверка is True не будет правильно работать, если проверяемый класс только прикидывается True через перегруженный __bool__. Такое точно есть в логических операциях в SQLAlchemy
                              0

                              Конечно, bool(x) совсем не то же самое что x is True. Последний можно использовать для строгой проверки того, что x — в точности True, а не произвольный True-like объект с реализованным __bool__. Такая строгая проверка на идентичность None/True/False часто используется в сериализаторах, а is None встречается в обычном коде вообще везде.

                          +3
                          С учетом того, что сейчас программист пишет обычно на нескольких языках(c, java, sql, javascipt), полагаться на порядок операторов, это бомба. Я, например, пишу всегда скобки, и не засоряю мозг какой порядок операторов в конкретном языке.
                            +2
                            Параметр а превратился в метод класса, такой же, как при определении метода внутри класса через def a(self).


                            Тут, как мне видится, можно написать еще лучше, заменив человеческое «превратился» на описание того, что происходит. Насколько я помню, «При доступе к атрибуту экземпляра Python создаст объект типа types.MethodType с полями __func__ и __self__, если этот атрибут был найден в классе и является чем-то вызываемым». Ну и сразу будет понятно, почему для «b» превращений не произошло — его в классе не находили, его нашли в экземпляре.
                              0
                              Насколько я помню, «При доступе к атрибуту экземпляра Python создаст объект типа types.MethodType с полями func и self, если этот атрибут был найден в классе и является чем-то вызываемым».

                              Превращение функций в методы (bound methods, они же экземпляры MethodType) происходит еще на этапе создания объекта.

                                0
                                О, это ценно. А не помнишь, это где-то в явном виде написано? Я быстро чекнул в доках и там «при создании»:

                                When an instance method object is created by retrieving a user-defined function object from a class via one of its instances, its __self__ attribute is the instance, and the method object is said to be bound. The new method’s __func__ attribute is the original function object.
                                  +1

                                  Хм, ты был прав. Документация пишет, что


                                  When a non-data attribute of an instance is referenced, the instance’s class is searched. If the name denotes a valid class attribute that is a function object, a method object is created by packing (pointers to) the instance object and the function object just found together in an abstract object: this is the method object.
                                  https://docs.python.org/3/tutorial/classes.html

                                  class A:
                                      def f1(self):
                                          pass
                                  
                                  def f2():
                                      pass
                                  
                                  a = A()
                                  A.f1 = f2
                                  print(a.f1) # <bound method f2 of <__main__.A object at 0x7f0fd200d610>>

                                  Я почему-то был уверен, что это не так ;)

                              +1
                              Согласно ему, значение переменной из замыкания (это переменная i) вычисляется в тот момент, когда вызывается внутренняя функция (наши list_lamba_f).


                              Тут, как мне видится, можно написать еще лучше, заменив абстрактное «вычисляется» на техническую реализацию. Насколько я помню, «Замыкание сохраняется как ссылка на namespace, из которого значение будет получаться по ключу в момент использования (а не в момент замыкания). Все созданные lambda будут замыкать один и тот же namespace (Замыкание сохраняет ссылку на существующий namespace, откуда замыкается какое-то имя. Замыкание не создает копию namespace и ничего из namespace не копирует). Так как после прокручивания range в этом namespace останется последнее значение i=4, то имено оно будет использовано кодом позднее»
                                +1
                                По смыслу пример представляет собой искаженный паттерн lambda x, i=i:… для сохранения i в multiplier.__defaults__
                                  0
                                  Паттерн видится как одно из решений «проблемы». Где «проблема» в том, что при создании нескольких замыканий они получают один и тот же scope, поиск в котором по имени идентификатора будет происходить позже, когда лямбды будут вызывать. Коварный вопрос проблему подчеркивает одним из возможных способов)
                                +4
                                Не нужно таким людей пугать. Тем более на собеседованиях.
                                Нужно использовать хороший статический анализатор, который не допустит ни одной ситуации из списка: github.com/wemake-services/wemake-python-styleguide
                                  +2
                                  Красиво! :) Первую задачу сразу не сообразил, но с подсказки вспомнил про цепи. Остальное — в новинку. Спасибо!
                                    0
                                    Про КДПВ:
                                      0
                                      А есть какое-то обьяснение почему пул малых интов в отрицательную сторону именно до -5?
                                      В чём магичность -5 для питона?
                                        0

                                        Никакой магии нет, этот диапазон закешировали как наиболее часто используемый.

                                          0
                                          Где-то можно почитать на основе чего выбран диапазон до -5 среди негативных чисел?
                                          С 256 довольно очевидно.
                                          А с -5 что-то ни чего толкового не гуглиться.

                                          PS: Нашёл несколько статей — я взял и померял количесвто ссылок на своём проекте. Ок. Возможно в среднем на типовом проекте где-то так и есть, хотя ожидал какого-то более научного обьяснения :)
                                      0
                                      Этот вопрос однажды встретился мне на собеседовании. Он про классы и методы.


                                      Всегда очень с подозрением отношусь к компаниям, которые на интервью спрашивают вопросы вроде: «Вот вам и безобразный и заведомо не работающий код, в котором больше одной ошибки. Какую из них вернет интерпретатор?» — А какая собственно разница? Я готов еще понять, если бы под этим вызовом была бы еще обработка TypeError и NameError с разной логикой, хотя, это еще безобразнее. Сразу вопрос к потенциальному нанимателю: «а какую задачу мне придется выполнять, если надо будет разбирать такой код?». Вы хотите понять, понимаю ли я логику построения и разворачивания цепочки вызовов? Ну так придумайте релевантную практике задачу, где это значимо. Не можете? значит в вашем проекте просто нету таких задач, и этот навык не релевантен вашей позиции, зачем его проверять?
                                        0

                                        В той компании собеседование было на большой монолит с приличной историей. Кода было всякого не мало.
                                        Такие задачи, мне кажется, дают, чтобы проверить "соображалку" и понимание языка. А релевантные примеры обрезаются до минимума, чтобы кандидату не пришлось вникать в условную "бизнес логику", а сосредоточиться на поведении языка.
                                        А какие задачи вам запомнились на собеседованиях? Что хорошо характеризовало работодателя? Что было приятно решать?

                                        0

                                        Нормальные вопросы для собеседования. Но лучше сначала показывать майндфак, а потом предлагать объяснить/пофиксить. За незнание заранее минус не ставить.


                                        Аналогичный пример для числа > 256 сработает ожидаемо

                                        А вот PyPy выдаст True вместо False.


                                        Про LOAD_CONST я бы добавил пояснение, что за массив констант такой. Его можно глянуть в test.__code__.co_consts.


                                        Про метод против функции, как-то не проясняется сам механизм, что дескрипторы используются только при заглядывании в свойства класса, если в экземпляре свойства с таким именем не оказалось. Если дескриптор читается, то вызывается его метод __get__, а такой метод есть у любой функции. Можно делать любопытные вещи:


                                        >>> f = lambda self: self + 1
                                        >>> m = f.__get__(6)
                                        >>> m
                                        <bound method <lambda> of 6>
                                        >>> m()
                                        7

                                        То есть, чтобы исправить пример нужно написать


                                        self.b = (lambda self: None).__get__(self)

                                        тогда функция b привяжется к self и станет методом, в точности как a.

                                          0
                                          Ой помню, как я когда-то еще плохо знал питон и наткнулся на 4й пример. Когда разобрался — думал, что нашел баг в интерпретаторе. Спросил у лида, кому такое репортить, а он мне переписал примерно вот так:
                                          I = 0
                                          def create_multipliers():
                                              for _ in range(5):
                                                  yield lambda x: x * I
                                          
                                          multipliers_generator = create_multipliers()
                                          next(multipliers_generator)(2)
                                          I = 1
                                          next(multipliers_generator)(2)
                                          I = 2
                                          next(multipliers_generator)(2)
                                          ...
                                          

                                          И только тогда до меня дошло)
                                            +2
                                            Меня в свое время очень впечатлил следующий каверзный пример из книги Л. Ромальо:
                                            >>> a = {True : "1", 1 : "one"}
                                            >>> print(a)    #?


                                            Вывод print(a)
                                            >>> print(a)
                                            {True: 'one'}



                                            Объяснение
                                            В python3 тип bool реализован как подкласс инта, поэтому хеши True и 1 (а также False и 0) cовпадают. Для словаря, который использует хеш-функцию как индекс это одинаковые объекты.

                                            Можно пойти еще дальше и вспомнить, как считаются хешы для float:
                                            >>> a = {True : "1", 1 : "one", 1.0 : "double one"}
                                            >>> print(a)
                                            {True: 'double one'}
                                            



                                            Такой вот коварный питон :)
                                              0

                                              Спасибо за интересный пример!

                                                +1

                                                Вот интересно почему true идёт как 1 а не как -1 в доп коде (255/65535....)

                                                +3
                                                Со вторым примером все еще интереснее:
                                                $ python
                                                Python 3.8.3 (default, Jul  2 2020, 16:21:59) 
                                                [GCC 7.3.0] :: Anaconda, Inc. on linux
                                                Type "help", "copyright", "credits" or "license" for more information.
                                                >>> a = 123
                                                >>> b = 123
                                                >>> a == b
                                                True
                                                >>> a is b
                                                True
                                                >>> a = 257
                                                >>> b = 257
                                                >>> a == b  # True
                                                True
                                                >>> a is b  # False
                                                False
                                                >>> 
                                                $ cat 1.py 
                                                a = 65537
                                                b = 65537
                                                print(a == b) 
                                                print(a is b) 
                                                a = 251
                                                b = 251
                                                print(a == b) 
                                                print(a is b) 
                                                $ python 1.py 
                                                True
                                                True
                                                True
                                                True
                                                

                                                Видимо, второй пример работает так как описано только при запуске в интерактивном режиме в интерпретаторе, а при запуске файла — происходит какая-то компилляция в байткод и в ходе нее что-то оптимизируется, так что две переменных с числом 65537 тоже оказываются одним объектом. Что совсем интересно — в байткоде полученом дизассемблированием pyc лежит вот это:
                                                  1           0 LOAD_CONST               0 (65537)
                                                              2 STORE_NAME               0 (a)
                                                
                                                  2           4 LOAD_CONST               0 (65537)
                                                              6 STORE_NAME               1 (b)
                                                
                                                  3           8 LOAD_NAME                2 (print)
                                                             10 LOAD_NAME                0 (a)
                                                             12 LOAD_NAME                1 (b)
                                                             14 COMPARE_OP               2 (==)
                                                             16 CALL_FUNCTION            1
                                                             18 POP_TOP
                                                
                                                  4          20 LOAD_NAME                2 (print)
                                                             22 LOAD_NAME                0 (a)
                                                             24 LOAD_NAME                1 (b)
                                                             26 COMPARE_OP               8 (is)
                                                             28 CALL_FUNCTION            1
                                                             30 POP_TOP
                                                
                                                  5          32 LOAD_CONST               1 (251)
                                                             34 STORE_NAME               0 (a)
                                                
                                                  6          36 LOAD_CONST               1 (251)
                                                             38 STORE_NAME               1 (b)
                                                
                                                  7          40 LOAD_NAME                2 (print)
                                                             42 LOAD_NAME                0 (a)
                                                             44 LOAD_NAME                1 (b)
                                                             46 COMPARE_OP               2 (==)
                                                             48 CALL_FUNCTION            1
                                                             50 POP_TOP
                                                
                                                  8          52 LOAD_NAME                2 (print)
                                                             54 LOAD_NAME                0 (a)
                                                             56 LOAD_NAME                1 (b)
                                                             58 COMPARE_OP               8 (is)
                                                             60 CALL_FUNCTION            1
                                                             62 POP_TOP
                                                             64 LOAD_CONST               2 (None)
                                                             66 RETURN_VALUE
                                                

                                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                Самое читаемое