Python 3.7の新機能

Python 3.7の新機能

Python 3.7はhttps://www.python.org/downloads/release/python-370/[公式リリース]です! この新しいPythonバージョンはhttps://www.python.org/dev/peps/pep-0537/[2016年9月]から開発されており、コア開発者の努力の結果を享受できるようになりました。

新しいPythonバージョンは何をもたらしますか? https://docs.python.org/3.7/whatsnew/3.7.html [ドキュメント]には新機能の概要が記載されていますが、この記事ではニュースの大きな部分について詳しく説明します。 これらが含まれます:

  • 新しい `+ breakpoint()+`ビルトインによるデバッガーへのより簡単なアクセス

  • データクラスを使用した簡単なクラス作成

  • モジュール属性へのカスタマイズされたアクセス

  • タイプヒンティングのサポートの改善

  • 高精度のタイミング関数

さらに重要なことに、Python 3.7は高速です。

この記事の最後のセクションでは、この速度のほか、Python 3.7のその他の優れた機能について詳しく説明します。 また、新しいバージョンへのアップグレードに関するアドバイスも得られます。

`+ breakpoint()+`ビルトイン

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

ファイル `+ bugs.py +`に次のバグのあるコードがあると仮定します。

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

a, b = 0, 1
print(divide(a, b))

コードを実行すると、 `+ divide()`関数内で ` ZeroDivisionError `が発生します。 コードを中断して、 ` divide()+`の一番上にあるhttps://realpython.com/python-debugging-pdb/[debugger]にドロップするとします。 これを行うには、コードにいわゆる「ブレークポイント」を設定します。

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

ここで、https://docs.python.org/library/pdb.html [+ pdb +]は標準ライブラリのPythonデバッガーです。 Python 3.7では、代わりに新しい `+ breakpoint()+`関数呼び出しをショートカットとして使用できます。

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

バックグラウンドで、 `+ breakpoint()`は最初に ` pdb `をインポートし、次に ` pdb.set_trace()`を呼び出します。 明らかな利点は、 ` breakpoint()`のほうが覚えやすく、27文字ではなく12文字を入力するだけで済むことです。 ただし、 ` breakpoint()+`を使用することの本当のボーナスは、そのカスタマイズ性です。

`+ breakpoint()`で ` bugs.py +`スクリプトを実行します:

$ python3.7 bugs.py
>/home/gahjelle/bugs.py(3)divide()
-> return f/e
(Pdb)

スクリプトは、 `+ breakpoint()`に到達すると中断し、PDBデバッグセッションにドロップします。 「 c +」と入力して[.keys]#Enter#を押すと、スクリプトを続行できます。 PDBとデバッグの詳細については、https://realpython.com/python-debugging-pdb/[Nathan JenningsのPDBガイド]を参照してください。

次に、バグを修正したと思うと言います。 デバッガーで停止せずに、スクリプトを再度実行する必要があります。 もちろん、 `+ breakpoint()`行をコメントアウトすることもできますが、別のオプションは ` PYTHONBREAKPOINT `環境変数を使用することです。 この変数は、 ` breakpoint()`の動作を制御し、 ` PYTHONBREAKPOINT = 0 `を設定すると、 ` breakpoint()+`の呼び出しはすべて無視されます。

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

バグを修正していないようです。

別のオプションは、 `+ PYTHONBREAKPOINT +`を使用してPDB以外のデバッガーを指定することです。 たとえば、https://pypi.org/project/pudb/[PuDB](コンソールのビジュアルデバッガー)を使用するには、次のようにします。

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

これが機能するには、 + pudb +`をインストールする必要があります( `+ pip install pudb +)。 ただし、Pythonが `+ pudb `のインポートを処理します。 この方法で、デフォルトのデバッガを設定することもできます。 ` PYTHONBREAKPOINT +`環境変数をお好みのデバッガーに設定するだけです。 システムで環境変数を設定する方法については、https://www.schrodinger.com/kb/1842 [このガイド]を参照してください。

新しい `+ breakpoint()+`関数は、デバッガーでのみ機能しません。 便利なオプションの1つは、コード内でインタラクティブシェルを単純に起動することです。 たとえば、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

また、独自の関数を作成し、 `+ breakpoint()`がそれを呼び出すようにすることもできます。 次のコードは、ローカルスコープ内のすべての変数を出力します。 ` 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\n")

*注意:*デフォルトのPDBデバッガーは、この行で `+ TypeError `を発生させます。これは、 ` pdb.set_trace()+`が位置引数を受け取らないためです。

`+ print()`関数を装った ` breakpoint()+`でこのコードを実行すると、渡される引数の簡単な例を見ることができます。

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

PEP 553およびhttps://docs.python.org/3.7/library/functions.html#breakpoint [のドキュメントを参照してください。詳細については、+ breakpoint()+ `]およびhttps://docs.python.org/3.7/library/sys.html#sys.breakpointhook [ + sys.breakpointhook()+ `]を参照してください。

データクラス

新しいhttps://realpython.com/python-data-classes/[+ dataclasses +]モジュールは、 + . init ()++ . reprのような特別なメソッドとして、独自のクラスを書くのをより便利にします。 ()+ `および + . 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行のコードは、かなりの定型コードとベストプラクティスを表しています。 + Country +`を通常のクラスとして実装するには何が必要かを考えてください: `+ . init ()+`メソッド、 `+ repr +、6つの異なる比較メソッド、および `+ .beach_per_person()`方法。 以下のボックスを展開して、データクラスとほぼ同等の ` Country +`の実装を確認できます。

作成後、データクラスは通常のクラスになります。 たとえば、通常の方法でデータクラスから継承できます。 データクラスの主な目的は、堅牢なクラス、特に主にデータを格納する小さなクラスをすばやく簡単に記述できるようにすることです。

他のクラスと同様に、 `+ Country +`データクラスを使用できます。

>>>

>>> norway = Country("Norway", 5320045, 323802, 58133)
>>> norway
Country(name='Norway', population=5320045, coastline=58133)

>>> norway.area
323802

>>> usa = Country("United States", 326625791, 9833517, 19924)
>>> nepal = Country("Nepal", 29384297, 147181)
>>> nepal
Country(name='Nepal', population=29384297, coastline=0)

>>> usa.beach_per_person()
0.06099946957342386

>>> norway.beach_per_person()
10.927163210085629

クラスを初期化するときに、すべてのフィールド「+ .name 」、「。population 」、「。area 」、および「 .coastline 」が使用されることに注意してください(ただし、「。coastline 」は、内陸ネパールの例)。 ` Country `クラスには合理的なhttps://dbader.org/blog/python-repr-vs-str [` repr +`]がありますが、メソッドの定義は通常のクラスと同じように機能します。

デフォルトでは、データクラスの同等性を比較できます。 `+ @ dataclass `デコレータで ` order = True `を指定したため、 ` Country +`クラスもソートできます:

>>>

>>> norway == norway
True

>>> nepal == usa
False

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

ソートは、フィールド値で行われ、最初に「+ .name 」、次に「 .population 」などになります。 ただし、 ` field()`を使用する場合は、https://realpython.com/python-data-classes/#advanced-default-values [customize]で比較で使用するフィールドを指定できます。 この例では、 ` .area `フィールドは ` repr +`と比較から除外されました。

*注:*国のデータはhttps://www.cia.gov/library/publications/the-world-factbook/[CIA World Factbook]からのもので、2017年7月の推定人口数です。

ノルウェーでの次の海辺の休暇を予約する前に、ファクトブックがhttps://www.cia.gov/library/publications/the-world-factbook/geos/no.html [ノルウェーの気候]について次のように述べています。 「北大西洋海流によって修正された海岸沿いの温帯。降水量が増加し、夏がより寒くなる寒い室内;西海岸では一年中雨が降ります。」

データクラスはhttps://dbader.org/blog/writing-clean-python-with-namedtuples [+ namedtuple +]と同じことをいくつか行います。 それでも、彼らはhttp://www.attrs.org/[`+ attrs +`プロジェクト]から最大のインスピレーションを得ています。 その他の例と詳細については、https://realpython.com/python-data-classes/[データクラスの完全ガイド]とhttps://www.python.org/dev/peps/pep-0557をご覧ください。/[PEP 557]公式の説明。

モジュール属性のカスタマイズ

属性は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.8017715125270618

クラスの場合、 `+ thing.attr `を呼び出すと、最初に ` thing `で定義されている ` attr `が検索されます。 見つからない場合は、特別なメソッド ` thing . getattr (" attr ")`が呼び出されます。 (これは単純化です。 詳細については、http://blog.lerner.co.il/python-attributes/[この記事]を参照してください。) ` . getattr ()+`メソッドを使用して、オブジェクトの属性へのアクセスをカスタマイズできます。

Python 3.7までは、モジュール属性に対して同じカスタマイズを簡単に利用できませんでした。 ただし、https://www.python.org/dev/peps/pep-0562/[PEP 562]では、対応する + dir ()+`関数とともに、モジュールに `+ getattr ()+`を導入しています。 `+ dir ()+`特殊関数を使用すると、https://realpython.com/python-modules-packages/#the-dir-function [+ dir()+` on a module]を呼び出した結果をカスタマイズできます。

PEP自体は、これらの関数の使用方法のいくつかの例を示しています。これには、関数への非推奨警告の追加や、重いサブモジュールの遅延読み込みが含まれます。 以下では、関数をモジュールに動的に追加できる単純なプラグインシステムを構築します。 この例では、Pythonパッケージを利用しています。 パッケージの再確認が必要な場合は、https://realpython.com/python-modules-packages/[この記事]を参照してください。

新しいディレクトリ「+ 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")

次に、ファイル `+ plugins/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()`を呼び出すには、 ` hello_1()`関数を ` plugins `モジュールで定義するか、 ` init 。py +`内で明示的にインポートする必要がありますplugins + `パッケージ。 ここでは、どちらでもありません!

代わりに、「+ hello_1()」は「 plugins 」パッケージ内の任意のファイルで定義され、「 hello_1()」は「 @ register_plugin + `https://realpython.com/primer-on-python-decorators/[decorator]。

違いは微妙です。 使用可能な機能を指示するパッケージの代わりに、個々の機能はパッケージの一部として登録されます。 これにより、使用可能な関数の集中リストを保持することなく、コードの残りの部分から独立して関数を追加できる単純な構造が得られます。

`+ plugins/ init 。py `コード内での ` getattr ()`の動作を簡単に確認しましょう。 ` plugins.hello_1()`を要求すると、Pythonは最初に ` plugins/ init 。py `ファイル内で ` hello_1()`関数を探します。 そのような関数は存在しないため、Pythonは代わりに ` getattr (" hello_1 ")`を呼び出します。 ` 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

`+ getattr ()+`には次の手順が含まれます。 次のリストの番号は、コード内の番号付きコメントに対応しています。

  1. 最初に、関数は `+ PLUGINS `辞書から名前付きプラグインを楽観的に返そうとします。 ` name +`という名前のプラグインが存在し、既にインポートされている場合、これは成功します。

  2. 名前付きプラグインが `+ PLUGINS +`辞書に見つからない場合、すべてのプラグインがインポートされていることを確認します。

  3. インポート後に名前付きプラグインが使用可能になった場合、そのプラグインを返します。

  4. すべてのプラグインをインポートした後、プラグインが `+ PLUGINS `辞書にない場合、 ` name `は現在のモジュールの属性(プラグイン)ではないことを示す ` AttributeError +`を発生させます。

しかし、「+ PLUGINS 」辞書はどのように取り込まれますか? ` _import_plugins()`関数は ` 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 +`ディクショナリに入力されます。 プラグインファイルの1つを手動でインポートすると、これを確認できます。

>>>

>>> import plugins
>>> plugins.PLUGINS
{}

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

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

>>>

>>> dir(plugins)
['goodbye', 'hello_1', 'hello_2']

>>> plugins.PLUGINS
{'hello_1': <function hello_1 at 0x7f29d4341598>,
 'hello_2': <function hello_2 at 0x7f29d4341620>,
 'goodbye': <function goodbye at 0x7f29d43416a8>}

`+ 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 +`ディレクトリ内のすべてのモジュールをインポートするために、新しいhttps://docs.python.org/3.7/library/importlib.html#module-importlib.resources [+ importlib.resources `]モジュールを使用しました。 このモジュールは、モジュールとパッケージ内のファイルとリソースへのアクセスを提供し、 ` file `ハック(常に動作するとは限りません)や ` pkg_resources `(遅い)を必要としません。 ` importlib.resources +`の他の機能はlink:#other-pretty-cool-features [後で強調表示]になります。

入力の機能強化

型のヒントと注釈は、Python 3シリーズのリリース全体で絶えず開発されています。 Pythonのタイピングシステムは現在非常に安定しています。 それでも、Python 3.7では、パフォーマンスの向上、コアサポート、前方参照など、いくつかの機能強化がテーブルに加えられています。

Pythonは実行時に型チェックを行いません(https://pypi.org/project/enforce/[+ enforce +]などのパッケージを明示的に使用している場合を除く)。 したがって、タイプヒントをコードに追加しても、そのパフォーマンスに影響はありません。

残念ながら、ほとんどのタイプヒントには `+ typing `モジュールが必要なので、これは完全に真実ではありません。 ` typing `モジュールは、標準ライブラリのhttps://www.python.org/dev/peps/pep-0560/#performance[slowest modules]の1つです。 https://www.python.org/dev/peps/pep-0560[PEP 560]は、Python 3.7で入力するためのコアサポートを追加します。これにより、 ` typing +`モジュールが大幅に高速化されます。 この詳細は、一般に知る必要はありません。 後ろに傾いて、パフォーマンスの向上をお楽しみください。

Pythonの型システムは適度に表現力がありますが、多少の痛みを引き起こす問題の1つは前方参照です。 型のヒント(より一般的には注釈)は、モジュールのインポート中に評価されます。 したがって、使用する前にすべての名前を定義しておく必要があります。 以下は不可能です。

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

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

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

元の議論については、https://www.python.org/dev/peps/pep-0484/#forward-references [PEP 484]を参照してください。

将来のhttp://www.curiousefficiency.org/posts/2014/08/python-4000.html[Python 4.0]では、このようないわゆる前方参照が許可されます。 これは、明示的に要求されるまで注釈を評価しないことで処理されます。 PEP 563は、この提案の詳細を説明しています。 Python 3.7では、前方参照はhttps://docs.python.org/library/future.html [+ future + import]として既に利用可能です。 次のように記述できます。

from __future__ import annotations

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

型ヒントが実行されないため、多少不器用な +" Tree "+`構文を回避することに加えて、延期された注釈の評価もコードを高速化することに注意してください。 前方参照は、http://mypy-lang.org/[+ mypy +`]で既にサポートされています。

注釈の最も一般的な使用法は、タイプヒンティングです。 それでも、実行時に注釈に完全にアクセスでき、必要に応じて使用できます。 アノテーションを直接処理する場合は、可能な前方参照を明示的に処理する必要があります。

注釈が評価されるタイミングを示す、明らかに馬鹿げた例を作成してみましょう。 まず、古いスタイルで行うため、注釈はインポート時に評価されます。 `+ anno.py +`に次のコードを含めます:

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

`+ name `の注釈は ` print()+`であることに注意してください。 これは、注釈が評価されるタイミングを正確に確認するためです。 新しいモジュールをインポートします。

>>>

>>> import anno
Now!

>>> anno.greet.__annotations__
{'name': None}

>>> anno.greet("Alice")
Hello Alice

ご覧のとおり、注釈はインポート時に評価されました。 `+ name `は ` print()`の戻り値であるため、 ` None +`で注釈付けされることに注意してください。

`+ 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

`+ Now!`は決して出力されず、注釈は ` 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では、https://docs.python.org/library/time.html [+ time +]モジュールはhttps://www.python.org/dev/peps/pep-で説明されているいくつかの新しい機能を取得します0564/[PEP 564]。 特に、次の6つの機能が追加されています。

  • * + clock_gettime_ns()+:*指定された時計の時刻を返します

  • * + clock_settime_ns()+:*指定した時計の時刻を設定します

  • * + monotonic_ns()+:*後方に移動できない相対時計の時間を返します(たとえば夏時間のため)

  • + perf_counter_ns()+ :パフォーマンスカウンター(短い間隔を測定するために特別に設計されたクロック)の値を返します。

  • * + process_time_ns()+:*現在のプロセスのシステムとユーザーCPU時間の合計を返します(スリープ時間は含まれません)

  • + time_ns()+ :1970年1月1日からのナノ秒数を返します

ある意味では、追加された新しい機能はありません。 各関数は、 `+ _ns `接尾辞のない既存の関数に似ています。 違いは、新しい関数が「 float 」としての秒数ではなく、「 int +」として数ナノ秒を返すことです。

ほとんどのアプリケーションでは、これらの新しいナノ秒機能と古い同等の機能との違いはあまり認識されません。 ただし、新しい関数は、「+ float 」ではなく「 int +」に依存しているため、推論が容易です。 浮動小数点数はhttps://docs.python.org/tutorial/floatingpoint.html [本質的に不正確]です。

>>>

>>> 0.1 + 0.1 + 0.1
0.30000000000000004

>>> 0.1 + 0.1 + 0.1 == 0.3
False

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

Pythonの + float +`はhttps://en.wikipedia.org/wiki/IEEE_754[IEEE 754標準]に従い、53ビットの有効ビットを使用します。 その結果、約104日(2⁵³または約https://en.wikipedia.org/wiki/Names_of_large_numbers[9兆ナノ秒])を超える時間は、ナノ秒精度の浮動小数点数として表現できません。 対照的に、Python https://stackoverflow.com/a/9860611 [+ int +` is unlimited]であるため、整数値のナノ秒は、時間値に関係なく常にナノ秒の精度を持ちます。

例として、 `+ time.time()`は1970年1月1日からの秒数を返します。 この数値はすでに非常に大きいため、この数値の精度はマイクロ秒レベルです。 この関数は、 ` _ns `バージョンで最大の改善を示しています。 ` time.time_ns()`の解像度は、 ` time.time()+`よりもhttps://www.python.org/dev/peps/pep-0564/#analysis[3倍]程度です。 。

ところでナノ秒とは何ですか? 技術的には、10億分の1秒、または科学表記法を好む場合は「+ 1e-9 +」秒です。 ただし、これらは単なる数字であり、実際には直感を提供しません。 より優れた視覚資料については、https://en.wikipedia.org/wiki/Grace_Hopper#Anecdotes [Grace Hopper’s]の素晴らしいhttps://www.youtube.com/watch?v=JEpsKnWZrJ8 [ナノ秒のデモ]をご覧ください。

余談ですが、ナノ秒の精度で日時を操作する必要がある場合、 `+ datetime +`標準ライブラリはそれをカットしません。 明示的にはマイクロ秒のみを処理します。

>>>

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

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

代わりに、http://www.astropy.org/[+ astropy +`プロジェクト]を使用できます。 そのhttp://docs.astropy.org/en/stable/time/[+ astropy.time `]パッケージは、2つの ` float +`オブジェクトを使用して日時を表します。 」

>>>

>>> from astropy.time import Time, TimeDelta
>>> Time("2018-06-27")
<Time object: scale='utc' format='iso' value=2018-06-27 00:00:00.000>

>>> t = Time("2018-06-27") + TimeDelta(1e-9, format="sec")
>>> (t - Time("2018-06-27")).sec
9.976020010071807e-10

`+ astropy +`の最新バージョンは、Python 3.5以降で利用できます。

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

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

辞書の順序は保証されています

Python 3.6のCPython実装では、辞書を並べています。 (http://pypy.org/[PyPy]にもこれがあります。)これは、辞書のアイテムが挿入されたのと同じ順序で繰り返されることを意味します。 最初の例は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では、この順序付けは `+ dict +`の実装の良い結果でした。 ただし、Python 3.7では、挿入順序を保持する辞書はhttps://mail.python.org/pipermail/python-dev/2017-December/151283.html [言語仕様]の一部です。 そのため、Python> = 3.7(またはCPython> = 3.6)のみをサポートするプロジェクトに依存するようになりました。

+ async +」および「 + await +」はキーワードです

Python 3.5はhttps://www.python.org/dev/peps/pep-0492/[「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で導入され、イベントループ、コルーチン、フューチャーを使用して現代的な方法で並行性を処理します。 https://hackernoon.com/asyncio-for-the-working-python-developer-5c468e6e2e8e [やさしい紹介]をご覧ください。

Python 3.7では、 + asyncio +`モジュールはhttps://docs.python.org/3.7/whatsnew/3.7.html#asyncio [メジャーフェイスリフト]を取得しています。これには、多くの新しい関数、コンテキスト変数のサポートが含まれます( link:#context-variables [below])、およびパフォーマンスの改善。 特に注意すべきは、 `+ asyncio.run()+`です。これにより、同期コードからのコルーチンの呼び出しが簡単になります。 https://docs.python.org/3.7/library/asyncio-task.html#asyncio.run [+ asyncio.run()+`]を使用すると、イベントループを明示的に作成する必要はありません。 非同期Hello Worldプログラムを作成できるようになりました。

import asyncio

async def hello_world():
    print("Hello World!")

asyncio.run(hello_world())

コンテキスト変数

コンテキスト変数は、コンテキストに応じて異なる値を持つことができる変数です。 これらは、各実行スレッドが変数に対して異なる値を持つ可能性があるスレッドローカルストレージに似ています。 ただし、コンテキスト変数では、1つの実行スレッドに複数のコンテキストが存在する場合があります。 コンテキスト変数の主な使用例は、同時非同期タスクの変数を追跡することです。

次の例では、3つのコンテキストを作成し、それぞれに値「+ name 」の独自の値を設定します。 ` greet()`関数は、後で各コンテキスト内で ` 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)

このスクリプトを実行すると、Steve、Dina、およびHarryの順序が逆になります。

$ python3.7 context_demo.py
Hello Harry
Hello Dina
Hello Steve

+ importlib.resources +」を使用したデータファイルのインポート

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

  • データファイルへのパスをハードコードします。

  • データファイルをパッケージ内に配置し、 `+ file +`を使用して検索します。

  • https://setuptools.readthedocs.io/en/latest/pkg_resources.html [+ setuptools.pkg_resources +]を使用して、データファイルリソースにアクセスします。

これらにはそれぞれ欠点があります。 最初のオプションは移植性がありません。 `+ file `を使用するとより移植性が高くなりますが、Pythonプロジェクトがインストールされている場合は、zip内に配置され、 ` file +`属性がない場合があります。 3番目のオプションはこの問題を解決しますが、残念ながら非常に遅いです。

より良い解決策は、標準ライブラリの新しいhttps://docs.python.org/3.7/library/importlib.html#module-importlib.resources [+ importlib.resources +]モジュールです。 Pythonの既存のインポート機能を使用して、データファイルもインポートします。 次のようなPythonパッケージ内にリソースがあると仮定します。

data/
│
├── alice_in_wonderland.txt
└── __init__.py

`+ data `はhttps://realpython.com/python-modules-packages/[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?"

同様のhttps://docs.python.org/3.7/library/importlib.html#importlib.resources.open_binary [+ resources.open_binary()+]関数は、バイナリモードでファイルを開くために使用できます。 前のリンク:#customization-of-module-attributes [「モジュール属性としてのプラグイン」の例]では、 `+ importlib.resources `を使用して、 ` resources.contents()+`を使用して使用可能なプラグインを検出しました。 詳細については、https://www.youtube.com/watch?v = ZsGFU2qh73E [Barry WarsawのPyCon 2018トーク]をご覧ください。

Python 2.7およびPython 3.4+では、https://pypi.org/project/importlib_resources/[backport]を介して + importlib.resources +`を使用できます。 http://importlib-resources.readthedocs.io/en/latest/migration.html [+ pkg_resources `から ` importlib.resources +`への移行ガイド]が利用可能です。

開発者の秘

Python 3.7には、開発者としてのユーザー向けの機能がいくつか追加されています。 link:#the-breakpoint-built-in [すでに新しい + breakpoint()+`ビルトインが表示されています] さらに、いくつかの新しいhttps://docs.python.org/3.7/using/cmdline.html#id5 [+ -X +`コマンドラインオプション]がPythonインタープリターに追加されました。

`+ -X importtime +`を使用すると、スクリプトのインポートにかかる時間を簡単に把握できます。

$ python3.7 -X importtime my_script.py
import time: self [us] | cumulative | imported package
import time:      2607 |       2607 | _frozen_importlib_external
...
import time:       844 |      28866 |   importlib.resources
import time:       404 |      30434 | plugins

`+ cumulative `カラムには、インポートの累積時間が(マイクロ秒単位で)表示されます。 この例では、「 plugins 」のインポートには約0.03秒かかり、そのほとんどは「 importlib.resources 」のインポートに費やされました。 「 self +」列は、ネストされたインポートを除くインポート時間を示します。

これで、「-X dev +」を使用して「開発モード」を有効にできます。開発モードでは、デフォルトでは有効にするには遅すぎると見なされる特定のデバッグ機能とランタイムチェックが追加されます。 これには、https://docs.python.org/library/faulthandler.html#module-faulthandler [` faulthandler +`]を有効にして、重大なクラッシュのトレースバックと、より多くの警告とデバッグフックを表示することが含まれます。

最後に、 `+ -X utf8 `はhttps://docs.python.org/3.7/using/cmdline.html#envvar-PYTHONUTF8[UTF-8モード]を有効にします。 (https://www.python.org/dev/peps/pep-0540/[PEP 540]を参照してください。)このモードでは、現在のロケールに関係なく、テキストエンコーディングに ` UTF-8 +`が使用されます。

最適化

Pythonの新しいリリースには、それぞれ最適化のセットが付属しています。 Python 3.7には、次のような大幅な高速化があります。

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

  • メソッド呼び出しは一般に最大20%高速です。

  • Python自体の起動時間は10〜30%短縮されます。

  • `+ typing +`のインポートは7倍高速です。

さらに、より多くの特殊な最適化が含まれています。 詳細な概要については、https://docs.python.org/3.7/whatsnew/3.7.html#optimizations [このリスト]を参照してください。

これらすべての最適化の結果は、https://speed.python.org/[Python 3.7は高速]です。 これは、これまでにリリースされたhttps://hackernoon.com/which-is-the-fastest-version-of-python-2ae7c61a6b2b[CPythonの最速バージョン]です。

だから、アップグレードする必要がありますか?

簡単な答えから始めましょう。 ここで見た新機能を試してみたい場合は、Python 3.7を使用できる必要があります。 https://github.com/pyenv/pyenv [+ pyenv +]やhttps://www.anaconda.com/download/[Anaconda]などのツールを使用すると、Pythonのいくつかのバージョンを簡単に並べてインストールできます。 Python 3.7をインストールして試してみてもマイナス面はありません。

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

実稼働環境をアップグレードする前に常に徹底的なテストを行う必要があることは明らかな警告であるため、Python 3.7には以前のコードを壊すものはほとんどありません( `+ async `および ` await +`がキーワードになるのは一例です)。 すでに最新のPythonを使用している場合、3.7へのアップグレードは非常にスムーズに行われるはずです。 少し保守的になりたい場合は、最初のメンテナンスリリース(Python 3.7.1)のリリース(https://www.python.org/dev/peps/pep-0537/#maintenance-releases)を待つことをお勧めします。 [2018年7月に暫定的に予想される]。

プロジェクトを3.7のみにするべきだと主張するのは難しいです。 Python 3.7の新機能の多くは、Python 3.6へのバックポート(データクラス、 + importlib.resources +)または便利さ(起動とメソッド呼び出しの高速化、デバッグの容易化、および `+ -X +`オプション)として利用できます。 後者では、Python 3.6(またはそれ以下)との互換性を保ちながら、Python 3.7を自分で実行することで利用できます。

コードをPython 3.7にロックする大きな機能は、link:#customization-of-module-attributes [+ getattr ()+ on modules]、link:#typing-enhancements [タイプヒントの前方参照]、およびlink:#timing-precision [ナノ秒の `+ time +`関数]。 これらのいずれかが本当に必要な場合は、先に進んで要件を改善する必要があります。 そうでなければ、あなたのプロジェクトは、Python 3.6でしばらく実行することができれば、おそらく他の人にとってより役に立つでしょう。

アップグレード時に注意すべき詳細については、https://docs.python.org/3.7/whatsnew/3.7.html#porting-to-python-37 [Porting to Python 3.7 guide]を参照してください。