blog

クローラーPython 3.7の超クールな新機能

Python の新しいバージョンは何をもたらしますか?ドキュメントにはこれらの新機能の概要がよく書かれていますが、この記事では大きなニュースのいくつかを掘り下げます。以下はその一部です:\n新しい (...

Jan 18, 2021 · 22 min. read
シェア

Python 3.7が正式にリリースされました!この新しいPythonバージョンは2016年9月から開発が進められており、今、誰もがコア開発者の努力の成果を楽しむことができます。

Python の新バージョンは何をもたらすのでしょうか?ドキュメントにはこれらの新機能の概要がよく書かれていますが、この記事では大きなニュースのいくつかを紹介します。これらには以下が含まれます:

新しいbreakpoint()組み込み関数により、デバッガへのアクセスが容易になりました。

  • データクラスを使ったシンプルなクラスの作成

  • アクセスモジュールのプロパティのカスタマイズ

  • タイプヒントのサポート向上

  • 高精度タイミング関数

さらに、Python 3.7は高速です!

この記事の最後のセクションでは、Python 3.7の他の素晴らしい機能と同様に、この速度についてさらに学びます。新しいバージョンへのアップグレードに関するアドバイスもあります。

組み込みのブレークポイント関数

完璧なコードを書こうとする誘惑に駆られるかもしれませんが、単純な事実は決してそうではありません。デバッグはプログラミングの重要な一部であり、Python 3.7 では新しい組み込み関数 breakpoint() が導入されました。これは実際には Python に新しい機能を追加するものではありませんが、デバッガをより柔軟かつ直感的に使えるようにします。

ファイルに次のようなエラーコード bugs.py があるとします:

def divide(e, f):
 return f / e
 
a, b = 0, 1
print(divide(a, b))

コードを実行すると、内部的に関数ZeroDivisionErrorがdivide()されます。このコードをブレークし、デバッガ divide() を直接実行させたいとします。これを行うには、コードにいわゆる「ブレークポイント」を設定します:

def divide(e, f):
 # Insert breakpoint here
 return f / e

ブレークポイントとは、プログラムの現在の状態を見ることができるように、コード内で一時的に実行を停止させる信号のことです。どのようにブレークポイントを置くのですか?Python 3.6 以下では、次のようなあいまいな行を使います:

def divide(e, f):
 import pdb; pdb.set_trace()
 return f / e

この pdb は標準ライブラリの Python デバッガです。Python 3.7 では、新しい breakpoint() 関数呼び出しがショートカットとして使えます:

def divide(e, f):
 breakpoint()
 return f / e

バックグラウンドでは、breakpoint() がまず pdb をインポートし、pdb.set_trace() に照会ます。breakpoint()の明らかな利点は、27文字の代わりに12文字を入力する必要があることを覚えやすいということです。しかし、breakpoint() を使用する本当の利点は、そのカスタマイズ性です。

bugs.pyスクリプトのbreakpoint()を以下のコマンドで実行します:

$ python bugs.py
> bugs.py(3)divide()
-> return f / e
(Pdb)

$ PYTHONBREAKPOINT=0 python3.7 bugs.py
ZeroDivisionError: division by zero

おっと、結局エラーは直っていないようですね...。

PYTHONBREAKPOINT を使用して PDB 以外のデバッガを指定する方法もあります。例えば、PuDB を使用するには次のようにします:

$ PYTHONBREAKPOINT=pudb.set_trace python3.7 bugs.py

そのためにはpudbをインストールする必要があります。しかし、Pythonはあなたの代わりにpudbがインポートを行います。これはデフォルトのデバッガを設定することもできます。PYTHONBREAKPOINT 環境変数に好みのデバッガを設定するだけです。あなたのシステムで環境変数を設定する方法については、このガイドを参照してください。

新しいbreakpoint()関数は、デバッガーのためだけのものではありません。便利な代替手段は、コードの中だけで対話的なシェルを起動することです。例えば、IPythonセッションを開始するには、以下のコマンドを使います:

$ PYTHONBREAKPOINT=IPython.embed python3.7 bugs.py
IPython 6.3.1 -- An enhanced Interactive Python. Type '?' for help.
 
In [1]: print(e / f)
0.0

独自の関数を作成し、ブレークポイント()コールを行うこともできます。次のコードはローカルスコープにある全ての変数を表示します。これを bp_utils.py というファイルに追加します:

from pprint import pprint
import sys
 
def print_locals():
 caller = sys._getframe(1) # Caller is 1 frame up.
 pprint(caller.f_locals)

この機能を使用するには、PYTHONBREAKPOINT を前と同じように .:

$ PYTHONBREAKPOINT=bp_utils.print_locals python3.7 bugs.py
{'e': 0, 'f': 1}
ZeroDivisionError: division by zero

通常、breakpoint() は引数なしで関数やメソッドを呼び出すために使用します。しかし、パラメータを渡すことも可能です。bugs.pyのbreakpoint()の行を次のように変更してください:

breakpoint(e, f, end="<-END
")

注意: デフォルトのPDBデバッガはこの行aでTypeErrorを発生します。 なぜならpdb.set_trace()は位置引数を受け取らないからです。

breakpoint()をprint()関数に見せかけて以下のコードを実行すると、渡される引数の簡単な例を見ることができます:

$ PYTHONBREAKPOINT=print python3.7 bugs.py
0 1<-END
ZeroDivisionError: division by zero

詳細は PEP 553 および breakpoint() と sys.breakpointhook() を参照してください。

データクラス・カテゴリー・モジュール

新しいdataclassesモジュールは、独自のクラスを書きやすくします。"init"と ."eq"() が自動的に追加されます。dataclassデコレータを使うと、次のように書くことができます:

from dataclasses import dataclass, field
 
@dataclass(order=True)
class Country:
 name: str
 population: int
 area: float = field(repr=False, compare=False)
 coastline: float = 0
 
 def beach_per_person(self):
 """Meters of coastline per person"""
 return (self.coastline * 1000) / self.population

この9行のコードは、多くのサンプルコードとベストプラクティスを表しています。そのカントリーの実装を、.「init"メソッドを持つ通常のクラスとしてのCountryの実装を考えてみましょう。下のボックスを展開すると、Countryのデータクラスとしての実装とほぼ同等のものを見ることができます:

class Country:
 
 def __init__(self, name, population, area, coastline=0):
 self.name = name
 self.population = population
 self.area = area
 self.coastline = coastline
 
 def __repr__(self):
 return (
 f"Country(name={self.name!r}, population={self.population!r},"
 f" coastline={self.coastline!r})"
 )
 
 def __eq__(self, other):
 if other.__class__ is self.__class__:
 return (
 (self.name, self.population, self.coastline)
 == (other.name, other.population, other.coastline)
 )
 return NotImplemented
 
 def __ne__(self, other):
 if other.__class__ is self.__class__:
 return (
 (self.name, self.population, self.coastline)
 != (other.name, other.population, other.coastline)
 )
 return NotImplemented
 
 def __lt__(self, other):
 if other.__class__ is self.__class__:
 return ((self.name, self.population, self.coastline) < (
 other.name, other.population, other.coastline
 ))
 return NotImplemented
 
 def __le__(self, other):
 if other.__class__ is self.__class__:
 return ((self.name, self.population, self.coastline) <= (
 other.name, other.population, other.coastline
 ))
 return NotImplemented
 
 def __gt__(self, other):
 if other.__class__ is self.__class__:
 return ((self.name, self.population, self.coastline) > (
 other.name, other.population, other.coastline
 ))
 return NotImplemented
 
 def __ge__(self, other):
 if other.__class__ is self.__class__:
 return ((self.name, self.population, self.coastline) >= (
 other.name, other.population, other.coastline
 ))
 return NotImplemented
 
 def beach_per_person(self):
 """Meters of coastline per person"""
 return (self.coastline * 1000) / self.population

データ・クラスは、作成された時点では通常のクラスです。例えば、通常の方法でデータ・クラスを継承することができます。データクラスの主な目的は、堅牢なクラスを素早く簡単に書くことです。

データ・クラスは他のクラスと同じように使うことができます:

>>> norway = Country("Norway", )
>>> norway
Country(name='Norway', population=, coastline=58133)
 
>>> norway.area

 
>>> usa = Country("United States", , 924)
>>> nepal = Country("Nepal", , )
>>> nepal
Country(name='Nepal', population=, coastline=0)
 
>>> usa.beach_per_person()
0.
 
>>> norway.beach_per_person()

すべてのフィールド .name、.opulation、.area、および .coastline は初期化されたクラスであることに注意してください。Countryクラスには妥当なreprがあり、定義メソッドは通常のクラスと同じように動作します。

既定では、データ・クラスが等しいかどうかを比較することができます。order=True が @dataclass デコレータで指定されているため、Country クラスを並べ替えることもできます:

>>> norway == norway
True
 
>>> nepal == usa
False
 
>>> sorted((norway, usa, nepal))
[Country(name='Nepal', population=, coastline=0),
 Country(name='Norway', population=, coastline=58133),
 Country(name='United States', population=, coastline=19924)]

並べ替えは、.nameの次に.populationというように、フィールド値に基づいて行われます。しかし、field() を使用すると、比較で使用するフィールドをカスタマイズすることができます。この例では、この.areaフィールドはreprとcompareを含んでいません。

データ・クラスは同じ名前付きタプルで機能しますが、attrsプロジェクトから最も多くのインスピレーションを得ています。より多くの例と詳細については、データ・クラスの完全なガイドを参照してください。

モジュールプロパティのカスタマイズ

Python の基本的な機能のいくつかは属性として実装されています!Python の基本的な機能のいくつかは属性として実装されています: ほとんどのイントロスペクション関数、ドキュメント文字列、名前空間などです。モジュールの中の関数はモジュール属性として使うことができます。

ドット記法は、属性thing.attributeを取得するために最も頻繁に使用されますが、以下のコマンドgetattr()を使用して実行時に名前付き属性を取得することもできます:

import random
 
random_attr = random.choice(("gammavariate", "lognormvariate", "normalvariate"))
random_func = getattr(random, random_attr)
 
print(f"A {random_attr} random value: {random_func(1, 1)}")
このコードを実行すると、以下のようなものが生成される:
A gammavariate random value: 2.

getattr"は特別なメソッドを呼び出します。この."getattr"()メソッドは、オブジェクトの属性へのアクセスをカスタマイズするために使用することができます。

Python 3.7 では、モジュール属性に対して同じカスタマイズを行うことは困難でした。しかし、PEP 562 で __getattr__ 関数が導入されました。が導入されました。

PEP 自身は、機能に非推奨の警告を追加したり、重いサブモジュールのロードを遅延させるなど、これらの機能の使い方の例をいくつか提供しています。以下では、モジュールに動的に機能を追加できる簡単なプラグインシステムを構築します。この例では Python パッケージを利用します。パッケージのアップデートが必要な場合は、こちらの記事を参照してください。

新しいディレクトリ plugins を作成し、plugins/"init".py に以下のコードを追加します:

from importlib import import_module
from importlib import resources
 
PLUGINS = dict()
 
def register_plugin(func):
 """Decorator to register plug-ins"""
 name = func.__name__
 PLUGINS[name] = func
 return func
 
def __getattr__(name):
 """Return a named plugin"""
 try:
 return PLUGINS[name]
 except KeyError:
 _import_plugins()
 if name in PLUGINS:
 return PLUGINS[name]
 else:
 raise AttributeError(
 f"module {__name__!r} has no attribute {name!r}"
 ) from None
 
def __dir__():
 """List available plug-ins"""
 _import_plugins()
 return list(PLUGINS.keys())
 
def _import_plugins():
 """Import all resources to register plug-ins"""
 for name in resources.contents(__name__):
 if name.endswith(".py"):
 import_module(f"{__name__}.{name[:-3]}")

このコードが何をするか見た後、plugins ディレクトリにさらに2つのファイルを追加します。まず、plugins/plugin_1.pyを見てみましょう:

from . import register_plugin
 
@register_plugin
def hello_1():
 print("Hello from Plugin 1")
次に、同様のコードプラグインをファイル/plugin_2.py 
from . import register_plugin
 
@register_plugin
def hello_2():
 print("Hello from Plugin 2")
 
@register_plugin
def goodbye():
 print("Plugin 2 says goodbye")

プラグインは以下のように使用できます:

>>> import plugins
>>> plugins.hello_1()
Hello from Plugin 1
 
>>> dir(plugins)
['goodbye', 'hello_1', 'hello_2']
 
>>> plugins.goodbye()
Plugin 2 says goodbye

しかし、ここで実際に何が起こっているのか見てみましょう。通常、plugins.hello_1() を呼び出すには、plugins モジュールで hello_1() 関数を定義するか、plugins パッケージで __init__.py を明示的にインポートする必要があります。ここでは、そのどちらでもありません!

むしろ、hello_1() はプラグインパッケージ内のどのファイルでも定義され、hello_1() はデコレーターを使って登録することでパッケージの一部になります。 plugins@register_plugin

違いは微妙です。どの機能が利用可能かを示すパッケージの代わりに、個々の機能がパッケージの一部として登録されます。これにより、利用可能な機能の一元的なリストを保持することなく、他のコードから独立して機能を追加できるシンプルな構造が得られます。

Pythonは最初にhello_1()でplugins/"init".pyファイル内の関数を探します。そのような関数は存在しないので、Python は __getattr__ を呼び出します。その __getattr__() 関数のソースコードを思い出してください:

def __getattr__(name):
 """Return a named plugin"""
 try:
 return PLUGINS[name] # 1) Try to return plugin
 except KeyError:
 _import_plugins() # 2) Import all plugins
 if name in PLUGINS:
 return PLUGINS[name] # 3) Try to return plugin again
 else:
 raise AttributeError( # 4) Raise error
 f"module {__name__!r} has no attribute {name!r}"
 ) from None

まず、この関数はPLUGINS辞書から指定されたプラグインを返そうとします。指定されたプラグイン名が存在し、インポートされていれば成功します。

指定されたプラグインがPLUGINS辞書に見つからない場合は、すべてのプラグインがインポートされていることを確認します。

指定したプラグインがインポート後に利用可能になった場合、そのプラグインを返します。

PLUGINSがそのプラグインがレキシコンにない後にすべてのプラグインをインポートする場合、その名前は現在のモジュールの属性ではないというAttributeErrorの主張がなされます。

PLUGINS 辞書はどのように作成されますか?この _import_plugins() 関数は、すべてのプラグイン・パッケージの

Pythonのファイルですが、PLUGINSには接触しないようです:

def _import_plugins():
 """Import all resources to register plug-ins"""
 for name in resources.contents(__name__):
 if name.endswith(".py"):
 import_module(f"{__name__}.{name[:-3]}")

すべてのプラグイン機能は @register_plugin デコレータでデコレートされていることを忘れないでください。このデコレータはプラグインをインポートするときに呼び出され、 PLUGINS辞書に実際にデータを登録します。プラグインファイルを手動でインポートすると、次のようになります:

>>> import plugins
>>> plugins.PLUGINS
{}
 
>>> import plugins.plugin_1
>>> plugins.PLUGINS
{'hello_1': <function hello_1 at 0x7f98>}

例の続きですが、モジュールのdir()呼び出しが残りのプラグインもインポートしていることに注意してください:

>>> dir(plugins)
['goodbye', 'hello_1', 'hello_2']
 
>>> plugins.PLUGINS
{'hello_1': <function hello_1 at 0x7f98>,
 'hello_2': <function hello_2 at 0x7f20>,
 'goodbye': <function goodbye at 0x7fa8>}

dir() は通常、オブジェクトの利用可能なプロパティをすべてリストします。通常、dir() をモジュールで使用すると、以下のような結果が得られます:

>>> import plugins
>>> dir(plugins)
['PLUGINS', '__builtins__', '__cached__', '__doc__',
 '__file__', '__getattr__', '__loader__', '__name__',
 '__package__', '__path__', '__spec__', '_import_plugins',
 'import_module', 'register_plugin', 'resources']

これは有用な情報かもしれませんが、一般に利用可能なプラグインがあることの方が興味深いと思います。Python 3.7 では、__dir__() 関数を追加することで、モジュール呼び出しの結果を dir() でカスタマイズすることができます。plugins/"init".py の場合、この関数は最初に全てのプラグインがインポートされていることを確認し、次にそれらの名前をリストします:

def __dir__():
 """List available plug-ins"""
 _import_plugins()
 return list(PLUGINS.keys())

この例を離れて、Python 3.7のもう一つのクールな新機能も使われていることに注意してください。plugins ディレクトリにあるすべてのモジュールをインポートするために、新しい importlib.resources モジュールが使われています。このモジュールを使うと、__file__ hacking や pkg_resources を使わなくても、モジュールやパッケージのファイルやリソースにアクセスできるようになります。importlib.resources の他の機能については後で説明します。

拡張モジュール

Python の型付けシステムは現在とても安定しています。それにもかかわらず、Python 3.7では、より良いパフォーマンス、コアサポート、前方参照など、いくつかの機能強化が行われました。

Python は実行時に型チェックを行いません。したがって、コードに型ヒントを追加してもパフォーマンスに影響を与えることはありません。

残念ながら、これは完全な真実ではありません。ほとんどの型ヒントは typing モジュールを必要とするからです。型付けモジュールは標準ライブラリの中で最も遅いモジュールの1つです。PEP 560はPython 3.7で型付けのコアサポートを追加し、型付けモジュールをかなり高速化しました。通常、その詳細を知る必要はありません。より高いパフォーマンスを享受するために身を乗り出すだけです。

Pythonの型システムはそれなりに表現力がありますが、トラブルを引き起こす問題の1つが前方参照です。型ヒントはモジュールをインポートするときに評価されます。そのため、使用するすべての名前はすでに定義されていなければなりません。次のようなことはできません:

class Tree:
 def __init__(self, left: Tree, right: Tree) -> None:
 self.left = left
 self.right = right

このコードを実行すると、.NameError が発生します。"init"()メソッドの定義でクラス Tree がまだ定義されていないため、コードを実行すると、Name Error が発生します:

Traceback (most recent call last):
 File "tree.py", line 1, in <module>
 class Tree:
 File "tree.py", line 2, in Tree
 def __init__(self, left: Tree, right: Tree) -> None:
NameError: name 'Tree' is not defined

この問題を解決するには、"Tree "を文字列として記述する必要があります:

class Tree:
 def __init__(self, left: "Tree", right: "Tree") -> None:
 self.left = left
 self.right = right

将来の Python 4.0 では、いわゆる前方参照が許可されます。PEP 563 にこの提案の詳細が書かれています。Python 3.7 では、前方参照は __future__import として提供されています。以下のように書けるようになりました:

from __future__ import annotations
 
class Tree:
 def __init__(self, left: Tree, right: Tree) -> None:
 self.left = left
 self.right = right

アノテーションの最も一般的な使い方は、型ヒントです。とはいえ、実行時にアノテーションにフルアクセスすることは可能で、必要に応じて使用することができます。アノテーションを直接扱う場合は、可能性のある前方参照を明示的に処理する必要があります。

アノテーションがいつ評価されるかを示す、ばかげた例をいくつか作ってみましょう。まず、インポート時にアノテーションが評価されるように、古いスタイルでやってみましょう。anno.pyに以下のコードを記述します:

def greet(name: print("Now!")):
 print(f"Hello {name}")

>>> import anno
Now!
 
>>> anno.greet.__annotations__
{'name': None}
 
>>> anno.greet("Alice")
Hello Alice

future__インポートを追加し、アノテーションの遅延評価を可能にしました:

from __future__ import annotations
 
def greet(name: print("Now!")):
 print(f"Hello {name}")

>>> import anno
 
>>> anno.greet.__annotations__
{'name': "print('Now!')"}
 
>>> anno.greet("Marty")
Hello Marty

注釈は__annotations__辞書に文字列リテラルとして残ります。注釈を評価するには、typing.get_type_hints() または eval() を使用します:

>>> import typing
>>> typing.get_type_hints(anno.greet)
Now!
{'name': <class 'NoneType'>}
 
>>> eval(anno.greet.__annotations__["name"])
Now!
 
>>> anno.greet.__annotations__
{'name': "print('Now!')"}

この__annotations__辞書は決して更新されないので、アノテーションを使用するたびに評価する必要があることに注意してください。

タイミングの正確さ

Python 3.7 では、PEP 564 で説明されているように、time モジュールに多くの新機能が追加されました。特に、以下の6つの機能が追加されました:

clock_gettime_ns(): 指定された時計の時刻を返す
clock_settime_ns()指定した時計の時刻を設定する
monotonic_ns()巻き戻せない相対時計の時刻を返す
perf_counter_ns()パフォーマンスカウンタの値を返す。
process_time_ns(): 現在のプロセスのシステム CPU 時間とユーザー CPU 時間の合計を返す
time_ns()日からのナノ秒数を返す。

ある意味、新しい機能は追加されていません。各関数は、_nsという接尾辞のない既存の関数と似ています。違いは、新しい関数は a の秒数を float として返すのではなく、int の秒数を n として返すという点です。

ほとんどのアプリケーションでは、これらの新しいナノ秒関数と古い関数の違いは重要ではありません。浮動小数点数は本質的に不正確だからです:

>>> 0.1 + 0.1 + 0.1
0.
 
>>> 0.1 + 0.1 + 0.1 == 0.3
False

これはPythonの問題ではなく、コンピュータが有限のビット数で無限の10進数を表現する必要がある結果です。

Python の float は IEEE 754 標準に従っており、53 の有効ビットを使います。その結果、約104日以上の時間をナノ秒精度の float で表現することはできません。対照的に、Python の int は無制限なので、整数のナノ秒は時間の値とは関係なく、常にナノ秒の精度を持ちます。

例えば、time.time()は1970年1月1日からの秒数を返します。この数値はすでに大きいので、この数値の精度はマイクロ秒レベルです。この関数は、その_nsバージョンで最大の改良点です。time.time_ns()の分解能はtime.time()の約3倍です。

ところで、ナノ秒とは何でしょう?厳密には、10億分の1秒、科学的表記法を好むなら1e-9、または秒です。これらは単なる数字であり、直感的なものではありません。より視覚的に理解するには、グレース・ホッパーによるナノ秒の素晴らしいデモンストレーションをご覧ください。

ところで、ナノ秒の精度でdatetimeを使用する必要がある場合、datetime標準ライブラリでは対応できません。マイクロ秒しか扱えません:

>>> from datetime import datetime, timedelta
>>> datetime() + timedelta(seconds=1e-6)
datetime.datetime(, 0, 0, 0, 1)
 
>>> datetime() + timedelta(seconds=1e-9)
datetime.datetime(, 0, 0)

その代わり、astropyprojectを使うことができます。astropy.timeパッケージは、2つのfloatオブジェクトを使って時系列を表すので、「全宇宙エポックの全時間スケールでサブナノ秒の精度」が保証されます。

>>> from astropy.time import Time, TimeDelta
>>> Time("")
<Time object: scale='utc' format='iso' value= .000>
 
>>> t = Time("") + TimeDelta(1e-9, format="sec")
>>> (t - Time("")).sec
9.71807e-10

その他のクールな機能

ここまでで、Python 3.7で何が新しくなったかについての見出しを見てきました。しかし、他にもクールな変更がたくさんあります。このセクションではそのいくつかを簡単に説明します。

辞書の順序の確保

Python 3.6 の CPython の実装は辞書をソートします。つまり、辞書の項目は挿入された順番に繰り返し処理されます。最初の例は Python 3.5 を使い、2 番目の例は Python 3.6 を使っています:

>>> {"one": 1, "two": 2, "three": 3} # Python <= 3.5
{'three': 3, 'one': 1, 'two': 2}
 
>>> {"one": 1, "two": 2, "three": 3} # Python >= 3.6
{'one': 1, 'two': 2, 'three': 3}

Python 3.6 では、このソートはうまく実装された結果ディクショナリにすぎませんでした。しかし、 Python 3.7 では、挿入順序を保持するディクショナリは言語仕様の一部です。その結果、Python > = 3.7 をサポートするプロジェクトでのみ利用できるようになりました。

"async "と "await "はキーワードです。

Python 3.5 では async と await 構文によるコプロセッシングが導入されました。後方互換性の問題を避けるために、async と await は予約キーワードのリストに追加されていません。つまり、async や await で変数を定義したり、関数名を指定することは可能です。

Python 3.7 では、これは動作しなくなりました:

 
>>> async = 1
 File "<stdin>", line 1
 async = 1
 ^
SyntaxError: invalid syntax
 
>>> def await():
 File "<stdin>", line 1
 def await():
 ^
SyntaxError: invalid syntax

" asyncio "薄い顔

このasyncio標準ライブラリはPython 3.4で初めて導入され、イベントループ、コ・プログラミング、フューチャーを使い、並行処理をモダンに扱います。ここではやさしく紹介します。

asyncioモジュールはPython 3.7で大幅に改良され、多くの新機能、コンテキスト変数のサポート、パフォーマンスの向上が行われました。特に注目すべきは asyncio.run() で、これは同期コードからコプロセッサを呼び出すプロセスを簡素化します。asyncio.run()を使用すると、明示的にイベントループを作成する必要がなくなります。これで、非同期のHello Worldプログラムを書くことができます:

import asyncio
 
async def hello_world():
 print("Hello World!")
 
asyncio.run(hello_world())

コンテキスト変数

コンテキスト変数とは、コンテキストによって異なる値を持つ変数のことです。これはスレッドローカルストレージに似ており、変数の値は実行スレッドごとに異なる可能性があります。しかし、コンテキスト変数では、1つの実行スレッドに複数のコンテキストが存在する可能性があります。コンテキスト変数の主な使用例は、同時実行の非同期タスクの変数を追跡することです。

以下の例では、3つのコンテキストを構築し、それぞれにnameという値を設定しています:

import contextvars
 
name = contextvars.ContextVar("name")
contexts = list()
 
def greet():
 print(f"Hello {name.get()}")
 
# Construct contexts and set the context variable name
for first_name in ["Steve", "Dina", "Harry"]:
 ctx = contextvars.copy_context()
 ctx.run(name.set, first_name)
 contexts.append(ctx)
 
# Run greet function inside each context
for ctx in reversed(contexts):
 ctx.run(greet) 

このスクリプトを実行して、スティーブ、ディナ、ハリーを逆の順番で迎えます:

$ python context_demo.py
Hello Harry
Hello Dina
Hello Steve

importlib.resources」でデータファイルをインポートします。

Python プロジェクトをパッケージ化するときの課題の一つは、プロジェクトが必要とするデータファイルのようなプロジェクトのリソースをどうするか決めることです。いくつかのオプションがよく使われます:

ハードコードされたデータファイルへのパス。

データ・ファイルをラッパーに入れ、__file__を見つけるために使用します。

データファイルリソースにアクセスするには、setuptools.pkg_resourcesを使用します。

それぞれに欠点があります。最初のオプションは移植性がありません。file__を使う方が移植性は高いですが、Pythonプロジェクトがインストールされている場合、zipの中に入ってしまい、__file__属性がないかもしれません。3番目のオプションはこの問題を解決しますが、遅いです。

より良い解決策は importlib.resources 標準ライブラリの新しいモジュールです。これはPythonの既存のimport機能を使ってデータファイルをインポートします。Pythonパッケージにそのようなリソースがあると仮定します:

data/
 
アリス_in_wonderland.txt
└──__init__.py

ファイルデータは Python パッケージでなければならないことに注意してください。つまり、ディレクトリに __init__.py ファイルが必要です。alice_in_wonderland.txtは次のように読みます:

>>> from importlib import resources
>>> with resources.open_text("data", "alice_in_wonderland.txt") as fid:
... alice = fid.readlines()
... 
>>> print("".join(alice[:7]))
CHAPTER I. Down the Rabbit-Hole
 
Alice was beginning to get very tired of sitting by her sister on the
bank, and of having nothing to do: once or twice she had peeped into the
book her sister was reading, but it had no pictures or conversations in
it, "and what is the use of a book," thought Alice "without pictures or
conversations?"

同様の関数resources.open_binary()は、バイナリモードでファイルを開くために使用できます。前回の「モジュールの属性としてのプラグイン」の例では、importlib.resourcesを使ってresources.contents()で利用可能なプラグインを検出しました。詳しくは Barry Warsaw の PyCon 2018 講演を参照してください。

開発者のヒント

Python 3.7では、開発者向けに多くの機能が追加されました。新しい breakpoint() 組み込みプロシージャはすでに見たでしょう。さらに、いくつかの新しい -X コマンドラインオプションが Python インタプリタに追加されました。

次のコマンド -X importtimeを使用すると、スクリプト内でインポートに費やされた時間を簡単に知ることができます:

$ python -X importtime my_script.py
import time: self [us] | cumulative | imported package
import time:  | _frozen_importlib_external
...
import time:  | importlib.resources
import time:  | plugins

累積カラムは累積インポート時間を示します。この例では、プラグインのインポートに約0.03秒かかり、そのほとんどはimportlib.resourcesのインポートに費やされました。self列は、ネストされたインポートを除いたインポート時間を示しています。

これで、-X devを使って「開発モード」を有効にすることができます。開発モードは、デフォルトで有効にするには遅すぎると考えられる特定のデバッグ機能とランタイムチェックを追加します。これには、 faulthandler が深刻なクラッシュ時にトレースバックを表示する機能や、より多くの警告やデバッグフックが含まれます。

最後に、-X utf8はUTF-8モードを有効にします。このモードでは、現在のロケールに関係なく、テキストエンコーディングにUTF-8が使用されます。

最適化

Python の各新リリースには最適化のセットが含まれています。Python 3.7 では、以下のような大幅な高速化が行われています:

標準ライブラリの多くのメソッドを呼び出す際のオーバーヘッドが少なくなります。

通常、メソッド呼び出しは最大20パーセント速くなります。

Python自体は起動時間を10-30%短縮しました。

インポートのタイピングが7倍速くなりました。

さらに多くの特殊な最適化も含まれています。詳しくはこちらのリストをご覧ください。

これらの最適化の結果、Python 3.7 は高速になりました。現在までにリリースされた CPython の中で最も速いバージョンです。

では、アップグレードすべきでしょうか?簡単な答えから始めましょう。pyenvやAnacondaのようなツールを使えば、複数のバージョンのPythonを並行してインストールするのは簡単です。

さて、より複雑な質問です。実運用環境を Python 3.7 にアップグレードすべきでしょうか。また、これらの新機能を利用するために、プロジェクトを Python 3.7 に依存させるべきでしょうか。

実運用環境をアップグレードする前に、常に徹底的なテストを行うべきだという明らかな注意点はありますが、Python 3.7 で以前のコードが壊れるようなことはほとんどありません。すでに最新の Python を使っているのであれば、3.7 へのアップグレードはスムーズにいくはずです。少し控えめにしたいのであれば、2018年7月に予定されている最初のメンテナンスリリースを待つのがよいでしょう。

Python3.7の新機能の多くは、Python3.6への逆移植か、便利な機能として利用できます。後者は、Python 3.6との互換性を保ちながら、Python 3.7を自分で実行することで利用できます。

Python 3.7 にコードをロックする主な機能は、モジュールの __getattr__() 、型ヒントの前方参照、ナノ秒時間関数です。これらのどれかが必要であれば、要件を絞り込んでください。そうでなければ、あなたのプロジェクトは Python 3.6 で長く動いた方が他の人にとって有益かもしれません。

アップグレードの際の注意点については Python 3.7 への移行ガイド を参照してください。

より引用

https://realpython.com/python37-new-features/

Read next

PostgreSQLからPythonへの接続

これはPythonのpsycopg2モジュールと統合できます。 sycopg2はデータベース用のPythonプログラミング言語のアダプタです。 そのプログラムコードは小さく、高速で安定しています。Pythonバージョン2.5.xと一緒にデフォルトで出荷されているので、このモジュールを個別にインストールする必要はありません。

Jan 18, 2021 · 8 min read