Pythonの新ライブラリ!「AbsStream」を使えば文字列やリストのパースが簡単に

37, 2019-07-10

目次

「AbsStream」というライブラリを作った

「AbsStream」というPython3用のライブラリを作りました。

narupo/absstream: Abstract Stream
absstream · PyPI

AbsStreamの「Abs」は「Abstract」の略です。
「Abstract Stream」で直訳すると「抽象ストリーム」ですね。
その名の通りデータをストリームの形に抽象化します。

AbsStreamのライセンス

MITライセンスです。

MITライセンスなのでこのライブラリは無償・無制限に利用できます。
ただし、利用に関して著作者(私、narupo)は一切の責任を負いません。

安心と安定のMITライセンス

文句言うなよ

AbsStreamのインストール方法

環境にvirtualenvがインストール済みであれば最初に仮想環境を作るのがいいでしょう。

$ virtualenv -p `which python3` venv
$ source venv/bin/activate

その後、仮想環境下のpipでAbsStreamをインストールできます。

(venv) $ pip install absstream
(venv) $ pip list

AbsStreamの特徴

文字列とリストにファイルオブジェクトっぽくアクセスできます。
設計の参考にしたのはC言語で利用できるFILEオブジェクトです。

このFILEは、fgetcというインターフェースをユーザーに提供しています。
AbsStreamにもgetというメソッドが定義されており、ほぼfgetcと同じ動作をします。

ただし、AbsStreamはデータに対するアクセスは読み込みだけです。
データへの書き込み、更新はできません。

AbsStreamの使い方

AbsStreamの使い方を簡単に紹介します。

Streamをインポートする

AbsStreamではStreamというクラスを利用できます。
インポート方法は以下の通りです。

from absstream.stream import Stream

このStreamというクラスがAbsStreamの本体です。

Streamをコンストラクトする

Streamの使い方ですが、まず最初に以下のようにコンストラクト(構築)します。

strm = Stream('123')

コンストラクターの第一実引数には抽象化したいデータを渡します。
Streamはstrlisttupleのデータタイプを抽象化できます。

strm = Stream('string')
strm = Stream(['l', 'i', 's', 't'])
strm = Stream(('t', 'u', 'p', 'l', 'e'))

抽象化といっても、Pythonではこのみっつのデータタイプは最初から抽象化されているんですけどね。

虎の威を借りる狐!

データにアクセスする

ET 老人と子供

Streamからデータを取得するにはgetメソッドを使います。

strm = Stream('123')
strm.get() # '1'

このgetメソッドは、現在の読み込み位置のデータを返したあとに読み込み位置をひとつ進めます。
C言語のfgetcと同じ仕様ですね。

strm = Stream('123')
strm.get() # '1'
strm.get() # '2'

getメソッドはストリームを読みつくすとStream.EOFを返します。
EOFとは(End Of File)のことで、ファイル終端を表す記号ですね。

strm = Stream('123')
strm.get() # '1'
strm.get() # '2'
strm.get() # '3'
strm.get() # Stream.EOF

ループで回す際に、このEOFを使えばストリームを読みつくした直後にループから脱出できます。

while True:
    c = strm.get()
    if c == Stream.EOF:
        break
    print(c)

また、EOFに関しては専用のメソッドでeofがあり、これを使えばストリームがEOFに達しているかチェックできます。

if strm.eof():
    print('reached EOF')

よって、さきほどのループは以下のように書き換えができます。

while not strm.eof():
    c = strm.get()
    print(c)

現在の読み込み位置を基点にデータを取得したい場合はcurメソッドが使えます。
curメソッドは第一引数にオフセットを渡します。
たとえば、現在の読み込み位置から1つ先のデータを取得したい場合はcur(1)のようにします。

strm = Stream('123')
strm.cur() # '1'
strm.cur(1) # '2'
strm.cur(2) # '3'
strm.cur(3) # Stream.EOF

curメソッドもストリームの範囲外へアクセスするとStream.EOFを返します。

このcurメソッドを使えば文字列のパースがすごい楽になります。
たとえば文字列の中からcatという単語を発見したいとします。
その場合はループ中で以下のようにすることでcatを検出できます。

while not strm.eof():
    if strm.cur() == 'c' and strm.cur(1) == 'a' and strm.cur(2) == 't':
        print('found "cat"')
    strm.next()

添え字によるアクセスだと範囲外の参照エラーが発生する可能性がありますが、Streamでは範囲外の参照はEOFを返すのでその心配はありません。

もっとも、Pythonでは添え字の範囲アクセスで似たようなことは可能です。

src = "blah cat blah"
i = 0
while i < len(src):
    if src[i:i+3] == 'cat':
        print('found "cat"')
    i += 1

しかし、この方法の問題点として添え字iとデータsrcをセットで持ち運ばないといけない問題があります。
Streamは添え字を隠ぺいしているので、その点において持ち運びが楽になっています。

読み込み位置のシーク

ランナー達

読み込み位置を変更するにはprevnextメソッドを使います。

prevは読み込み位置を1つ戻し、nextは読み込み位置を1つ前に進めます。

strm = Stream('123')
strm.cur() # '1'

strm.next()
strm.cur() # '2'

strm.prev()
strm.cur() # '1'

実際の文字列のパースでは、読み込み位置を1つ戻したい、または1つ進めたいという時が頻繁にあります。
C/C++系の言語ではポインタでその課題を解決することが多いです。
たとえばCの文字列では以下のようにするとポインタを操作できます。

const char *str = "123";
const char *p = str;

p++; // ポインタを1つ進める
p--; // ポインタを1つ戻す

これと同じことをStreamのprevとnextでは実現しています。

さらに読み込み位置を直接変更したい場合は、indexプロパティーが使えます。
indexプロパティーに整数を代入することで、読み込み位置を直接変更できます。

strm.index = 10

このindexプロパティーによって読み込み位置の保存と復元が可能です。

index = strm.index # 読み込み位置の保存

strm.get() # 読み込む
strm.get()
strm.get()

strm.index = index # 読み込み位置の復元

便利だニャー

Streamが抱える問題

悩む少女

Stream.EOFですが、これはただの整数の-1です。

ですので、たとえば整数-1を含んだリストなどはパースに失敗することがあります。

strm = Stream([0, -1, -2])

while True:
    c = strm.get()
    print(c)
    if c == Stream.EOF: # ここでEOFを誤検出する
        break

↑のコードではデータの読み込みが-1に達した時点で終了します。
これを回避するにはeofメソッドを使います。

strm = Stream([0, -1, -2])

while True:
    c = strm.get()
    print(c)
    if strm.eof():
        break

この問題にはいずれ対応したいですが、いまのところ対応予定はありません。

おわりに

自画自賛になってしまいますが、なかなか便利なライブラリができたと思っています。
実際、私のプロダクトではもっとも利用頻度が高いライブラリでした。
そのため、今回公開することにしました。

AbsStreamがあなたのパースを効率化できることを願っています。
以上、narupoでした。

スポンサーリンク

スポンサーリンク

スポンサーリンク

スポンサーリンク