14 Октября 2019 года вышла новая версия языка Python под номером 3.8, содержащая множество мелких улучшений и оптимизаций, которые делают новую версию быстрее чем ее предшественники. Эта заметка охватывает наиболее интересные изменения в языке.
Новый оператор “Морж” (Walrus)
Начиная с версии 3.8 вводится новый оператор присваивания, который позволяет одновременно с присваиванием вернуть присваиваемое значение, с новым синтаксисом :=.
Например:
1 2 3 |
>>> walrus = False >>> print(walrus) False |
Начиная с версии 3.8, данный код можно переписать так:
1 2 |
<span class="gp">>>> </span><span class="nb">print</span><span class="p">(</span><span class="n">walrus</span> <span class="p">:</span><span class="o">=</span> <span class="kc">True</span><span class="p">)</span> <span class="go">True</span> |
Такой оператор позволяет одновременно присвоить значение переменной и сразу же вернуть его для использования. При этом оператор не делает ничего, такого-что бы было не возможно без него.
Более наглядно и эффективно оператор, можно применить в следующем примере, в котором требуется обновлять переменную в цикле и принимать решение о продолжении цикла или нет:
1 2 3 4 5 6 |
<span class="n">inputs</span> <span class="o">=</span> <span class="nb">list</span><span class="p">()</span> <span class="k">while</span> <span class="kc">True</span><span class="p">:</span> <span class="n">current</span> <span class="o">=</span> <span class="nb">input</span><span class="p">(</span><span class="s2">"Write something: "</span><span class="p">)</span> <span class="k">if</span> <span class="n">current</span> <span class="o">==</span> <span class="s2">"quit"</span><span class="p">:</span> <span class="k">break</span> <span class="n">inputs</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">current</span><span class="p">)</span> |
Однако применение нового оператора может упростить код:
1 2 3 |
inputs = list() while (current := input("Write something: ")) != "quit": inputs.append(current) |
Хотя новый вариант и короче, но вместе с тем и этот вариант не лишен недостатков, он тяжелее в понимании. Документ PEP 572 описывает все детали новых выражений присваивания, включая и некоторые обоснования для их введения в язык, а также несколько примеров того, как можно использовать этот оператор.
Только позиционные аргументы
Рассмотрим интересную особенность языка, если мы посмотрим, на стандартную функцию float:
1 2 3 4 5 6 7 |
>>> help(float) Help on class float in module builtins: class float(object) | float(x=0, /) | | Convert a string or number to a floating point number, if possible. |
При этом в описании функции вы увидите странный слэш “/” в конце параметров, который означает, что для данной функции можно использовать только позиционные параметры, но нельзя применить именованные. При попытке выполнить функцию с именованными параметрами мы получим следующую ошибку:
1 2 3 4 |
<span class="gp">>>> </span><span class="nb">float</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="s2">"3.8"</span><span class="p">)</span> <span class="gt">Traceback (most recent call last):</span> File <span class="nb">"<stdin>"</span>, line <span class="m">1</span>, in <span class="n"><module></span> <span class="gr">TypeError</span>: <span class="n">float() takes no keyword arguments</span> |
Однако такое поведение до версии 3.8 имеется только у встроенных функций и нет никакого механизма, применить такой подход для своих функций. До версии 3.8 следующий код всегда имеет такое поведение:
1 2 3 4 5 6 7 8 |
<span class="gp">>>> </span><span class="k">def</span> <span class="nf">incr</span><span class="p">(</span><span class="n">x</span><span class="p">):</span> <span class="gp">... </span> <span class="k">return</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">1</span> <span class="gp">... </span> <span class="gp">>>> </span><span class="n">incr</span><span class="p">(</span><span class="mf">3.8</span><span class="p">)</span> <span class="go">4.8</span> <span class="gp">>>> </span><span class="n">incr</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mf">3.8</span><span class="p">)</span> <span class="go">4.8</span> |
Конечно можно переписать код с использованием *args и смоделировать схожее поведение, однако при этом теряется вся гибкость и удобство чтения кода, а также требуются дополнительные усилия. Но с новой версией языка, можно переписать код, что бы он принимал только позиционные параметры:
1 2 3 4 5 6 7 8 9 10 11 |
<span class="gp">>>> </span><span class="k">def</span> <span class="nf">incr</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="o">/</span><span class="p">):</span> <span class="gp">... </span> <span class="k">return</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">1</span> <span class="gp">... </span> <span class="gp">>>> </span><span class="n">incr</span><span class="p">(</span><span class="mf">3.8</span><span class="p">)</span> <span class="go">4.8</span> <span class="gp">>>> </span><span class="n">incr</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mf">3.8</span><span class="p">)</span> <span class="gt">Traceback (most recent call last):</span> File <span class="nb">"<stdin>"</span>, line <span class="m">1</span>, in <span class="n"><module></span> <span class="gr">TypeError</span>: <span class="n">incr() got some positional-only arguments passed as</span> <span class="go"> keyword arguments: 'x'</span> |
В этом случае после параметра “х” идет символ клэш “/”, который означает, что все параметры до него могут передаваться только через позиционные аргументы. При этом стоит отметить, что все параметры после слэша могут передаваться как через именованные параметры так и через позиционные.
На первый взгляд, такие позиционные аргументы могут показаться идущими в разрез с духом Python, а кроме того не так много случаев, когда позиционные аргументы улучшат код.
Однако в некоторых случаях такие аргументы могут быть весьма полезны. Во-первых, позиционные аргументы имеют смысл, когда у вас есть аргументы, которые имеют естественный порядок, но для них трудно придумать хорошие имена.
Другое возможное преимущество использования позиционных аргументов состоит в том, что вы можете легче реорганизовать свои функции. В частности, вы можете изменить имя ваших параметров, не беспокоясь о том, что другой код зависит от этих имен.
Только позиционные аргументы хорошо дополняют аргументы только которые передаются только через ключевые слова. В любой версии Python 3 вы можете указать аргументы только для ключевых слов, используя звездочку (*). Любой аргумент после * должен быть указан с помощью передачи через ключевые слова, так в примере параметр celsium должен быть передан только через ключевое слово и другие варианты всегда будет приводить к ошибке:
1 2 3 4 5 6 7 8 9 10 |
<span class="gp">>>> </span><span class="k">def</span> <span class="nf">to_fahrenheit</span><span class="p">(</span><span class="o">*</span><span class="p">,</span> <span class="n">celsius</span><span class="p">):</span> <span class="gp">... </span> <span class="k">return</span> <span class="mi">32</span> <span class="o">+</span> <span class="n">celsius</span> <span class="o">*</span> <span class="mi">9</span> <span class="o">/</span> <span class="mi">5</span> <span class="gp">... </span> <span class="gp">>>> </span><span class="n">to_fahrenheit</span><span class="p">(</span><span class="mi">40</span><span class="p">)</span> <span class="gt">Traceback (most recent call last):</span> File <span class="nb">"<stdin>"</span>, line <span class="m">1</span>, in <span class="n"><module></span> <span class="gr">TypeError</span>: <span class="n">to_fahrenheit() takes 0 positional arguments but 1 was given</span> <span class="gp">>>> </span><span class="n">to_fahrenheit</span><span class="p">(</span><span class="n">celsius</span><span class="o">=</span><span class="mi">40</span><span class="p">)</span> <span class="go">104.0</span> |
Вы можете комбинировать позиционные, обычные и ключевые параметры, указав их в в определенном порядке через / и *. В следующем примере text является позиционным аргументом, border является обычным аргументом со значением по умолчанию, а width является ключевым аргументом со значением по умолчанию:
1 2 |
<span class="gp">>>> </span><span class="k">def</span> <span class="nf">headline</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="o">/</span><span class="p">,</span> <span class="n">border</span><span class="o">=</span><span class="s2">"♦"</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">width</span><span class="o">=</span><span class="mi">50</span><span class="p">):</span> <span class="gp">... </span> <span class="k">return</span> <span class="n">f</span><span class="s2">" </span><span class="si">{text}</span><span class="s2"> "</span><span class="o">.</span><span class="n">center</span><span class="p">(</span><span class="n">width</span><span class="p">,</span> <span class="n">border</span><span class="p">)</span> |
Более точные типы
Более точные типы
На данный момент система ввода Python достаточно развита. Однако в Python 3.8 были добавлены некоторые новые функции для набора текста, чтобы обеспечить более точную печать:
- Литеральные типы (Literal types)
- Типовые словари (Typed dictionaries)
- Конечные объекты (Final objects)
- протоколы (Protocols)
Python поддерживает необязательные подсказки типов, обычно в виде аннотаций в вашем коде, например:
1 2 |
<span class="k">def</span> <span class="nf">double</span><span class="p">(</span><span class="n">number</span><span class="p">:</span> <span class="nb">float</span><span class="p">)</span> <span class="o">-></span> <span class="nb">float</span><span class="p">:</span> <span class="k">return</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">number</span> |
Эта форма записи функции указывает, что функция должна принимать как параметр число с плавающей запятой и возвращать число с плавающей запятой, однако это только рекомендательное указание которое никак не влияет на работу функции и если ей на вход передать строку, то на выходе так же окажется строка без всяких ошибок:
1 2 3 4 5 |
<span class="gp">>>> </span><span class="n">double</span><span class="p">(</span><span class="mf">3.14</span><span class="p">)</span> <span class="go">6.28</span> <span class="gp">>>> </span><span class="n">double</span><span class="p">(</span><span class="s2">"I'm not a float"</span><span class="p">)</span> <span class="go">"I'm not a floatI'm not a float"</span> |
https://realpython.com/python38-new-features/