Python 3.8の新機能

Python 3.8の新機能

Pythonの最新バージョンがリリースされました! Python 3.8は夏からベータ版で利用できますが、https://www.python.org/dev/peps/pep-0569/[October 14th、2019]で最初の公式バージョンが用意されています。 これで、私たち全員が新しい機能を試し始め、最新の改善の恩恵を受けることができます。

Python 3.8はテーブルに何をもたらしますか? https://docs.python.org/3.8/whatsnew/3.8.html [ドキュメント]は、新機能の概要を説明しています。 ただし、この記事ではいくつかの最大の変更点について詳しく説明し、Python 3.8を活用する方法を示します。

この記事では、以下について学習します:

  • 割り当て式を使用して一部のコード構成を簡素化する

  • 独自の関数で位置のみの引数を強制する

  • より正確な型ヒントの指定

  • デバッグを簡単にするためにf文字列を使用する

いくつかの例外を除き、Python 3.8には以前のバージョンに比べて多くの小さな改善が含まれています。 記事の終わりに向かって、これらのあまり注目を集めない変更の多くと、Python 3.8を以前のものより高速にする最適化のいくつかについての議論を見るでしょう。 最後に、新しいバージョンへのアップグレードに関するアドバイスが得られます。

*無料ボーナス:*リンク:[Python Tricks:The Book]の章にアクセスするには、ここをクリックして、Pythonのベストプラクティスを簡単な例とともに示します。すぐに適用して、より美しい+ Pythonコードを記述できます。

部屋のセイウチ:割り当て式

Python 3.8の最大の変更点は、*割り当て式*の導入です。 新しい表記( +:= +)を使用して記述されています。 このオペレーターは、セイウチの目や牙に似ているため、*セイウチオペレーター*と呼ばれます。

割り当て式を使用すると、同じ式で値を割り当てて返すことができます。 たとえば、変数に割り当ててその値を出力する場合、通常は次のようにします。

>>>

>>> walrus = False
>>> print(walrus)
False

Python 3.8では、セイウチ演算子を使用して、これら2つのステートメントを1つにまとめることができます。

>>>

>>> print(walrus := True)
True

代入式を使用すると、「+ True 」を「 walrus +」に割り当てて、すぐに値を出力できます。 ただし、セイウチのオペレーターは、それなしでは不可能なことは一切行わないことに注意してください。 特定のコンストラクトがより便利になるだけで、コードの意図をより明確に伝えることができます。

セイウチ演算子の長所のいくつかを示すパターンの1つは、変数の初期化と更新が必要な「+ while 」ループです。 たとえば、次のコードは、ユーザーが「 quit +」と入力するまで入力を求めます。

inputs = list()
current = input("Write something: ")
while current != "quit":
    inputs.append(current)
    current = input("Write something: ")

このコードは理想的ではありません。 あなたは `+ input()`ステートメントを繰り返しており、何らかの形でユーザーにそれを尋ねる前にリストに ` current `を追加する必要があります。 より良い解決策は、無限の ` while `ループを設定し、 ` break +`を使用してループを停止することです。

inputs = list()
while True:
    current = input("Write something: ")
    if current == "quit":
        break
    inputs.append(current)

このコードは上記のコードと同等ですが、繰り返しを避け、何らかの形で行をより論理的な順序に保ちます。 割り当て式を使用する場合、このループをさらに単純化できます。

inputs = list()
while (current := input("Write something: ")) != "quit":
    inputs.append(current)

これにより、テストが存在するはずの `+ while +`行に戻ります。 ただし、現在はその行でいくつかのことが行われているため、適切に読み取るには少し手間がかかります。 セイウチのオペレーターがいつコードを読みやすくするかについて、最善の判断をしてください。

PEP 572では、割り当て式のすべての詳細を説明しています。これには、それらを言語に導入するための理論的根拠やhttps://www .python.org/dev/peps/pep-0572/#examples [いくつかの例]セイウチ演算子の使用方法。

位置のみの引数

組み込み関数 `+ float()`は、テキスト文字列と数値を ` float +`オブジェクトに変換するために使用できます。 次の例を見てください。

>>>

>>> float("3.8")
3.8

>>> help(float)
class float(object)
 |  float(x=0,/)
 |
 |  Convert a string or number to a floating point number, if possible.

[...]

+ float()+`のシグネチャをよく見てください。 パラメーターの後のスラッシュ( `+/+)に注意してください。 どういう意味ですか?

注意: `/`表記の詳細な説明については、https://www.python.org/dev/peps/pep-0457/[PEP 457-位置のみのパラメーターの表記]を参照してください。

`+ float()`の1つのパラメーターは ` x +`と呼ばれますが、その名前を使用することはできません。

>>>

>>> float(x="3.8")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: float() takes no keyword arguments

`+ float()+`を使用する場合、キーワードではなく位置によってのみ引数を指定できます。 Python 3.8以前では、そのような positional-only 引数は組み込み関数に対してのみ可能でした。 自分の関数で引数が位置のみであることを指定する簡単な方法はありませんでした。

>>>

>>> def incr(x):
...     return x + 1
...
>>> incr(3.8)
4.8

>>> incr(x=3.8)
4.8

https://github.com/python/cpython/blob/3.7/Lib/collections/init.py#L1000[simulate]位置のみの引数https://realpython.com/python-kwargs-and-argsが可能です。/[using + * args +]、ただしこれは柔軟性が低く、読みにくいため、独自の引数解析を実装する必要があります。 Python 3.8では、 `/`を使用して、それより前のすべての引数を位置で指定する必要があることを示すことができます。 `+ incr()+`を書き換えて、位置引数のみを受け入れることができます。

>>>

>>> def incr(x,/):
...     return x + 1
...
>>> incr(3.8)
4.8

>>> incr(x=3.8)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: incr() got some positional-only arguments passed as
           keyword arguments: 'x'

「+ x 」の後に「/」を追加することにより、「 x +」が位置のみの引数であることを指定します。 スラッシュの後に通常の引数を配置することにより、通常の引数と位置のみの引数を組み合わせることができます。

>>>

>>> def greet(name,/, greeting="Hello"):
...     return f"{greeting}, {name}"
...
>>> greet("Łukasz")
'Hello, Łukasz'

>>> greet("Łukasz", greeting="Awesome job")
'Awesome job, Łukasz'

>>> greet(name="Łukasz", greeting="Awesome job")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: greet() got some positional-only arguments passed as
           keyword arguments: 'name'

`+ greet()`では、スラッシュは ` name `と ` greeting `の間に配置されます。 つまり、「 name 」は位置のみの引数であり、「 greeting +」は位置またはキーワードで渡すことができる通常の引数です。

一見すると、位置のみの引数は少し制限されているように見え、読みやすさの重要性についてのPythonのマントラに反しています。 おそらく、位置のみの引数がコードを改善することはあまりないでしょう。

ただし、適切な状況では、位置のみの引数を使用すると、関数を設計するときに柔軟性が得られます。 まず、位置のみの引数は、自然な順序を持っているが、適切で説明的な名前を付けるのが難しい引数がある場合に意味があります。

位置のみの引数を使用する別の利点は、関数をより簡単にリファクタリングできることです。 特に、他のコードがそれらの名前に依存することを心配することなく、パラメーターの名前を変更できます。

位置のみの引数は、キーワードのみ*の引数をうまく補完します。 Python 3のどのバージョンでも、スター( + *+)を使用してキーワードのみの引数を指定できます。 引数_after_ `+ +`は、キーワードを使用して指定する必要があります。

>>>

>>> def to_fahrenheit(*, celsius):
...     return 32 + celsius *9/5
...
>>> to_fahrenheit(40)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: to_fahrenheit() takes 0 positional arguments but 1 was given

>>> to_fahrenheit(celsius=40)
104.0

`+ celsius +`はキーワードのみの引数であるため、キーワードなしで位置に基づいて指定しようとすると、Pythonでエラーが発生します。

位置のみの引数、通常の引数、およびキーワードのみの引数を、「/」と「* +」で区切られたこの順序で指定することにより、組み合わせることができます。 次の例では、「 text 」は位置のみの引数、「 border 」はデフォルト値の通常の引数、「 width +」はデフォルト値のキーワードのみの引数です。

>>>

>>> def headline(text,/, border="♦", *, width=50):
...     return f" {text} ".center(width, border)
...

`+ text `は位置のみであるため、キーワード ` text +`は使用できません:

>>>

>>> headline("Positional-only Arguments")
'♦♦♦♦♦♦♦♦♦♦♦ Positional-only Arguments ♦♦♦♦♦♦♦♦♦♦♦♦'

>>> headline(text="This doesn't work!")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: headline() got some positional-only arguments passed as
           keyword arguments: 'text'

一方、 `+ border +`は、キーワードの有無にかかわらず指定できます。

>>>

>>> headline("Python 3.8", "=")
'=================== Python 3.8 ==================='

>>> headline("Real Python", border=":")
':::::::::::::::::: Real Python :::::::::::::::::::'

最後に、キーワードを使用して `+ width +`を指定する必要があります。

>>>

>>> headline("Python", "????", width=38)
'???????????????????????????????????????????????????????????? Python ????????????????????????????????????????????????????????????'

>>> headline("Python", "????", 38)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: headline() takes from 1 to 2 positional arguments
           but 3 were given

位置のみの引数の詳細については、https://www.python.org/dev/peps/pep-0570/[PEP 570]をご覧ください。

より正確なタイプ

Pythonのタイピングシステムはこの時点で非常に成熟しています。 ただし、Python 3.8では、より正確な入力を可能にするために、いくつかの新しい機能が `+ typing +`に追加されました。

  • リテラル型

  • 型付き辞書

  • 最終オブジェクト

  • プロトコル

Pythonは、オプションの* typeヒント*をサポートします。通常、コードの注釈として:

def double(number: float) -> float:
    return 2 * number

この例では、 `+ number `は ` float `であり、 ` double()`関数も ` float +`を返す必要があると言います。 ただし、Pythonはこれらの注釈を_hints_として扱います。 これらは実行時に強制されません。

>>>

>>> double(3.14)
6.28

>>> double("I'm not a float")
"I'm not a floatI'm not a float"

`+ double()`は、 ` float `ではありませんが、引数として `" I’m not a float "+`を喜んで受け入れます。 https://realpython.com/python-type-checking/#using-types-at-runtime [実行時に型を使用できるライブラリ]がありますが、これはPythonの型システムの主な使用例ではありません。

代わりに、タイプヒントを使用すると、https://realpython.com/python-type-checking/#other-static-type-checkers [静的タイプチェッカー]を使用して、実際にスクリプトを実行せずにPythonコードのタイプチェックを行うことができます。 これは、https://www.java.com [Java]、https://www.rust-lang.org/[Rust]、https://crystal-langなどの他の言語で型エラーを検出するコンパイラを連想させます。 org/[Crystal]。 さらに、タイプヒントはコードのドキュメントとして機能し、読みやすくするだけでなく、https://realpython.com/python-type-checking/#pros-and-cons [IDEのオートコンプリートの改善]としても機能します。

注意: Pyright、https://google.github.io/pytype/[Pytype]、およびhttps://pyreなど、いくつかの静的型チェッカーが利用可能です。 -check.org/[Pyre]。 この記事では、http://mypy-lang.org/[Mypy]を使用します。 + pip +を使用してhttps://pypi.org/project/mypy/[PyPI]からMypyをインストールできます。

$ python -m pip install mypy

ある意味では、MypyはPythonの型チェッカーのリファレンス実装であり、Jukka Lehtasaloの指導の下でhttp://mypy-lang.org/about.html[Dropboxで開発]されています。 Pythonの作成者であるGuido van Rossumは、Mypyチームの一員です。

Pythonのタイプヒントの詳細については、https://www.python.org/dev/peps/pep-0484/[original PEP 484]とhttps://realpython.com/python-をご覧ください。 type-checking/[Python Type Checking(ガイド)]。

Python 3.8で受け入れられ、組み込まれたタイプチェックに関する4つの新しいPEPがあります。 これらのそれぞれから短い例を見ることができます。

PEP 586は、 https://docs.python.org/3.8/library/typing.html#typing.Literal [`+ Literal + `] タイプ。 `+ Literal `は、1つまたは複数の特定の値を表すという点で少し特別です。 ` Literal +`の使用例の1つは、特定の動作を記述するために文字列引数が使用される場合に、型を正確に追加できるようにすることです。 次の例を見てください。

# draw_line.py

def draw_line(direction: str) -> None:
    if direction == "horizontal":
        ...  # Draw horizontal line

    elif direction == "vertical":
        ...  # Draw vertical line

    else:
        raise ValueError(f"invalid direction {direction!r}")

draw_line("up")

`" up "`が無効な方向であっても、プログラムは静的型チェッカーに合格します。 型チェッカーは、 `" up "`が文字列であることのみをチェックします。 この場合、 `+ direction `はリテラル文字列 `" horizo​​ntal "`またはリテラル文字列 `" vertical "`のいずれかでなければならないと言う方が正確です。 ` Literal +`を使用すると、まさにそれを行うことができます。

# draw_line.py

from typing import Literal

def draw_line(direction: Literal["horizontal", "vertical"]) -> None:
    if direction == "horizontal":
        ...  # Draw horizontal line

    elif direction == "vertical":
        ...  # Draw vertical line

    else:
        raise ValueError(f"invalid direction {direction!r}")

draw_line("up")

`+ direction +`の許可された値を型チェッカーに公開することにより、エラーについて警告することができます。

$ mypy draw_line.py
draw_line.py:15: error:
    Argument 1 to "draw_line" has incompatible type "Literal['up']";
    expected "Union[Literal['horizontal'], Literal['vertical']]"
Found 1 error in 1 file (checked 1 source file)

基本的な構文は `+ Literal [<literal>] `です。 たとえば、 ` Literal [38] `はリテラル値38を表します。 ` Union +`を使用して、いくつかのリテラル値のいずれかを表現できます。

Union[Literal["horizontal"], Literal["vertical"]]

これはかなり一般的な使用例であるため、代わりに、より単純な表記 `+ Literal [" horizo​​ntal "、" vertical "] `を使用できます(おそらくそうすべきです)。 ` draw_line()`に型を追加するときに、後者をすでに使用しています。 上記のMypyの出力を注意深く見ると、内部でより単純な表記を ` Union +`表記に変換していることがわかります。

関数の戻り値の型が入力引数に依存する場合があります。 1つの例は、「+ mode 」の値に応じてテキスト文字列またはバイト配列を返す「 open()+」です。 これはhttps://mypy.readthedocs.io/en/latest/more_types.html#function-overloading[overloading]で処理できます。

次の例は、通常の数字( + 38 +)として、またはhttp://code.activestate.com/recipes/81611-roman-numerals/[ローマ数字]として回答を返すことができる計算機のスケルトンを示しています( + XXXVIII +):

# calculator.py

from typing import Union

ARABIC_TO_ROMAN = [(1000, "M"), (900, "CM"), (500, "D"), (400, "CD"),
                   (100, "C"), (90, "XC"), (50, "L"), (40, "XL"),
                   (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I")]

def _convert_to_roman_numeral(number: int) -> str:
    """Convert number to a roman numeral string"""
    result = list()
    for arabic, roman in ARABIC_TO_ROMAN:
        count, number = divmod(number, arabic)
        result.append(roman *count)
    return "".join(result)

def add(num_1: int, num_2: int, to_roman: bool = True) -> Union[str, int]:
    """Add two numbers"""
    result = num_1 + num_2

    if to_roman:
        return _convert_to_roman_numeral(result)
    else:
        return result

コードには正しい型のヒントがあります: `+ add()`の結果は ` str `または ` int `のいずれかになります。 ただし、多くの場合、このコードは、「 to_roman 」の値としてリテラル「 True 」または「 False 」を使用して呼び出されます。その場合、タイプチェッカーで「 str 」または「 int 」返されます。 これは、 ` @ overload `とともに ` Literal +`を使用して実行できます。

# calculator.py

from typing import Literal, overload, Union

ARABIC_TO_ROMAN = [(1000, "M"), (900, "CM"), (500, "D"), (400, "CD"),
                   (100, "C"), (90, "XC"), (50, "L"), (40, "XL"),
                   (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I")]

def _convert_to_roman_numeral(number: int) -> str:
    """Convert number to a roman numeral string"""
    result = list()
    for arabic, roman in ARABIC_TO_ROMAN:
        count, number = divmod(number, arabic)
        result.append(roman* count)
    return "".join(result)

@overload
def add(num_1: int, num_2: int, to_roman: Literal[True]) -> str: ...
@overload
def add(num_1: int, num_2: int, to_roman: Literal[False]) -> int: ...

def add(num_1: int, num_2: int, to_roman: bool = True) -> Union[str, int]:
    """Add two numbers"""
    result = num_1 + num_2

    if to_roman:
        return _convert_to_roman_numeral(result)
    else:
        return result

追加された + @ overload +`シグネチャは、タイプチェッカーが `+ to_roman +`のリテラル値に応じて `+ str +`または `+ int +`を推測するのに役立ちます。 省略記号( `+ …​ +)はコードのリテラル部分であることに注意してください。 これらは、オーバーロードされた署名の関数本体の代わりになります。

+ Literal +`を補完するものとして、https://www.python.org/dev/peps/pep-0591/[PEP 591]が https://docs.python.org/3.8/library/typing.htmlを導入しています#typing.Final [+ Final +`]