最近追加された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の意思決定がどうなっていくかは追っていきたいところです。

好きな品詞の組み合わせのフレーズを抜き出すPythonパッケージ「negima」を作った

日本語の自然言語処理が絡んだ作業をする際に、

  • 名詞だけ抜き出したい
  • 名詞だけ抜き出したいが、接頭詞の「未」「非」とかもくっつけて抜き出したい
  • 形容詞を抜き出したいが、否定の「ない」もくっつけて抜き出したい

みたいに形態素解析をしたあとに形態素同士をつなげてフレーズの抽出をしたいシチュエーションがよくあると思う。

そういった特定の品詞の組み合わせをルールを定義することで、フレーズを抜き出せるPythonパッケージ「negima」を作った。

概要

例えば複合名詞を抽出したい場合、このようなルールを定義する。

id min max pos0 pos1 pos2 pos3 pos4 pos5
nouns 0 2 接頭詞
1 4 名詞 一般|サ変接続|数
0 2 名詞 接尾

このルールをnoun.csvとしてファイルに定義したとすると、

  1. 0個以上2個以下の接頭詞ではじまり、
  2. 1個以上4個以下の名詞(一般orサ変接続or数)があり、
  3. 0個以上2個以下の名詞(接尾)を持つ

ような、フェーズを以下のコードで抽出することができる。

sample.py

from negima import MorphemeMerger
mm = MorphemeMerger()
mm.set_rule_from_csv('noun.csv')

words, _ = mm.get_rule_pattern('約5000人が国立競技場に駆けつけた')
print(words)
$ python sample.py
['約5000人', '国立競技場']

内部的には、定義されたルールから以下のような木構造を作成し、matchするかを確かめている。 f:id:cocodrips:20180820144644p:plain

詳しいルールの書き方はリポジトリREADME.mdに書いてあるが、 ルールファイルはcsv/tsv/エクセルで定義することが可能で、複数のルールを同時に定義することができる。

ルールの定義順は関係なく、id列につけたルールの名前によりsortされ、小さい順に優先度が高いルールとなる。

例えば以下のような複数のルール定義する。

id min max pos0 pos1 pos2 pos3 pos4 pos5
999_noun 名詞
100_set 1 4 名詞
助詞 格助詞 係助詞
形容詞 自立
0 助動詞 特殊・ナイ
200_verb 動詞 自立
0 助動詞 特殊・ナイ

レベルが低すぎてボスに勝てない という文章から抽出を行うと、

  • レベルが低い: 100_set
  • ボス: 999_noun
  • 勝てない: 200_verb

の3パーツになる。 レベルは名詞なので 999_noun のルールにも引っかかるが、100_set のルールのほうが優先度が高いため、そちらの組み合わせが優先される。

インストール

pypiに登録してみた。 pypi.org

利用するには

  • Python3.4以上
  • mecabのインストール

が必要になる。これらを満たせば、

$ pip install negima

でインストールできる。

さいごに

こういうのあるでしょって思って調べたんだけどなかなか見つけられなくて、 仕事で使うために作ったんだけど、結構便利だったのでpypiに登録してみました。
(パッケージ名は可愛い名前の食べ物のなかでpypiにないやつを探した!パーツを組み合わせる感じはねぎまっぽさあると思う!)

不具合等あればissue/PRだしていただければ嬉しいです。
GitHub - cocodrips/negima