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
代入式を使用すると、True
をwalrus
に代入して、すぐに値を出力できます。 ただし、セイウチの演算子は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()
では、スラッシュはname
とgreeting
の間に配置されます。 これは、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
この例では、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"
を喜んで受け入れます。 libraries that can use types at runtimeがありますが、それはPythonの型システムの主な使用例ではありません。
代わりに、型ヒントを使用すると、static type checkersは、実際にスクリプトを実行しなくても、Pythonコードの型チェックを実行できます。 これは、Java、Rust、Crystalなどの他の言語で型エラーをキャッチするコンパイラーを彷彿とさせます。 さらに、型ヒントはコードのドキュメントとして機能し、improving auto-complete in your IDEと同様に読みやすくなります。
$ python -m pip install mypy
ある意味で、MypyはPythonのタイプチェッカーのリファレンス実装であり、Jukka Lehtasaloの指揮下でdeveloped at Dropboxになっています。 Pythonの作成者であるGuido van Rossumは、Mypyチームの一員です。
Pythonの型ヒントの詳細については、original PEP 484とPython Type Checking (Guide)を参照してください。
Python 3.8で受け入れられ、組み込まれたタイプチェックに関する4つの新しいPEPがあります。 これらのそれぞれから短い例を見ることができます。
PEP 586は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
はリテラル文字列"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 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
のいずれかになります。 ただし、多くの場合、このコードはリテラルTrue
またはFalse
をto_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
を推測するのに役立ちます。 楕円(...
)はコードのリテラル部分であることに注意してください。 これらは、オーバーロードされた署名の関数本体の代わりになります。
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 Rossumのbenevolent 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.metadata
available 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
モジュールには、いくつかの新しい機能もあります。
-
statistics.fmean()
は、float
の数値の平均を計算します。 -
statistics.geometric_mean()
は、float
数の幾何平均を計算します。 -
statistics.multimode()
は、シーケンス内で最も頻繁に発生する値を検索します。 -
statistics.quantiles()
は、データをnの連続区間に等しい確率で分割するためのカットポイントを計算します。
次の例は、使用中の関数を示しています。
>>>
>>> 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で標準ライブラリが提供するよりも高度な統計が必要な場合は、statsmodels
とscipy.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を使用できる必要があります。 pyenv
やAnacondaなどのツールを使用すると、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 expressionsやpositional-only argumentsなどのPython3.8のみにある機能の実験を開始できます。 ただし、他の人が自分のコードに依存しているかどうかを意識する必要があります。これにより、他の人も環境をアップグレードする必要があります。 人気のあるライブラリは、おそらく少なくともPython 3.6をかなり長い間サポートするでしょう。
Python 3.8用のコードの準備の詳細については、Porting to Python 3.8を参照してください。