Python 3.8の新機能

Python 3.8の新機能

newest version of Python is released! Python 3.8は夏からベータ版で利用可能になっていますが、October 14th, 2019で最初の公式バージョンの準備ができています。 これで、私たち全員が新しい機能を試し始め、最新の改善の恩恵を受けることができます。

Python 3.8はテーブルに何をもたらしますか? documentationは、新機能の概要を示しています。 ただし、この記事ではいくつかの最大の変更点について詳しく説明し、Python 3.8を活用する方法を示します。

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

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

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

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

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

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

Free Bonus:Click here to get access to a chapter from Python Tricks: The Bookは、Pythonのベストプラクティスと、より美しい+ Pythonicコードをすぐに適用できる簡単な例を示しています。

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

Python 3.8での最大の変更点は、assignment expressionsの導入です。 これらは、新しい表記(:=)を使用して記述されています。 この演算子は、側面のセイウチの目と牙に似ているため、walrus operatorと呼ばれることがよくあります。

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

>>>

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

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

>>>

>>> print(walrus := True)
True

代入式を使用すると、Truewalrusに代入して、すぐに値を出力できます。 ただし、セイウチの演算子はnotで、それなしでは不可能なことをすべて実行することに注意してください。 特定のコンストラクトがより便利になるだけで、コードの意図をより明確に伝えることができます。

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

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

このコードは理想的ではありません。 input()ステートメントを繰り返していますが、どういうわけか、currentをリストbeforeに追加して、ユーザーに要求する必要があります。 より良い解決策は、無限の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は、代入式を言語に導入する理由の一部や、セイウチ演算子の使用方法のseveral 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()の署名をよく見てください。 パラメータの後のスラッシュ(/)に注意してください。 どういう意味ですか?

Note:/表記の詳細については、PEP 457 - Notation for Positional-Only Parametersを参照してください。

float()の1つのパラメータはxと呼ばれていますが、その名前を使用することは許可されていません。

>>>

>>> float(x="3.8")
Traceback (most recent call last):
  File "", line 1, in 
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]位置のみの引数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 "", line 1, in 
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 "", line 1, in 
TypeError: greet() got some positional-only arguments passed as
           keyword arguments: 'name'

greet()では、スラッシュはnamegreetingの間に配置されます。 これは、nameが位置のみの引数であるのに対し、greetingは位置またはキーワードのいずれかで渡すことができる通常の引数であることを意味します。

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

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

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

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

>>>

>>> def to_fahrenheit(*, celsius):
...     return 32 + celsius * 9 / 5
...
>>> to_fahrenheit(40)
Traceback (most recent call last):
  File "", line 1, in 
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 "", line 1, in 
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 "", line 1, in 
TypeError: headline() takes from 1 to 2 positional arguments
           but 3 were given

位置のみの引数の詳細については、PEP 570を参照してください。

より正確なタイプ

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

  • リテラル型

  • 型付き辞書

  • 最終オブジェクト

  • プロトコル

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

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

この例では、numberfloatであり、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"を喜んで受け入れます。 libraries that can use types at runtimeがありますが、それはPythonの型システムの主な使用例ではありません。

代わりに、型ヒントを使用すると、static type checkersは、実際にスクリプトを実行しなくても、Pythonコードの型チェックを実行できます。 これは、JavaRustCrystalなどの他の言語で型エラーをキャッチするコンパイラーを彷彿とさせます。 さらに、型ヒントはコードのドキュメントとして機能し、improving auto-complete in your IDEと同様に読みやすくなります。

Note:PyrightPytypePyreなど、いくつかの静的型チェッカーを使用できます。 この記事では、Mypyを使用します。 pipを使用してPyPIからMypyをインストールできます。

$ python -m pip install mypy

ある意味で、MypyはPythonのタイプチェッカーのリファレンス実装であり、Jukka Lehtasaloの指揮下でdeveloped at Dropboxになっています。 Pythonの作成者であるGuido van Rossumは、Mypyチームの一員です。

Pythonの型ヒントの詳細については、original PEP 484Python Type Checking (Guide)を参照してください。

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

PEP 586Literalタイプを導入します。 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はリテラル文字列"horizontal"またはリテラル文字列"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を使用して、いくつかのリテラル値の1つを表すことができます。

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

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

関数の戻り値の型が入力引数に依存する場合があります。 1つの例はopen()で、modeの値に応じてテキスト文字列またはバイト配列を返す場合があります。 これはoverloadingで処理できます。

次の例は、ハミング数(38)またはroman numeralsXXXVIII)のいずれかとして回答を返すことができる電卓のスケルトンを示しています。

# 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のいずれかになります。 ただし、多くの場合、このコードはリテラルTrueまたはFalseto_romanの値として使用して呼び出されます。その場合、タイプチェッカーでstrまたはstrのどちらであるかを正確に推測する必要があります。 intが返されます。 これは、Literal@overloadを使用して実行できます。

# 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を補完するものとして、PEP 591Finalを導入します。 この修飾子は、変数または属性を再割り当て、再定義、またはオーバーライドしないことを指定します。 以下は入力エラーです。

from typing import Final

ID: Final = 1

...

ID += 1

Mypyは行ID += 1を強調表示し、Cannot assign to final name "ID"であることに注意してください。 これにより、コード内の定数が値を変更しないようにすることができます。

さらに、クラスとメソッドに適用できる@finalデコレータもあります。 @finalを持つクラスdecoratedはサブクラス化できませんが、@finalメソッドはサブクラスによってオーバーライドできません。

from typing import final

@final
class Base:
    ...

class Sub(Base):
    ...

Mypyは、この例にエラーメッセージCannot inherit from final class "Base"のフラグを付けます。 Finalおよび@finalの詳細については、PEP 591を参照してください。

より具体的なタイプのヒントを可能にする3番目のPEPはPEP 589で、これはTypedDictを導入します。 これは、型指定されたNamedTupleと同様の表記法を使用して、ディクショナリ内のキーと値の型を指定するために使用できます。

従来、辞書にはDictを使用して注釈が付けられていました。 問題は、これがキーに1つのタイプと値に1つのタイプしか許可せず、多くの場合、Dict[str, Any]のような注釈につながることです。 例として、Pythonバージョンに関する情報を登録する辞書を考えてみましょう。

py38 = {"version": "3.8", "release_year": 2019}

versionに対応する値は文字列であり、release_yearは整数です。 これは、Dictを使用して正確に表すことはできません。 新しいTypedDictを使用すると、次のことができます。

from typing import TypedDict

class PythonVersion(TypedDict):
    version: str
    release_year: int

py38 = PythonVersion(version="3.8", release_year=2019)

タイプチェッカーは、py38["version"]のタイプがstrであるのに対し、py38["release_year"]intであると推測できます。 実行時には、TypedDictは通常のdictであり、型のヒントは通常どおり無視されます。 TypedDictを純粋に注釈として使用することもできます。

py38: PythonVersion = {"version": "3.8", "release_year": 2019}

Mypyは、値の型が間違っているか、宣言されていないキーを使用しているかどうかを通知します。 その他の例については、PEP 589を参照してください。

Mypyはすでにしばらくの間Protocolsをサポートしています。 ただし、official acceptanceは2019年5月にのみ発生しました。

プロトコルは、Pythonのアヒルタイピングのサポートを形式化する方法です。

アヒルのように歩き、アヒルのように泳ぎ、カモのように鳴く鳥を見るとき、私はその鳥をアヒルと呼びます。 (Source

ダックタイピングを使用すると、たとえば、オブジェクトのタイプを気にすることなく、.name属性を持つ任意のオブジェクトの.nameを読み取ることができます。 タイピングシステムがこれをサポートするのは直感に反するように思えるかもしれません。 structural subtypingを使用しても、ダックタイピングの意味を理解することは可能です。

たとえば、.name属性を持つすべてのオブジェクトを識別できるNamedというプロトコルを定義できます。

from typing import Protocol

class Named(Protocol):
    name: str

def greet(obj: Named) -> None:
    print(f"Hi {obj.name}")

ここで、greet()は、.name属性を定義している限り、任意のオブジェクトを取ります。 プロトコルの詳細については、PEP 544およびthe Mypy documentationを参照してください。

f-Stringを使用した簡単なデバッグ

f-stringsはPython 3.6で導入され、非常に人気があります。 Pythonライブラリがバージョン3.6以降でのみサポートされている最も一般的な理由である可能性があります。 f-stringは、フォーマットされた文字列リテラルです。 先頭のfで認識できます。

>>>

>>> style = "formatted"
>>> f"This is a {style} string"
'This is a formatted string'

f文字列を使用する場合は、変数や式を中括弧で囲むことができます。 これらは実行時に評価され、文字列に含まれます。 1つのf文字列に複数の式を含めることができます。

>>>

>>> import math
>>> r = 3.6

>>> f"A circle with radius {r} has area {math.pi * r * r:.2f}"
'A circle with radius 3.6 has area 40.72'

最後の式{math.pi * r * r:.2f}では、フォーマット指定子も使用します。 書式指定子は、コロンで式から分離されます。

.2fは、領域が小数点以下2桁の浮動小数点数としてフォーマットされていることを意味します。 フォーマット指定子は.format()の場合と同じです。 許可されるフォーマット指定子の完全なリストについては、official documentationを参照してください。

Python 3.8では、f文字列内で代入式を使用できます。 割り当て式を括弧で囲むようにしてください:

>>>

>>> import math
>>> r = 3.8

>>> f"Diameter {(diam := 2 * r)} gives circumference {math.pi * diam:.2f}"
'Diameter 7.6 gives circumference 23.88'

ただし、Python 3.8の実際のf-newsは新しいデバッグ指定子です。 式の最後に=を追加できるようになり、式とその値の両方が出力されます。

>>>

>>> python = 3.8
>>> f"{python=}"
'python=3.8'

これは略記法であり、通常、インタラクティブに作業する場合、またはスクリプトをデバッグするためにprintステートメントを追加する場合に最も役立ちます。 Pythonの以前のバージョンでは、同じ情報を取得するために変数または式を2回入力する必要がありました。

>>>

>>> python = 3.7
>>> f"python={python}"
'python=3.7'

=の前後にスペースを追加し、通常どおりフォーマット指定子を使用できます。

>>>

>>> name = "Eric"
>>> f"{name = }"
"name = 'Eric'"

>>> f"{name = :>10}"
'name =       Eric'

>10形式指定子は、nameを10文字の文字列内で右揃えにする必要があることを示しています。 =は、より複雑な式でも機能します。

>>>

>>> f"{name.upper()[::-1] = }"
"name.upper()[::-1] = 'CIRE'"

f文字列の詳細については、Python 3’s f-Strings: An Improved String Formatting Syntax (Guide)を参照してください。

Pythonステアリングカウンシル

技術的には、Python’s governanceは言語機能ではありません。 ただし、Python 3.8は、Guido van Rossumbenevolent dictatorshipで開発されていないPythonの最初のバージョンです。 Python言語は現在、5人のコア開発者で構成されるsteering councilによって管理されています。

Pythonの新しいガバナンスモデルへの道は、自己組織化における興味深い研究でした。 Guido van Rossumは1990年代初頭にPythonを作成し、愛情を込めてPythonのBenevolent Dictator for Life (BDFL)と呼ばれています。 何年にもわたって、Python言語に関する決定はPython Enhancement Proposals (PEPs)を通じてますます行われてきました。 それでも、Guidoには新しい言語機能に関する最後の言葉が正式にありました。

assignment expressionsについての長く引き出された議論の後、2018年7月にGuidoannouncedは、BDFL(for real this time)としての役割から引退することを発表しました。 彼は意図的に後継者を指名しませんでした。 その代わりに、彼はコア開発者のチームに、Pythonが今後どのように管理されるべきかを理解するよう求めました。

幸いなことに、PEPプロセスは既に十分に確立されているため、PEPを使用して新しいガバナンスモデルを議論し、決定することは自然でした。 2018年の秋までに、electing a new BDFL(Gracious Umpire Influencing Decisions Officer:GUIDOに改名)を含むseveral modelsが提案されました。または、中央集権化せずに、コンセンサスと投票に基づいてcommunity modelに移行しました。リーダーシップ。 2018年12月、コア開発者の間で投票された後、steering council modelが選択されました。

運営委員会は、上記のPythonコミュニティの5人のメンバーで構成されています。 Pythonがメジャーリリースされるたびに、新しい運営評議会が選出されます。 言い換えれば、Python 3.8のリリース後に選挙が行われます。

開かれた選挙ですが、最初の運営評議会のすべてではないにしても、ほとんどが再選されると予想されます。 運営評議会には、Python言語に関する決定を下すためのbroad powersがありますが、これらの権限をできるだけ行使しないように努める必要があります。

新しいガバナンスモデルのすべてをPEP 13で読むことができますが、新しいモデルを決定するプロセスはPEP 8000で説明されています。 詳細については、PyCon 2019 Keynoteを参照し、Talk Python To MeおよびThe Changelog podcastでBrettCannonを聞いてください。 GitHubの運営評議会からの更新をフォローできます。

その他のかなりクールな機能

これまで、Python 3.8の新機能に関するヘッドラインニュースを見てきました。 ただし、他にも多くの変更点があり、それらもかなりクールです。 このセクションでは、それらのいくつかについて簡単に説明します。

importlib.metadata

Python 3.8の標準ライブラリで利用できる新しいモジュールが1つあります:importlib.metadata。 このモジュールを使用して、Pythonインストールのインストール済みパッケージに関する情報にアクセスできます。 コンパニオンモジュールであるimportlib.resourcesとともに、importlib.metadataは古いpkg_resourcesの機能を改善します。

例として、pipに関するいくつかの情報を取得できます。

>>>

>>> from importlib import metadata
>>> metadata.version("pip")
'19.2.3'

>>> pip_metadata = metadata.metadata("pip")
>>> list(pip_metadata)
['Metadata-Version', 'Name', 'Version', 'Summary', 'Home-page', 'Author',
 'Author-email', 'License', 'Keywords', 'Platform', 'Classifier',
  'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier',
  'Classifier', 'Classifier', 'Classifier', 'Classifier', 'Classifier',
  'Classifier', 'Classifier', 'Requires-Python']

>>> pip_metadata["Home-page"]
'https://pip.pypa.io/'

>>> pip_metadata["Requires-Python"]
'>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*'

>>> len(metadata.files("pip"))
668

現在インストールされているpipのバージョンは19.2.3です。 metadata()を使用すると、PyPIに表示されるほとんどの情報にアクセスできます。 たとえば、このバージョンのpipには、Python2.7またはPython3.5以降が必要であることがわかります。 files()を使用すると、pipパッケージを構成するすべてのファイルのリストが表示されます。 この場合、ほぼ700個のファイルがあります。

files()は、Pathオブジェクトのリストを返します。 これらは、read_text()を使用して、パッケージのソースコードを調べる便利な方法を提供します。 次の例では、realpython-readerパッケージから__init__.pyを出力します。

>>>

>>> [p for p in metadata.files("realpython-reader") if p.suffix == ".py"]
[PackagePath('reader/__init__.py'), PackagePath('reader/__main__.py'),
 PackagePath('reader/feed.py'), PackagePath('reader/viewer.py')]

>>> init_path = _[0]  # Underscore access last returned value in the REPL
>>> print(init_path.read_text())
"""Real Python feed reader

Import the `feed` module to work with the Real Python feed:

    >>> from reader import feed
    >>> feed.get_titles()
    ['Logging in Python', 'The Best Python Books', ...]

See https://github.com/realpython/reader/ for more information
"""

# Version of realpython-reader package
__version__ = "1.0.0"

...

パッケージの依存関係にもアクセスできます。

>>>

>>> metadata.requires("realpython-reader")
['feedparser', 'html2text', 'importlib-resources', 'typing']

requires()は、パッケージの依存関係を一覧表示します。 たとえば、realpython-readerはバックグラウンドでfeedparserを使用して、記事のフィードを読み取って解析していることがわかります。

以前のバージョンのPythonで機能するimportlib.metadataavailable on PyPIのバックポートがあります。 pipを使用してインストールできます。

$ python -m pip install importlib-metadata

次のように、コードでPyPIバックポートを使用してフォールバックできます。

try:
    from importlib import metadata
except ImportError:
    import importlib_metadata as metadata

...

importlib.metadataの詳細については、documentationを参照してください。

新規および改善されたmathおよびstatistics関数

Python 3.8は、既存の標準ライブラリパッケージとモジュールに多くの改善をもたらします。 標準ライブラリのmathには、いくつかの新しい関数があります。 math.prod()は、組み込みのsum()と同様に機能しますが、乗法積の場合です。

>>>

>>> import math
>>> math.prod((2, 8, 7, 7))
784

>>> 2 * 8 * 7 * 7
784

2つのステートメントは同等です。 因子がすでに反復可能に格納されている場合、prod()は使いやすくなります。

もう1つの新しい関数はmath.isqrt()です。 isqrt()を使用して、square rootsの整数部分を見つけることができます。

>>>

>>> import math
>>> math.isqrt(9)
3

>>> math.sqrt(9)
3.0

>>> math.isqrt(15)
3

>>> math.sqrt(15)
3.872983346207417

9の平方根は3です。 isqrt()は整数の結果を返すのに対し、math.sqrt()は常にfloatを返すことがわかります。 15の平方根はほぼ3.9です。 isqrt()truncatesは、次の整数(この場合は3)までの答えであることに注意してください。

最後に、標準ライブラリのn次元の点とベクトルをより簡単に操作できるようになりました。 math.dist()で2点間の距離を、math.hypot()でベクトルの長さを見つけることができます。

>>>

>>> import math
>>> point_1 = (16, 25, 20)
>>> point_2 = (8, 15, 14)

>>> math.dist(point_1, point_2)
14.142135623730951

>>> math.hypot(*point_1)
35.79106033634656

>>> math.hypot(*point_2)
22.02271554554524

これにより、標準ライブラリを使用してポイントとベクターを簡単に操作できます。 ただし、ポイントまたはベクトルで多くの計算を行う場合は、NumPyを確認する必要があります。

statisticsモジュールには、いくつかの新しい機能もあります。

次の例は、使用中の関数を示しています。

>>>

>>> import statistics
>>> data = [9, 3, 2, 1, 1, 2, 7, 9]
>>> statistics.fmean(data)
4.25

>>> statistics.geometric_mean(data)
3.013668912157617

>>> statistics.multimode(data)
[9, 2, 1]

>>> statistics.quantiles(data, n=4)
[1.25, 2.5, 8.5]

Python 3.8には、work with the Gaussian normal distributionをより便利にする新しいstatistics.NormalDistクラスがあります。

NormalDistの使用例を見るには、新しいstatistics.fmean()と従来のstatistics.mean()の速度を比較してみてください。

>>>

>>> import random
>>> import statistics
>>> from timeit import timeit

>>> # Create 10,000 random numbers
>>> data = [random.random() for _ in range(10_000)]

>>> # Measure the time it takes to run mean() and fmean()
>>> t_mean = [timeit("statistics.mean(data)", number=100, globals=globals())
...           for _ in range(30)]
>>> t_fmean = [timeit("statistics.fmean(data)", number=100, globals=globals())
...            for _ in range(30)]

>>> # Create NormalDist objects based on the sampled timings
>>> n_mean = statistics.NormalDist.from_samples(t_mean)
>>> n_fmean = statistics.NormalDist.from_samples(t_fmean)

>>> # Look at sample mean and standard deviation
>>> n_mean.mean, n_mean.stdev
(0.825690647733245, 0.07788573997674526)

>>> n_fmean.mean, n_fmean.stdev
(0.010488564966666065, 0.0008572332785645231)

>>> # Calculate the lower 1 percentile of mean
>>> n_mean.quantiles(n=100)[0]
0.6445013221202459

この例では、timeitを使用して、mean()fmean()の実行時間を測定します。 信頼できる結果を得るには、timeitに各関数を100回実行させ、関数ごとに30個のそのような時間サンプルを収集します。 これらのサンプルに基づいて、2つのNormalDistオブジェクトを作成します。 コードを自分で実行する場合、異なる時間サンプルを収集するのに最大1分かかることがあります。

NormalDistには、多くの便利な属性とメソッドがあります。 完全なリストについては、documentationを参照してください。 .mean.stdevを調べると、古いstatistics.mean()は0.826±0.078秒で実行され、新しいstatistics.fmean()は0.0105±0.0009秒で実行されることがわかります。 つまり、これらのデータのfmean()は約80倍高速です。

Pythonで標準ライブラリが提供するよりも高度な統計が必要な場合は、statsmodelsscipy.statsを確認してください。

危険な構文に関する警告

PythonにはSyntaxWarningがあり、通常はSyntaxErrorではない疑わしい構文について警告できます。 Python 3.8では、コーディングとデバッグの際に役立ついくつかの新しい機能が追加されています。

is==の違いは混乱を招く可能性があります。 後者は等しい値をチェックしますが、isはオブジェクトが同じ場合にのみTrueです。 Python 3.8は、isの代わりに==を使用する必要がある場合について警告しようとします。

>>>

>>> # Python 3.7
>>> version = "3.7"
>>> version is "3.7"
False

>>> # Python 3.8
>>> version = "3.8"
>>> version is "3.8"
:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
False

>>> version == "3.8"
True

長いリストを書き出すとき、特に縦にフォーマットするときは、カンマを見逃しがちです。 タプルのリストでコンマを忘れると、タプルが呼び出し可能でないという混乱したエラーメッセージが表示されます。 Python 3.8はさらに、実際の問題を示す警告を発します。

>>>

>>> [
...   (1, 3)
...   (2, 4)
... ]
:2: SyntaxWarning: 'tuple' object is not callable; perhaps
           you missed a comma?
Traceback (most recent call last):
  File "", line 2, in 
TypeError: 'tuple' object is not callable

この警告は、欠落しているコンマを実際の犯人として正しく識別します。

最適化

Python 3.8にはいくつかの最適化が行われています。 コードの実行を高速化するものもあります。 その他は、メモリフットプリントを削減します。 たとえば、namedtupleでフィールドを検索すると、Python3.7と比較してPython3.8の方が大幅に高速になります。

>>>

>>> import collections
>>> from timeit import timeit
>>> Person = collections.namedtuple("Person", "name twitter")
>>> raymond = Person("Raymond", "@raymondh")

>>> # Python 3.7
>>> timeit("raymond.twitter", globals=globals())
0.05876131607996285

>>> # Python 3.8
>>> timeit("raymond.twitter", globals=globals())
0.0377705999400132

Python 3.8では、namedtuple.twitterを検索する方が30〜40%高速であることがわかります。 リストは、既知の長さのイテラブルから初期化されるときにスペースを節約します。 これによりメモリを節約できます。

>>>

>>> import sys

>>> # Python 3.7
>>> sys.getsizeof(list(range(20191014)))
181719232

>>> # Python 3.8
>>> sys.getsizeof(list(range(20191014)))
161528168

この場合、リストはPython 3.7と比較してPython 3.8で約11%少ないメモリを使用します。

その他の最適化には、subprocessでのパフォーマンスの向上、shutilでのファイルコピーの高速化、pickleでのデフォルトパフォーマンスの向上、operator.itemgetter操作の高速化が含まれます。 最適化の完全なリストについては、official documentationを参照してください。

では、Python 3.8にアップグレードする必要がありますか?

簡単な答えから始めましょう。 ここで見た新機能を試してみたい場合は、Python 3.8を使用できる必要があります。 pyenvAnacondaなどのツールを使用すると、Pythonの複数のバージョンを簡単に並べてインストールできます。 または、official Python 3.8 Docker containerを実行することもできます。 Python 3.8を自分で試してみてもマイナス面はありません。

さて、より複雑な質問に。 実稼働環境をPython 3.8にアップグレードする必要がありますか? 新しい機能を利用するには、Python 3.8に依存する独自のプロジェクトを作成する必要がありますか?

Python 3.8でPython 3.7コードを実行する際に問題が発生することはほとんどありません。 したがって、Python 3.8を実行するように環境をアップグレードすることは非常に安全であり、新しいバージョンで作成されたoptimizationsを利用することができます。 さまざまなベータ版のPython 3.8がすでに数か月間利用可能であるため、ほとんどのバグが既に潰されていることを願っています。 ただし、保守的になりたい場合は、最初のメンテナンスリリース(Python 3.8.1)が利用可能になるまで待つことがあります。

環境をアップグレードしたら、assignment expressionspositional-only argumentsなどのPython3.8のみにある機能の実験を開始できます。 ただし、他の人が自分のコードに依存しているかどうかを意識する必要があります。これにより、他の人も環境をアップグレードする必要があります。 人気のあるライブラリは、おそらく少なくともPython 3.6をかなり長い間サポートするでしょう。

Python 3.8用のコードの準備の詳細については、Porting to Python 3.8を参照してください。