最近追加されたPythonの便利機能とこれからのPython in #ll2018jp

Learn Languages 2018 というイベントで、最近のPythonについて発表してきました。

(一昨年まではLightweight LanguageでLLイベントだったのが、去年からLearn Languagesイベントになったらしい!)

www.slideshare.net

せっかくなのでポイントだけでも書き起こして見ようと思います。

ここ1~2年で便利になった機能

1. The pathlib module (PEP 428)

pathlibはファイルパスに関するモジュールで3.4で導入されました。

ただ、build-inのopenやos.pathモジュールがpathlib.Pathオブジェクトを受け入れられるようになったのがPython3.6(PEP519)となっています。

3.6以降ではぜひos.pathよりもpathlibを優先して使っていきたいです。

使用例: スクリプトのディレクトリを取得

今までは、スクリプトのあるディレクトリを取得するとき

import os
root_path = os.path.dirname(os.path.abspath(__file__))

こんなかんじでos.pathモジュールを駆使していました。

これを、pathlibを使うと

import pathlib
root_path = pathlib.Path(__file__).resolve().parent

というように

  1. 現在のファイルのPathオブジェクトを作る
  2. resolveで相対パスを絶対パスに解決
  3. 親ディレクトリの取得

みたいに直感的にかけるようになりました。

使用例: ディレクトリ以下のファイルの探索

ディレクトリ以下のcsvファイル探索をこんなふうに簡単にかけます。

data_path = pathlib.Path('data')
for filename in data_path.glob('**/*.csv'):
    print(filename)

# data/csv/data1.csv
# data/csv/data2.csv

今までは os.walkを使って書いてたのに比べるとだいぶ楽ちんです。

2. Type hints (PEP 484)

TypeHintっていうのは
引数や関数の返り値に型注釈をつけられる仕様です。

def greeting(name: str) -> str:
    return 'Hello ' + name

print(greeting.__annotations__)
# {'name': <class 'str'>, 'return': <class 'str'>}

このサンプルだと

  • 引数nameはstr型であり
  • 関数greetingはstr型を返す

ということを示しています。

これはあくまで静的解析のための構文で、 実際にビルドするときに型チェック等は行われるわけではありません。 しかし、この構文によってIDE等での解析が可能になります。

ただ、もともと型注釈の評価はコンパイル時に行われていたので
、

class C:
    @classmethod
    def from_string(cls, source: str) -> C:
        …

こんなふうにCというクラスが自分自身のクラスを返すという型注釈をつけると、 その時点ではまだクラスCはクラスCの存在を知らず、実行時にエラーが起こってしまっていました。

これを踏まえ、3.7ではアノテーションの遅延評価(PEP 563) が導入されました。 型注釈の評価が実行時に行われるようになり、このサンプルのような前方参照が可能になりました。

これでTypeHintがかなり使いやすくなったのではないかと思います。

ただ、互換性のない変更になるので利用する際には

from __future__ import annotations

の一文が必要になります。

この挙動は4.0から標準の挙動になる予定です。

3. Data Classes (PEP557)

クラス変数アノテーションを使って変数を定義することで、
__init__, __repr__, __eq__, __ne__, 
__lt__, __le__, __gt__, __ge__
を自動的に生成できるDataClassが3.7で導入されました。

今までname, age, itemsの3つをもった
Userクラスを定義しようとすると、こんな感じでした。

from typing import List

class User:
    name: str
    age: int
    items: List[int] = None

    def __init__(self, name: str, age: int = 0):
        self.name = name
        self.age = age
        self.items = [] 
        
    def __repr__(self):
        return f’{self.__class__.__name__}’
                        ....

   user = User('Taro', 10)
    # procedue User(name='Taro', age=10, items=[])

インスタンス変数が多いクラスを書くときに、 この__init__ で引数と代入部分で
何度も同じ変数名書くのめんどくさい…
と思ったことないですか。
私はよく正規表現駆使して__init__生成してました。

またitemのようにmutableなオブジェクトは
関数の
デフォルト引数にitem = []
というように書くことはできません。

上記サンプルのように__init__で初期化を書くようにしないと、 インスタンス間で
オブジェクトがシェアされるような挙動になってしまうためです。 (これはPythonの落とし穴!)

3.7で導入されたDataclasses(PEP 557)のdataclassデコレータを使うと、 上記コードはこんな感じになります。

from dataclasses import dataclass, field
from typing import List

@dataclass  
class User:
    name: str
    age: int
    items: List[int] = field(default_factory=list)

user = User('Taro', 10)
# procedue User(name=‘Taro’, age=10, items=[])

__init____repr__をかかなくてもよくなり、すっきりしました。
また、このdataclassesモジュールのfield関数を使うと
デフォルト値としてlistのような
mutableなvalueを設定できます!

dataclassは、あくまで
自動で特殊関数をはやしてくれる普通のクラスなので、
どんどんこの機能は使っていけたらいいなと思っています。

Other update

他にも特に面白そうな新規機能としては以下を挙げておきます。

興味がある方は見てみてください。

これからのPython: Python3.8

Python3.8はいまのところ2019年10月にリリース予定です。

組み込まれる予定の機能で一つ大きなのが
「Assignment Expression(代入演算子)の導入」があるのでこれについて少しだけ書いていきます。

Assignment Expressions (PEP 572)

今までなら正規表現のmatchしたオブジェクトを
一度変数に入れてifで評価してたのが

match = pattern.match(data)
if match:
    result = match.group(1)

代入演算子を使うと、こんな感じで代入と評価を同時にすることができます。

if (match := pattern.search(data)) is not None:
    result = match.group(1)

便利ですね。

しかし、これは本当にPythonicなのか?ということで、導入までには議論を呼びました。

Pythonには、 Zen of Python (PEP 20) の

There should be one-- and preferably only one --obvious way to do it.

にもあるように「同じことを複数の書き方ができるようにしない」ということを大切にしています。
Pythonはインクリメント演算子もないくらいその部分を気にしているのに、:=はいいのか・・・という気持ちは個人的にはあります。

ただPythonも色んな人に使われるようになってきたので、 そのあたりは柔軟になっていくのかもしれないです。

(おまけ) Retirements of Guido van Rossum

ところで、最近PythonのBDFL(Benevolent Dictator For Life, 最終意思決定者)であるGuidoがBDFLの引退を発表しました。

... I would like to remove myself entirely from the decision process. I'll still be there for a while as an ordinary core dev, and I'll still be available to mentor people -- possibly more available. But I'm basically giving myself a permanent vacation from being BDFL, and you all will be on your own. ....

[python-committers] Transfer of power

Guidoはこのポジションについて
後継者を決めるつもりはなく、 あとはみんなで決めてね、というように言っています。

今後のPythonの意思決定がどうなっていくかは追っていきたいところです。