image

Новую версию Python многие разработчики ожидали с нетерпением. Кто-то и не ждал, поскольку вполне достаточно было возможностей прежней версии. Но Python 3.12 таки выпустили, сейчас стало известно, что эту ветку будут поддерживать полтора года, а потом для нее станут формировать исправления для устранения уязвимостей — в течение еще трех с половиной лет.

Кроме того, запущен процесс альфа-тестирования Python 3.13, где заявлен режим сборки CPython без глобальной блокировки интерпретатора (GIL, Global Interpreter Lock). Тестировать ветку будут в течение семи месяцев, исправляя ошибки и добавляя новые возможности. Затем еще три месяца — тестирование бета-версий и еще через два месяца появится пред-финальная версия. Но будет потом, а сейчас поговорим о том, что уже есть в руках — о Python 3.12.

Так что мы получили?


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

  • Улучшена эффективность использования ресурсов многоядерных систем за счет добавления поддержки изолированных субинтерпретаторов и отдельных глобальных блокировок (GIL, Global Interpreter Lock) для разных интерпретаторов внутри процесса (CPython позволяет в одном процессе выполнять запускать сразу несколько интерпретаторов). Правда, сейчас эта новинка доступна посредством C-API, а поддержка Python API будет добавлена в следующей ветке.
  • Также добавлена поддержка подсистемы ядра Linux perf в интерпретаторе. Она дает возможность определять имена Python-функций при профилировании при помощи утилиты perf (ранее в трассировках определялись только имена Си-функций).
  • Еще одно важное изменение — повышение информативности сообщений об обшибках. Кроме того теперь спектр исключений с предложением рекомендации по устранению опечаток расширен. Вот пример:

   sys.version_info
   NameError: name 'sys' is not defined. Did you forget to import 'sys'?

   somethin = blech
   NameError: name 'blech' is not defined. Did you mean: 'self.blech'?

   import a.y.z from b.y.z
   SyntaxError: Did you mean to use 'from ... import ...' instead?

   from collections import chainmap
   ImportError: cannot import name 'chainmap' from 'collections'. Did you mean: 'ChainMap'?

  • Также продолжается активная работа по оптимизации производительности. Она понемногу движется и есть первые результаты — согласно анонсу производительность стала на 5% выше. Не бог весть что, но тоже неплохо. Повышения удалось добиться благодаря:
    • Добавлении поддержки бинарного оптимизатора BOLT, который и повышает производительность на 1-5%.
    • Добавлено inline-развёртывание списковых включений (comprehensions). Это нововведение дало возможность ускорить работу со списковыми включениями.
    • Размер Unicode-объектов снизили на 8-16 байт.
    • Увеличена скорсть операций с регулярными выражениями re.sub(), re.subn() и re.Pattern.
    • Ускорено выполнение проверок isinstance() для некоторых протоколов — от 2 до 20 раз.
    • Также ускорены функции tokenize.tokenize() и tokenize.generate_tokens().




  • Предложен более компактный синтаксис аннотирования типов для обобщенных классов и функций. Вот пример:

  def max[T](args: Iterable[T]) -> T:
       ...

   class list[T]:
       def __getitem__(self, index: int, /) -> T:
           ...

       def append(self, element: T) -> None:


  • Появился новый способ определения псевдонимов типов при помощи выражения type. Еще пример:

   type Point = tuple[float, float]
   type Point[T] = tuple[T, T]

  • Также в модуле typing добавлен новый декоратор override. Он информирует системы проверки типов о том, что метод в подклассе предназначен для переопределения метода или атрибута в суперклассе. И еще один пример:

   class Base:
       def log_status(self) -> None:
           ...
   
   class Sub(Base):
       @override
       def log_status(self) -> None:  # Ok, переопределяет Base.log_status
           ...

       @override
       def done(self) -> None:  # Система проверки типов выявит ошибку
           …

  • В новой версии увеличена гибкость разбора f-строк (форматируемые литералы с префиксом 'f'), что открыло новые возможности, включая возможность указаний любых допустимых для Python выражений, включая многострочные выражения, комментарии, обратные слэши и escape-последовательности для Unicode. Во внутренней строке можно повторно использовать двойные кавычки, не применяя одинарные. А еще увеличена информативность сообщений в f-строках, в которых теперь указывается точное место в строке, вызвавшее ошибку. Вот пример:

   print(f"This is the playlist: {"\n".join(songs)}")

   print(f"This is the playlist: {"\N{BLACK HEART SUIT}".join(songs)}")

   print(f"This is the playlist: {", ".join([
   ...     'Take me back to Eden',  # My, my, those eyes like fire
   ...     'Alkaline',              # Not acid nor alkaline
   ...     'Ascensionism'           # Take to the broken skies at last
   ... ])}")

  • Улучшена и безопасность. Так, встроенные реализации алгоритмов SHA1, SHA3, SHA2-384, SHA2-512 и MD5 в hashlib заменены на формально верифицированные варианты от проекта HACL* (встроенные реализации применяются только если отсутствует OpenSSL).
  • Добавлена защита от переполнения стека в Cpython.
  • В модуле pathlib.Path реализована поддержка подклассов, а в модуле os теперь расширена поддержка Windows. В частности, для этой ОС добавлена поддержка методов os.listdrives(), os.listvolumes() и os.listmounts(), а также повышена точность os.stat() и os.lstat().
  • Кром того, в модули sqlite3 и uuid добавлены интерфейсы командной строки («python -m sqlite3» и «python -m uuid»).
  • Кроме новых возможностей разработчики решили почистить некоторые старые, которые уже не нужны. В частности, убраны модули asynchat, asyncore, smtpd, imp и distutils (модуль distutils можно использовать из пакета setuptools). Убраны устаревшие методы в unittest. Избавились и от устаревших и некорректно работающих функций, классов и методов. В первую очередь, это locale.format(), io.OpenWrapper, ssl.RAND_pseudo_bytes(), ElementTree.Element.copy(), hashlib.pbkdf2_hmac(), gzip.GzipFile.
  • Удалена поддержка таких браузеров, как Grail, Mosaic, Netscape, Galeon, Skipstone, Iceape, Firebird, и Firefox до версии 36.

Другие полезные материалы


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


  1. CrazyOpossum
    03.10.2023 01:44
    +1

    Удалена поддержка таких браузеров, как Grail, Mosaic, Netscape, Galeon, Skipstone, Iceape, Firebird, и Firefox до версии 36.

    Это шутка? Или как вообще браузеры поддерживались в языке?


    1. DevAdv
      03.10.2023 01:44
      +12

      1. Nansch
        03.10.2023 01:44

        Ну это же программная поддержка через библиотечную функцию. На уровне языка никакие браузеры, конечно же, не поддерживаются.


        1. CrazyElf
          03.10.2023 01:44
          +5

          Стандартные библиотеки питона - это его неотъемлемая часть.


  1. Finesse
    03.10.2023 01:44
    -2

    Удалена поддержка таких браузеров, как…

    Каким образом Python поддерживает браузеры?


    1. blueboar2
      03.10.2023 01:44

      Может открыть веб-страницу, и указать, в каком именно браузере (в модуле webbrowser)


    1. marat2509
      03.10.2023 01:44
      +1

      https://habr.com/ru/companies/selectel/articles/761914/#comment_26022506

      https://docs.python.org/3/library/webbrowser.html


  1. jpegqs
    03.10.2023 01:44
    +3

    A "basic JIT compiler for short regions" might be added to CPython 3.12 to compile only tiny sections of specialized code...

    Так JIT добавили или нет, или Гвидо обманул? Помню старые новости, где это обещалось.


    1. Sap_ru
      03.10.2023 01:44
      +6

      Нет. Забоялись. Пришлось перетрусить много op-codes (а это само по себе ой-ой-ой изменение), притулить оптимизатор (что тоже ой-ой-ой), и ещё кучу всего. Поэтому всю подготовительную работу, вроде, сделали, а сам JIT добавлять не стали и перенесли на следующий релиз, так как он задержал бы выход этого релиза.
      Могут и дальше задерживать, так как там сейчас (наконец-то) очень серьёзная работа по избавлению GIL началась, что требует переписывания кучи кода, подсистем и библиотек. И пока всё идёт к тому, что блокирующие изменения будут просто откладываться до выпуска интерпретатора с режимом без GIL, так потом всё равно всё переписывать нужно будет.


  1. Sap_ru
    03.10.2023 01:44
    +9

    "Добавлено inline-развёртывание списковых включений (comprehensions). Это нововведение дало возможность ускорить работу со списковыми включениями."
    Вот эта штука ускоряет многие часто используемые вещи вроде list(some_generator) вещи в два раза и больше. 5% прироста это они поскромничали, на некоторых задачах там 15% прироста. Особенно если переписать циклы и проверки под новые присвоения переменных в выражениях.
    Вместе в новым оптимизатором в сумме очень хороший прирост получается.


    1. CrazyElf
      03.10.2023 01:44
      +1

      Если кого-то беспокоит скорость работы циклов, то нужно либо менять алгоритм, либо переделать код под Numba, эффект будет гораздо больше.


  1. Sap_ru
    03.10.2023 01:44
    +4

    А почему о ещё одной киллер-фиче не написали?
    while( ( a:=some_data_read_method() ) is not None ):
    print(a)

    Вместе с новыми дженериками, f-строками (которые больше не кривой велосипед, а именно то, что должно быть) и foward types язык снова стал современным. Не хватает только null-check оператора.


    1. AMDmi3
      03.10.2023 01:44
      +4

      Потому что assignment expressions появились ещё в 3.8?


      1. ritorichesky_echpochmak
        03.10.2023 01:44
        +9

        А коробит от := до сих пор, как и от for...else - лютая же наркомания


        1. Yuribtr
          03.10.2023 01:44
          +3

          Личное ИМХО: оператор "морж" неплохо так сокращает обьем кода и удобен для валидации большого количества параметров, стараюсь его использовать везде где поддерживается:

          def process_message(message):
              if not (value := extract_value(message)):
                  raise ValueError('Value is absent')
          
              if code := extract_code(message):
                  process_code(code)         
          
              ...

          А по поводу for ... else есть кейсы в которых значительно сокращает объем кода. Например вот такая задача по очистке списка от невалидных данных. Ее конечно можно решить разными способами, но вот мне такое решение показалось более изящным и понятным:

          list_a = [
              [1, 2, 3, 4],
              [1, 2, '3', 4],
              [1, 2, 3, 4],
          ]
          
          list_c = []
          
          for list_b in list_a:
              for item in list_b:
                  if not isinstance(item, int):
                      break
              else:
                  list_c.append(list_b)
          
          print(list_c)


          1. iliazeus
            03.10.2023 01:44
            +16

            В вашем примере очень неочевидный control flow, из-за чего не сразу понятно, что он вообще делает. Мне кажется, вот так и короче, и понятнее:

            for list_b in list_a:
              if all(isinstance(item, int) for item in list_b):
                list_c.append(list_b)
            

            Так что ваш пример получился не в пользу for-else :)


            1. leshabirukov
              03.10.2023 01:44
              -3

              А можете за один проход, чтобы вот такое тоже работало?

              def gen4():
                  yield from range(4)
              
              list_a = [
                  (1, 2, 3, 4),
                  [1, 2, '3', 4],
                  [1, 2, 3, 4],
                  gen4()
              ]
              
              list_c = []
              for list_b in list_a:
                if all(isinstance(item, int) for item in list_b):
                  list_c.append(sum(list_b))
                    
              print(list_c)
              [10, 10, 0] # ,а хотеть [10,10,6]

              ( Код @Yuribtr -а тоже опустошает генератор )


              1. iliazeus
                03.10.2023 01:44
                +2

                Я не спорю, что условие задачи можно поменять так, чтобы именно тот синтаксис был уместным. Более читаемым он от этого не станет.

                Можно написать так
                for iterator in iterators:
                  total = 0
                
                  for item in iterator:
                    if isinstance(item, int):
                      total += item
                    else:
                      break
                
                  else:
                    result.append(total)
                
                Можно так
                for iterator in iterators:
                  total = 0
                
                  for item in iterator:
                    if not isinstance(item, int):
                      break
                
                    total += item
                
                  else:
                    result.append(total)
                

                Хотя мне больше нравится такой вариант:

                for iterator in iterators:
                  valid = True
                  total = 0
                
                  for item in iterator:
                    if not isinstance(item, int):
                      valid = False
                      break
                
                    total += item
                
                  if valid:
                    result.append(total)
                

                В нем одновременно есть и "отрицательное условие" if not ...: break (потому что именно так выглядит привычный паттерн проверки предусловий), и нет визуальной путаницы, к чему именно относится else (повторюсь, по другим языкам мне намного привычнее ассоциировать его с ближайшим if, а не с циклом).


                1. leshabirukov
                  03.10.2023 01:44
                  +1

                  Да, наверное так - лучше всего. Если честно, я и сам не придумал, как сделать чистый pipe. Понятно, что в общем случае, последующий конвейер должен ждать, пока не станет ясно, что строка валидная.


              1. ritorichesky_echpochmak
                03.10.2023 01:44

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

                for list_b in list_a:
                    arr = list(list_b)
                    if all(isinstance(item, int) for item in arr):
                        list_c.append(sum(arr))


                1. leshabirukov
                  03.10.2023 01:44

                  Ну, это может быть библиотечная функция, которая, в частности, обрабатывает файл на проходе посимвольно, еще и фильтруя строки по какому-то принципу.


                  1. ritorichesky_echpochmak
                    03.10.2023 01:44

                    Так посимвольно или построчно? Там есть нюанс: читать файл побайтово - дико дорого и почти бессмысленно. Читать побайтово по сети - ещё дороже. Из базы данных - вообще безумие. Без практически ценной имплементации (какого-то реального кейса) этих костыликов, которая бы ЯВНО выигрывала у читаемых нормально вариантов всё это бессмысленно


            1. Yuribtr
              03.10.2023 01:44

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

              def process_codes(codes_list: list):
                  result = []
                  for codes in codes_list:
                      for code in codes:
                          if not isinstance(code, int):
                              logging.warning(f'Found wrong code {code}')
                              break
                      else:
                          result.append(codes)
                  return result

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

              В вашем примере очень неочевидный control flow, из-за чего не сразу понятно, что он вообще делает

              Ну это вопрос привычки. Привыкаешь к такому синтаксису и потом легко читать.


              1. iliazeus
                03.10.2023 01:44

                def validate_codes(codes):
                  for code in codes:
                    if not isinstance(code, int):
                      warn(f"wrong code: {code}")
                      return False
                  return True
                
                def extract_valid_codes(codes_list):
                  return [c for c in codes_list if validate_codes(c)]
                

                Ну это вопрос привычки. Привыкаешь к такому синтаксису и потом легко читать.

                Я не спорю, что я не привык. Но мне кажется, что это одна из наиболее непривычных фич Python, и я не уверен, что эта непривычность оправдана.


          1. ritorichesky_echpochmak
            03.10.2023 01:44

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

            Второй пример вообще выглядит как издевательство

            for list_b in list_a:
                if all(isinstance(item, int) for item in list_b):
                    list_c.append(list_b)

            А если Python не единственный язык разработчика (а возможно и не основной), то всё становится сильно трагичнее


            1. iliazeus
              03.10.2023 01:44
              +6

              Иронично, что мы написали прям дословно одинаковый код в одновременных комментариях :)

              Еще одно подтверждение тому, что такой вариант куда более интуитивен, чем for-else.


              1. AMDmi3
                03.10.2023 01:44
                +1

                Любая инвертированная логика неинтуитивна, а for..else скорее всего задумывался для прямолинейной логики поиска, которая выглядит вполне логично:

                for x in iterable:
                    if predicate(x):
                        break
                else:
                    raise NotFoundException()
                # work with x
                

                И я бы даже предпочёл её более современному функциональному коду, который в питоне получается отвратителен:

                if (x := next(filter(predicate, iterable)), None) is None:
                    raise NotFoundException()
                
                if (x := next((i for i in iterable if predicate(i)), None) is None:
                    raise NotFoundException()
                
                try:
                    x = next(filter(predicate, iterable))
                except StopIteration:
                    raise NotFoundException()
                
                from more_itertools import first_true  # лучше, но нужен внешний модуль
                if (x := first_true(iterable, pred=predicate)) is None:
                    raise NotFoundException()
                


                1. iliazeus
                  03.10.2023 01:44
                  +1

                  Ваш первый вариант мне не нравится тем, что для меня ключевое слово break не несет смысл возврата значения. Если бы в вашем примере predicate не назывался так очевидно, я бы сначала подумал, что эта функция как-то меняет или потребляет значения из iterable, и возвращает флаг "дайте мне еще данных".

                  Наверное, это дело привычки. Я не привык писать на Python, поэтому паттерны с for-else не узнаю сразу.

                  В идеали, я бы хотел иметь find в стандартной библиотеке, работающий как more_itertools.first_true. И писал бы так:

                  x = find(iterable, predicate)
                  if x is None:
                    raise NotFoundException()
                  

                  А из ваших вариантов мне ни один не нравится, что с for-else, что другие. Если выбирать только из существующих способов, сделал бы как-то так:

                  x = filter(predicate, iterable)
                  x = next(x, None)
                  if x is None:
                    raise NotFoundException()
                  

                  Но все равно некрасиво.


                  1. fireSparrow
                    03.10.2023 01:44

                    Я бы написал так:


                    found = any(predicate(x) for x in iterable)
                    if not found:
                      raise NotFoundException()

                    или так


                    found = any(map(predicate, iterable))
                    if not found:
                      raise NotFoundException()


                    1. iliazeus
                      03.10.2023 01:44

                      Наверное, из нашей ветки это не очень понятно, но я (и @AMDmi3, думаю, тоже) предполагал, что найденный элемент будет еще где-то потом использоваться. В ваших вариантах только проверяется наличие элемента: any() возвращает bool.

                      Грубо говоря, представьте, что после всех наших отрывков кода стоит return x.


                  1. ritorichesky_echpochmak
                    03.10.2023 01:44

                    Даже хотя бы

                    x = next(filter(predicate, iterable), None)
                    if x is None:
                      raise NotFoundException()

                    будет куда лучше, чем эти канделябры. Хотя в других языках было бы что-то вида

                    x = next(filter(predicate, iterable)) 
                      ?? throw NotFoundException()
                    
                    # в пайтоне ожидаешь что-то вида
                    x = next(filter(predicate, iterable), None) 
                      or raise NotFoundException()

                    потому что в одну строку - хуже читается. Почему из пайтона нельзя вместо None бросить исключение в next - куда более интересный вопрос, чем все эти моржекостыли


            1. NN1
              03.10.2023 01:44

              В C# “out var” работает похожим образом и ничего, удобно и читаемо.

              Может потому, что есть ещё «out» за который легко зацепиться глазами.


              1. ritorichesky_echpochmak
                03.10.2023 01:44

                out var часто не рекомендуют использовать, если есть другие варианты. А с появлением кортежей они есть. А до их появления рекомендовалось вернуть объект, если уж надо прям много и разного вернуть. Плюс в C# протерянная переменная, или объявленная в двух местах или с не тем типом громко упадёт ещё при сборке, т.е. настолько забагованную вещь ты не сможешь отдать в прод даже если тесты продолбаны


                1. NN1
                  03.10.2023 01:44

                  Это да, объявить повторно не получится.

                  Я тут поискал может MyPy как-то может спасти ситуацию но не нашёл.

                  Разве что с аннотацией typings.Final объявить.


            1. Yuribtr
              03.10.2023 01:44
              +1

              Очень не очевидно, почему переменная проинициализированная в блоке проверки видна вне блока

              Разверните свою мысль пожалуйста. Может у меня глаз замылился, не вижу проблемы

              Второй пример вообще выглядит как издевательство

              Чуть выше ответил. Да, чтобы убрать лишнее - сильно упростил пример. Но как вы понимаете в реальном приложении будет много чего в этих циклах. Но опять же это дело вкуса и сформировавшейся привычки. Просто хотел отметить что ваша фраза:

              А коробит от := до сих пор, как и от for...else - лютая же наркомания

              реально вызвала недоумение. Этот синтаксический сахар реально помогает, улучшая читаемость. Но если не нравится - вас никто не заставляет пользоваться )


              1. ritorichesky_echpochmak
                03.10.2023 01:44
                +1

                Давайте я вас лучше наведу на мысль: почему бы тогда этому коду не быть корректным

                for variable in array:
                  pass
                
                print("Latest: ", variable)

                Аргументация должна быть немножко интереснее, чем "это синтаксический сахар, значит надо пользоваться". Я пишу на .NET/C#, TypeScript (по сути JS), немного Kotlin... и потом переключаюсь на какой-нибудь скрипт для отчётов, а там вот такие синтаксические извращения не поддающиеся никакой логике, не переводящиеся на человеческий язык никаким боком. Как это читать? Для всех переменных из массива считаем что-то, иначе... какое иначе? Что иначе? Почему иначе? Иначе это нельзя было назвать хотя бы? Может это finally? Хотя там много других прекрасных слов можно было найти: another, otherwise... нейтив спикеры могли бы подсказать много куда более удачных вариантов

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


                1. Mingun
                  03.10.2023 01:44

                  Давайте я вас лучше наведу на мысль: почему бы тогда этому коду не быть корректным

                  Вам довольно прозрачно намекнули, что в изначальном примере кода c for ... else нет «переменной, проинициализированной в блоке проверки и видной вне блока». Так что вот и не понятно, что в этом синтаксисе вас не устраивает.


                  1. ritorichesky_echpochmak
                    03.10.2023 01:44
                    -1

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

                    if not (value := extract_value(message)):
                        raise ValueError('Value is absent')

                    и второй, про for...else. И я в ответе достаточно очевидно отделил ответ на второй пример. Написать про второй пример, что он второй пример - это не достаточный для кого-то намёк что он второй? Серьёзно? Тогда `:=` действительно наименьшая из проблем...


        1. NN1
          03.10.2023 01:44
          +5

          := довольно логичный, а вот for else на мой взгляд не такой.

          Всегда кажется что else это если не прошёлся по коллекции.


          1. redfox0
            03.10.2023 01:44

            Вот-вот, тем более джанговский for … empty в шаблонизаторе работает именно так.


    1. ababo
      03.10.2023 01:44

      while( ( a:=some_data_read_method() ) is not None ):

      Киллер фичи, которые мы заслужили...


  1. ritorichesky_echpochmak
    03.10.2023 01:44
    +2

    Убраны устаревшие методы в unittest

    А там что-то не устаревшее есть? Параметризованные тесты оно не умеет и в целом после Pytest выглядит как что-то максимально бесполезное. Лучше бы Pytest в основную кодовую базу втащили


    1. dolfinus
      03.10.2023 01:44
      +1

      pytest от этого станет медленнее развиваться, релизы же будут вместе с интерпретатором. И с поддержкой предыдущих версий Python будут сложности.


  1. AshBlade
    03.10.2023 01:44
    +1

    Ну все. Скоро PHP будет мертв


    1. isden
      03.10.2023 01:44
      +24

      шо_опять.png


  1. 0Bannon
    03.10.2023 01:44

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


    1. iliazeus
      03.10.2023 01:44
      +1

      Так наоборот же, в этом релизе многое стало проще:

      • убрали депрекейтнутые функции (unittest, webbrowser)

      • добавили более адекватный синтаксис (параметры дженериков, type, override)

      • убрали неочевидные ограничения, о которые все спотыкались (парсинг f-строк)

      • увеличили полезность сообщений об ошибках


      1. iuabtw
        03.10.2023 01:44
        +10

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


        1. iliazeus
          03.10.2023 01:44
          +6

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

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


          1. iuabtw
            03.10.2023 01:44
            +3

            А можете показать какой-нибудь ежедневный пример f-строки?

            Я просто, как старовер, не люблю в них писать ничего сложнее условного f"hello {var}". Интересно, как ими пользуются коллеги по цеху в более сложных случаях.

            Про код стайл согласен. Была бы возможность - настроил бы линтеры по вкусу.


            1. iliazeus
              03.10.2023 01:44
              +2

              Я не очень много пишу именно на Python, больше на JS/TS, в котором таких ограничений нет. Поэтому спотыкаюсь в простых случаях:

              # здесь нужны другие кавычки
              f"order id: {order["id"]}"
              
              # здесь нельзя бекслеш
              f"""
              some header
              {'\n'.join(rows)}
              """
              

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


              1. NN1
                03.10.2023 01:44
                +1

                А разве в первом случае в JS не нужны другие кавычки тоже ?

                Обратную косую черту можно обойти дополнительной переменной хотя конечно и не так удобно.

                nl = '\n'
                print(rf"""
                {nl.join(["a", "b"])}
                """)"""


                1. iliazeus
                  03.10.2023 01:44

                  А разве в первом случае в JS не нужны другие кавычки тоже ?

                  В том и дело, что нет:

                  > var order = { id: 123 }, s = `order: ${order[`id`]}`; s
                  'order: 123'
                  

                  Понятное дело, что и случаев таких в JS/TS меньше, потому что кавычки для форматирования - это не обычные одинарные и двойные, и потому что в "объекты-словари" можно ходить просто через точку (order.id).

                  Обратную косую черту можно обойти дополнительной переменной хотя конечно и не так удобно.

                  Понятно, что можно обойти. Но, если честно, в Python мне чаще, чем во многих других языках, приходится искать вот такие обходные пути. В треде выше вот огорчился, что нет встроенного find(iterable, predicate), тоже приходится как-то обходить.


                  1. NN1
                    03.10.2023 01:44

                    Так в JS просто есть ещё 3-е кавычки которых нет в Python.


                    1. iliazeus
                      03.10.2023 01:44
                      +1

                      В моем примере использованы именно эти "третьи" кавычки внутри подстановки внутри таких же кавычек. И все спокойно работает.


                      1. iStyx
                        03.10.2023 01:44
                        -1

                        И нормальная IDE должна подчеркнуть эти "третьи" кавычки внутри таких же, потому что "Unnecessary usage of template string".


      1. squaremirrow
        03.10.2023 01:44
        -1

        (параметры дженериков, type, override)

        А кто-то этим пользуется в Питоне?


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


        1. iliazeus
          03.10.2023 01:44
          +2

          Так без всего этого разрабатывать как раз бывает неудобно.

          Вот тут выше только что, опять-таки, обсуждали, что в Python нужно либо брать из библиотеки, либо реализовывать самому функцию find(iterable, predicate). Ее, например, без дженериков адекватно не типизируешь. И так с очень многими библиотечными, "не-бизнесовыми" функциями.


    1. AnyKey80lvl
      03.10.2023 01:44
      +15


  1. kefiiir
    03.10.2023 01:44
    +1

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

    меня это прямо пугает, и еще интересно сколько % подсвечевалок синтаксиса это умеет

    print(f"This is the playlist: {"\n".join(songs)}")


    1. iliazeus
      03.10.2023 01:44
      +1

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

      f"foo {x["bar"] + 1} baz"
      


      1. kefiiir
        03.10.2023 01:44

        vscode не очень
        vscode не очень


        1. iliazeus
          03.10.2023 01:44
          +5

          Что характерно, подсветка синтаксиса на вашем скриншоте работает прекрасно.

          Вам выдают SyntaxError, да, но это и понятно, если у вас версия питона еще не 3.12.


          1. kefiiir
            03.10.2023 01:44

            туплю, а еще даже не пятница


  1. un_m_nj
    03.10.2023 01:44

    Решил проверить прибавку производительности новой версии python.
    Запустил код на 4х версиях python (3.7, 3.9, 3.11, 3.12)


    import time
    times = []


    def check():
    strt = time.time()
    sum_: int = 0
    for x in range(1_000_000_000):
    sum
    += x
    times.append(time.time() — strt)


    for x in range(5):
    check()
    print(times, sum(times) / 5)


    Python 3.7.0: 37,76 с
    Python 3.9.0: 34,83 с
    Python 3.11.0: 32,28 с
    Python 3.12.0: 41,22 с


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


  1. valentinoshm
    03.10.2023 01:44

    Предложен более компактный синтаксис аннотирования типов для обобщенных классов и функций. Вот пример: def max[T](args: Iterable[T]) -> T:

    Я правильно понимаю, что это просто чтобы T = TypeVar('T') не писать?

    Появился новый способ определения псевдонимов типов при помощи выражения type. Еще пример: type Point = tuple[float, float]

    В чем отличие от Point = tuple[float, float] без type?


    1. iliazeus
      03.10.2023 01:44
      +3

      TypeVar в целом выглядел как костыль. Рад, что от него избавляются.

      Еще, например, forward references поддерживаются без костылей:

      # старый костыль
      # `: TypeAlias` можно не писать, но тогда
      # это выглядит еще больше как просто строка
      FooStr1: TypeAlias = "Foo1[str]"
      
      T = TypeVar('T')
      class Foo1(Generic[T]):
        ...
      
      # новый синтаксис
      type FooStr2 = Foo2[str]
      
      class Foo2[T]:
        ...
      


  1. siyet
    03.10.2023 01:44

    А почему не упомянули про фабрику жадных корутин?

    Там обещают прирост производительности до 50%

    https://docs.python.org/3.12/library/asyncio-task.html#asyncio.eager_task_factory

    https://github.com/python/cpython/issues/104144