Наступил 2020 год, а значит, Python 2 перестал поддерживаться. Если быть совсем точным, то основные разработчики уже перестали заниматься веткой, а выход релиза 2.7.18, приуроченный к PyCon US в апреле 2020 года, ознаменует полное прекращение любой активности, связанной с Python 2.


С другой стороны, совсем недавно состоялся релиз Python 3.8, добавивший немного синтаксического сахара в язык. Python 3.9 же ожидается ещё нескоро, да и пока не похоже что добавит в язык что-то интересное.


Так что если вы вдруг ещё не отказались от Python 2, то дальше тянуть смысла просто нет: поддержка второй версии уже прекратилась, а переход сразу на 3.8 позволит использовать язык в его самом актуальном состоянии ещё долгое время.


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


1. Выражения присваивания (Assignment expressions)


Так же известные как «моржовый оператор» (walrus operator) — новый синтаксис, который позволит присваивать значения переменным внутри другого выражения. Это, наверное, самое известное и обсуждаемое из нововведений версии 3.8.


a = 6

# Код ниже присваивает b значение a ** 2
# и проверяет, если b > 0
if (b := a ** 2) > 0: 
    print(f'Квадрат {a} это {b}.') # Квадрат 6 это 36.

Целью введения оператора является повышение читаемости кода, поэтому использовать его (как и все остальные операторы) стоит не при первой же возможности, а только тогда, когда это уместно.


# так делать НЕ НАДО
a = 5
d = [b := a+1, a := b-1, a := a*2]

2. Только позиционные аргументы


Функции могут принимать два типа аргументов.


  • Позиционные — передаваемые по позиции
  • Именованные — передаваемые по имени

В коде ниже, значения обоих аргументов a и b можно передать как по позиции, так и по имени.


def my_func(a, b=1):
    return a+b

my_func(5,2)      # Оба аргумента позиционные
my_func(a=5, b=2) # Оба аргумента именованные

В новой версии Питона можно явно ограничить, какие из параметров могут быть только позиционными или только именованными с помощью символов /  и *  в сигнатуре функции (последний, правда, появился ещё до Python 3.8).


В примере ниже, первые два параметра a и b только позиционные, c и d могут быть любыми, а последние два e и f должны быть именованными.


def my_func(a, b, /, c, d, *, e, f):
    return a+b+c+d+e+f
  
my_func(1, 2, 3, 4, 5, 6)         # ошибка: e, f должны быть именованными
my_func(a=1, b=2, 3, 4, e=5, f=6) # ошибка: a, b должны быть позиционными
my_func(1, 2, c=3, 4, e=5, f=6)   # returns 21
my_unc(1, 2, c=3, d=4, e=5, f=6)  # returns 21

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


3. f-строки 2.0


Сложно описать словами, насколько проще и элегантнее сделали форматирование f-строки, появившиеся ещё в Python 3.6.


Сложно поверить, но Python 3.8 удалось сделать их ещё удобнее. Добавив знак = после имени подставляемой переменной, вы сможете вывести её имя и её значение без дублирования имени.


pi = 3  # В военное время может быть и так

print(f'pi={pi}')  # так мы делали раньше
print(f'{pi=}')     # а так можно делать теперь

Дебаг с помощью print станет ещё удобнее :)


4. Обратный порядок элементов словаря


Итерироваться по словарям теперь можно в обратном порядке с помощью reversed().


5. Получение метаданных из других модулей


Новый модуль importlib.metadata позволит получать метаданные (например, версию) из сторонних пакетов.


6. finally + continue


Раньше нельзя было использовать выражение continue внутри finally из-за сложности в реализации этой фичи. Теперь можно. 


for i in range(2):
    try:
        print(i)
    finally:
        print('Тест.')
        continue
        print('Эту строку вы не увидите.')

# Python <= 3.7
>> SyntaxError: 'continue' not supported inside 'finally' clause
  
# Python 3.8
>> 0
   Тест.
   1
   Тест.

Комментарии (61)


  1. gecube
    09.01.2020 13:45
    +1

    Эти нововведения только лишь мне одному кажутся сомнительными ?


    Аргументирую.


    1. Моржовый оператор открывает дорогу в ад. Будет та же ситуация, что и в Си — поди догадайся в каком порядке вычисляются все подвыражения. Нужна однозначность, а она обеспечивается кодом в "старом" стиле. Если же мы говорим про эффективность интерпретации, ну, так интерпретатор сделайте умным — с JIT, blackjack и куртизанками.


    2. format v2.0 — очень сомнительно. Стало меньше писанины? Ну, немного. Стоило? Нет.


    3. continue в finally — выглядит как возможность создавать недостижимые участки кода. Зачем? В проекте в принципе не должно быть недостижимого кода.


    4. Позиционные аргументы. Ну, оч.странно. Реально у кого-то от этого болело? Проблем с передачей **kwargs между функциями не будет ?


    5. И только пп.4 и 5 выглядят более-менее полезными



    1. andreymal
      09.01.2020 14:14

      По-моему markdown сломал вам нумерацию


    1. SirEdvin
      09.01.2020 14:22

      format v2.0 — очень сомнительно. Стало меньше писанины? Ну, немного. Стоило? Нет.

      Стоило чего? Разработки? Ну, python это open source, так что чуваку, который законтрибутил ок, наверное. А какие минусы?


      continue в finally — выглядит как возможность создавать недостижимые участки кода. Зачем? В проекте в принципе не должно быть недостижимого кода.

      Аналогичное же раньше делалось через bool переменную, так что какая разница?


      Позиционные аргументы. Ну, оч.странно. Реально у кого-то от этого болело? Проблем с передачей **kwargs между функциями не будет ?

      Это стандартизация такая. В CPython методах такое было, а в обычном python нет. Вот исправили.


      1. Regis
        10.01.2020 04:09

        А какие минусы?

        Это становится частью стандарта языка. А значит требует поддержки (усложнит добавление нового функционала в связанном коде) и усложняет альтернативные реализации Python.


    1. WhiteBlackGoose
      09.01.2020 16:42

      Первый пункт — это наоборот счастье. Сколько приходится сталкиваться с


      b = myfunc(a)
      while b > 0:
          a += 1
          b = myfunc(a)

      А теперь я могу написать


      while b := myfunc(a) > 0:
          a += 1

      И кто бы что не говорил про оптимизацию, про костыли вида функции возвращающей значение И изменяющей его же в самой функции, это хорошо. А вот остальное — да, не могу сказать, что прям что-то кардинальное. Форматирование в строках я честно говоря вообще не использую. Дебагпринты? Сомнительный резон.


      1. gecube
        09.01.2020 16:52
        +3

        не могу согласиться, к сожалению, богатый опыт написания кода на С/C++ говорит о том, что это потенциальное UB. В Вашем же примере — первый сниппет четко понятен. Второй — все равно будет внутри преобразован в первый пример, но при этом и выглядит ужасно, и вводит новую сущность, и когнитивная нагрузка на разработчика выше.
        Единственное, где бы, ВОЗМОЖНО, я одобрил применение такого приема — это list comprehension и lambda, да и то не факт


      1. germn Автор
        09.01.2020 18:46
        +4

        Я в таких случаях делаю:

        while True:
            b = myfunc(a)
            if b <= 0: break
            a += 1


        1. vanxant
          09.01.2020 22:49
          -3

          Чем такое вот писать, лучше сразу расчехлить goto. Попробуйте, вам же понятнее будет.


        1. WhiteBlackGoose
          10.01.2020 08:16
          -1

          Не сильно лучше, чем через :=. Точнее имхо хуже


        1. acmnu
          10.01.2020 14:54

          Если уж за красоту бороться, то можно пойти дальше (хоть это и явно медленнее):


          import sys
          
          for a in range(sys.maxint):  # Python 3 range
            b = myfunc(a)
            if b<=0: break

          Кстати, я всегда жалею, что в Python нет обратного if, он так хорошо читается, как раз в таких коротких примерах.


      1. iroln
        10.01.2020 05:56
        +1

        Форматирование в строках я честно говоря вообще не использую. Дебагпринты? Сомнительный резон.

        История эволюции форматирования строк в Python полна боли и отчаяния. Интерполяция, которую завезли в 3.6 — это вершина этой эволюции и в большинстве случаев лучший и к тому же самый быстрый способ (для него сделали отдельную инструкцию в байткоде) форматировать строки. Попробуйте его использовать. Это правда круто и после него не хочется использовать format и %. :)


        Касательно новой "отладочной фичи", совсем недавно это использовал в примерно таком коде с pytest:


        ...
        sizes = ...
        ndims = ...
        ...
        ids = [f'{size=}|{ndim=}' for size, ndim in zip(sizes, ndims)]
        ...
        metafunc.parametrize('size, ndim', [sizes, ndims], ids=ids)

        collected 83 items
        
        test_foo[size=100|ndim=2] .....
        test_foo[size=100|ndim=3] .....
        ...


        1. ratijas
          10.01.2020 13:59

          Вот только нужно понимать, что format и % можно использовать для "отложенной" интерполяции, а f-строки — нет, всегда вычисляются сразу на месте. Отложенная интерполяция полезна, чтобы иметь возможность сохранить шаблон, передать, писать лог с параметрами — в последнем случае особенно хорошо экономится на бесполезной интерполяции, если уровень логгирования окажется ниже минимального.


          1. iroln
            10.01.2020 14:37
            +1

            Я не предлагаю использовать f-строки там где нужно отложенное форматирование, для логгирования или шаблонов. Это, очевидно, специфические случаи.


            logger.info('hello, %s', username)  # logging
            logger.info('hello, {}', username)  # loguru

            Но в случае прямого форматирования строк нет никаких причин отказываться от f-строк.


            'hello, %s' % username
            'hello, {}'.format(username)
            f'hello, {username}'

            Код получается более чистый, лаконичный и понятный (если, не писать в строках длиннющие выражения, конечно).


    1. john_2013
      09.01.2020 18:44
      +1

      1. Думаю нагляднее был бы пример с условием в finally

      if i > 0: 
          continue
      print('test')


      1. gecube
        09.01.2020 19:26

        Да, хороший пример, спасибо.


    1. Zanak
      09.01.2020 20:54
      +4

      Согласен с вами, странные плюшки.
      1. Новый оператор требует, как минимум, тщательного объяснения его применимости. Даже приведенный пример с массивом, сколько людей способно понять, почему в этом случае он не сработает? Извращенный ум и шаловливые рученки могут породить еще массу вариантов, которые нужно будет проверять, прежде чем вставлять в реальный код. И кто выиграет от такого «улучшения»?
      2. Было же простое и понятное правило, сначала идут позиционные, потом именованные аргументы. Зачем и кому потребовалась эта ужесть? Зачем в список параметров функции помещать что — то кроме параметров этой самой функции?
      4. Поправьте меня, если я ошибаюсь, но есть просто dict и есть OrderedDict, который помнит порядок вставки. Если для второго — ок, наверное это кому — то нужно, то для первого ценность нововведения сомнительна, на мой взгляд.

      П. п. 3 и 5 особой антипатии не вызывают, хотя, лично для меня, и особой ценности не представляют.


      1. bruce-willis
        10.01.2020 07:11

        2. рекомендую прочитать pep, где приведен вполне понятная (на мой взгляд) мотивация использования только позиционных аргументов. А также пример, как ужасно приходилось это реализовывать раньше вручную — www.python.org/dev/peps/pep-0570
        4. начиная с 3.7, ключи в словаре упорядочены по порядку добавления — stackoverflow.com/questions/39980323/are-dictionaries-ordered-in-python-3-6.
        Соответственно, обратный порядок тоже может быть полезен


    1. vassabi
      09.01.2020 23:59

      моржовый оператор — это попытка сделать дешевый аналог with для примитивных типов например
      формат — довольно полезная ИМХО фича, особенно если при переименовывании переменной (нет дублирования текста, нет дублирования работы)
      continue — это в примере нет условия, с условием сразу понятна полезность


    1. erty
      10.01.2020 08:58
      +1

      Я правильно понял, что первый оператор позволяет создавать рекурсии, вызывая в себе свою же функцию? Никаких запретов на это нет? Как этот ад отлаживать?!


      1. mayorovp
        10.01.2020 12:26

        Рекурсию и без него можно создать же...


    1. erty
      10.01.2020 08:57

      Я правильно понял, что первый оператор позволяет создавать рекурссии, вызывая в себе свою же функцию? Никаких запретов на это нет? Как этот ад отлаживать?!


    1. erty
      10.01.2020 09:00

      Я правильно понял, что первый оператор позволяет создавать рекурсии, вызывая в себе свою же функцию? Никаких запретов на это нет? Как этот ад отлаживать?!


      1. semen-pro
        10.01.2020 10:36

        3 коммента для наглядности?


        1. ratijas
          10.01.2020 14:01
          +2

          Всё нормально, он ушел в рекурсию…


  1. roller
    09.01.2020 14:50

    С точки зрения обычного рубиста это выглядит так:
    1) Ну наконец-то догадались сделать, и даже оператор присвоения из Паскаля — не слишком плохой вариант
    2) Выглядит как ересь, или призыв сотоны однострочником. Весь мир двигается к именованным аргументам
    3) Странная у вас интерполяция, явно js покусал
    4) А этого не было?!
    5)… все равно мета возможности рубей сломают мозг с большей вероятность (читай: руби тут мощнее)
    6) Ну и хорошо


  1. HeaTTheatR
    09.01.2020 14:53
    +1

    Однозначно, чем дальше — тем хуже!


    1. slavashock
      09.01.2020 15:57

      Можно аргументов? Я например, за 4 и 5 пункт, а остальное надеюсь станет deprecated в ближайшее время.


    1. WhiteBlackGoose
      09.01.2020 16:43
      -1

      Пункт 1 это победа


    1. gecube
      09.01.2020 16:53

      Вопрос тогда только один — а куда, на какой язык перекатываться? Пайтон привлекал и привлекает тем, что у него батарейки включены. И он очень хорошо заменяет баш для задач администрирования. Ну, и весь ML на нем.


      1. 0xd34df00d
        09.01.2020 18:00

        Смотря для чего.


        1. gecube
          09.01.2020 19:26

          автоматизация (сисопс, девопс, гитопс....)
          ML
          написание простых http сервисов


          1. prostofilya
            09.01.2020 20:12
            +1

            Python 2 =)


          1. 0xd34df00d
            09.01.2020 20:37

            Первое и третье я делаю на хаскеле.


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


            1. DarkGenius
              09.01.2020 21:49

              А в чем преимущества писать http-сервисы на Haskell?


              1. 0xd34df00d
                09.01.2020 22:52
                +1

                Ну, начнём с того, что это один из немногих языков, что я знаю :) Не на C++ же сервисы писать, в самом деле!


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


                Ну и вообще весь профит от статически типизированного языка, да.


          1. Falstaff
            10.01.2020 02:04

            Если хочется простые http сервисы и язык без bells and whistles, то вроде Go на такое упирает, там как раз не любят без особой нужды новые фичи включать.


      1. OnYourLips
        09.01.2020 23:15

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


        1. gecube
          09.01.2020 23:44

          Нет, уж, спасибо. Для руби надо быть каким-то одаренным....


  1. gdt
    09.01.2020 16:10

    Что ж, теперь можно и взяться за изучение Python


  1. Andy_U
    09.01.2020 16:33
    +1

    С моей точки зрения, самое интересное нововведение, это multiprocessing.shared_memory.

    P.S. Сам, правда, еще не пробовал ;)


  1. imengineer-org
    09.01.2020 16:37
    +2

    Спасибо за обзор. В чужом коде буду узнавать, если увижу. У меня самого энтузиазма новые фичи не вызвали.


  1. YuriM1983
    09.01.2020 20:48
    +8

    def my_func(a, b, /, c, d, *, e, f):
        return a+b+c+d+e+f  
    my_func(1, 2, 3, 4, 5, 6)
    Это вообще ужасно выглядит. Как будто у функции 8 параметров, но указывать надо 6.

    # так делать НЕ НАДО
    a = 5
    d = [b := a+1, a := b-1, a := a*2]
    А вот и задачки для горе-собеседований подъехали.


  1. Arcpool
    09.01.2020 23:02

    А когда порядок элементов словаря стал определённым, что понадобился reversed() или это для OrderedDict() ?


    1. Mingun
      10.01.2020 00:01
      +2

      С версии 3.7 Python'ий dict помнит порядок вставки своих элементов.


      Dictionaries preserve insertion order.
      Словари сохраняют порядок вставки элементов.


      1. ratijas
        10.01.2020 14:05

        На самом деле… порядок вставки стал запоминаться ещё с версии Python 3.5 или 3.6, в с версии Python 3.7 его сделали обязательной частью спецификации языка.


    1. vassabi
      10.01.2020 00:02

      а при чем там определенность порядка? Порядок элементов даже может быть случайным — и тогда reversed тоже будет случайным (только в обратном порядке).


      1. Mingun
        10.01.2020 00:08

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


        1. vassabi
          10.01.2020 00:35

          я не про «с версии 3.7», я про логику — что
          reversed([a for a in dict]) равен [a for a in reversed(dict)]


          1. Mingun
            10.01.2020 17:04

            А, мне просто показалось, что вы мне отвечали (у меня были проблемы с приходом уведомлений об ответах на почту и я в попытках исправить проблему поставил лишние галочки в настройках, в результате чего на почту стали приходить уведомления обо всех комментариях в теме, а не только о тех, которые являются ответом мне. Но сразу я этого не заметил и получилось так, что я написал комментарий, а минуту спустя на него прилетел «ответ»). Так-то, конечно, вы целиком и полностью правы.


    1. noth
      10.01.2020 00:57
      +2

      вроде как начиная с 3.6 в словари начали сохранять порядок (но это вроде была побочная фича)
      с 3.7 это гарантируется официально:

      What’s New In Python 3.7

      the insertion-order preservation nature of dict objects has been declared to be an official part of the Python language spec.


  1. S0mbre
    10.01.2020 01:45

    Зачем спорить, какие фичи нужны или не нужны языку? Мне кажется более правильным стратегия, когда язык гибкий и поддерживает много всего, а уже корректное применение инструментов — на совести программистов. Кому-то действительно понадобится присваивание в условиях (на C/C++ я вообще постоянно эти пользовался), а кому-то будет невлом написать лишнюю строчку кода для большей наглядности. Кто-то пишет для себя, кто-то — для сообщества, кто-то — для заказчика. Везде разные исходные условия, разные стили программирования и разные задачи. Язык — он ведь и есть язык, должен быть живым. Как мы с вами по-русски говорим, но все со своими особенностями, выговором, любимыми поговорками и т.д., так и на языке программирования. Какие проблемы? Компилятор все равно все оптимизирует и переварит в почти один байт-кот… ))


    1. gecube
      10.01.2020 09:02

      Чой-то хабр сгрыз мой ответ.
      Поэтому дублирую.


      Только вот не задача. Ядро языка должно быть минимальным. Так сказать — минимальный набор возможностей для решения максимального количества задач. Потому что если мы ядро языка раздуваем, то как выше коллега заметил — компилятор/интерпретатор языка становится сложнее и действительно нужные фичи становится сложнее добавлять. + Скорость интерпретации/компиляции падает (отличный пример — скорость компиляции с++, можно успеть состариться, пока напичанный на нем проект соберётся) + сложность автоматической кодогенерации (а кто сказал, что она не нужна?)
      А самое неприятное, что каждая не очень продуманная фишка языка приводит к новым неочевидным эффектам.
      Вот в сообщении ttpss://habr.com/ru/post/483276/#comment_21108246 очень хороший пример. И не надо говорить, что адекватные люди такой код писать не будут, ок?


      Я уж не говорю, что сравнивать естественные языки, на которых мы говорим, и языки программирования немного странно. Первые действительно развиваются, но и стоимость изменений в них не такая большая, как в ЯП. Лучше тогда уж сравнивать с искусственными языками. Ну, тем же эсперанто. И сильно ли он популярен? Была на него мода… Но вроде все уже пришли к тому, что язык интернационального общения — ломаный английский.


      И смотрите какая интересная штука. Принципиально — за последние лет 40 ничего нового не придумали. Циклы? Так они и тогда были. Функции? Классы? Ввод-вывод? Ну, разве что ФП пошло в массы, но это как-то очень совпало с распространением действительно распределенных систем. Ну, и какие тогда реальные фичи внедрять, кроме синтаксического сахара?


      1. iroln
        10.01.2020 09:28

        Ну, и какие тогда реальные фичи внедрять, кроме синтаксического сахара?

        Касательно CPython, думаю, преодоление проблем/наследия GIL и отсутствия полноценной многопоточности, реализация JIT с опциональной статической типизацией. В общем, двигаться в сторону повышения производительности, потому как с удобством и скоростью программирования на Python уже сейчас всё очень хорошо.


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


  1. iroln
    10.01.2020 06:07
    +1

    Итерироваться по словарям теперь можно в обратном порядке с помощью reversed().

    Вот такой код в бетах и 3.8.0 вызывал сегфолт:


    list(reversed({}))  # or next(reversed({}))

    В 3.8.1 поправили. Если используете 3.8.0 и reversed dict, лучше обновиться. :)


  1. LinearLeopard
    10.01.2020 11:44
    +1

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


    Некоторые ф-ции из стандартной библиотеки имеют такую сигнатуру, например bool.

    Changed in version 3.7: x is now a positional-only parameter.


    Что выглядит вполне логичным, действительно, какие имя дать этому параметру? `o`, `x`, `a`?

    И вполне логично запретить вызов

    bool(a=x)


    1. gecube
      10.01.2020 12:48

      Ну, это же чушь? Повторюсь, а если я **kwargs передаю через цепочку вызовов? То мне делать дополнительные конвертации?


      bool(a=x)


      а это пускай просто проверяет единственный аргумент на то True или False. Если передали больше одного аргумента — ошибка выполнения.


      1. andreymal
        10.01.2020 13:08
        +2

        Давайте с самого начала: зачем вы вообще передаёте **kwargs без *args?


      1. LinearLeopard
        10.01.2020 14:53

        Ну, это же чушь? Повторюсь, а если я **kwargs передаю через цепочку вызовов? То мне делать дополнительные конвертации?

        Ну если вам не надо, то никому не надо, это очевидно.

        а это пускай просто проверяет единственный аргумент на то True или False. Если передали больше одного аргумента — ошибка выполнения.


        Там более весёлый механизм.

        И вы не перепутали тело ф-ции и сигнатуру?


  1. Bytamine
    10.01.2020 13:29
    -3

    a = 6
    
    # Код ниже присваивает b значение a ** 2
    # и проверяет, если b > 0
    if (b := a ** 2) > 0: 
        print(f'Квадрат {a} это {b}.') # Квадрат 6 это 36.

    Тут либо условие, либо print неправильные.


    1. mayorovp
      10.01.2020 15:20
      +2

      А что именно тут неправильно?


  1. olekl
    10.01.2020 14:21
    -1

    continue в finally как-то странно. Во-первых, не очень понятно зачем, во-вторых, если я правильно понял как оно работает, то логичнее там был бы break…