Python3
文字列操作

文字列操作用のライブラリ Rev1.1

以前の記事でコメントにて素晴らしいアドバイスをいただきましたので、それに合わせて修正しました。

標準で StringIO というのもあります。インタフェースを合わせるといいかもしれません。

StringIO・・・知らなかった。そんな機能があったとは。
個人的には最後の項目に書いたalldecodeだけあれば十分なのだが、2週間前にやり始めたPythonの勉強も兼ね、そのまま関数は互換で使えるようにリファクタリングしてみることに。

作成目的

前の記事と同じです。

古い設計のソフトや、自分がJSONやXMLを知らなかった頃のソフトには、固定文字数で定義されたプレーンテキストのファイルやログが複数ある。これをその都度可視化するために展開するのは非常に厄介・・・

[商品コード(8)][商品名(20)][単価(5)][設定価格(5)][原価率(3)][フラグ(16)]
↓これがずっと続く

また、昔は根気よく文字数指定してFor文でぶん回して文字位置をカウントしていたのだが、Pythonでやろうとしたときにスライスで書くのがめんどくさくなってきた。

list[x]['項目名'] = line.[x*MAX+n:x*MAX+n+offset]

こんなのを延々と回している。文字数を指定したら上から順当に取得して、適当にリストまたはDictionaryに入れてあげたほうが使いやすい。

ファイルも一つならいいのだが、まだ複数種類眠っていそうな気もする。なるべくカンタンに展開してMySQLサーバーに書いたり、JSONで保管するほうが今後のメンテナンスも楽になると予測し、以後は他のソフトでも使用する可能性があるので、今回は移植することを視野に自分用のモジュールを作った。

また、まだPythonは触り始めて2週間。いままでのC++、C#といった言語との違いに四苦八苦しながらも勉強も兼ねて自分で作ることにした。

実装

以下実装例と解説

宣言

import DecordStream
ds = DecordStream.DecordStream()

文字列セット

set( string )

この関数で文字列を渡します。また、カーソルを初期位置に戻します。

ds.set("Hello World!")

文字列取得

read() #引数なし、または 0

引数なし、または0をセットしたときは、セットされている文字列を全部取得します。

ds.set("Hello World!")
temp = ds.read() # 何も指定しない
print(temp)
#---- Console ----
#> Hello World!

read( int ) #文字数を指定

以下の通り、指定文字数ぶん、文字列から取得します。

ds.set("Hello World!")
a = ds.read(6)
b = ds.read(6)
print(a)
print(b)
#---- Console ----
#> Hello World!

戻り値

戻り値は文字列ですのでキャストも可能です。

ds.set("0123456789")
ary = []
ary.append(str(ds.read(5)))
ary.append(int(ds.read(5)))
print(ary)
#---- Console ----
#> ['01234', 56789]

以下のようにリストに入れてしまうことも可能。

ds.set("0123456789")
ary = []
ary.append(int(ds.read(1))
#10回繰り返す
print(ary)
#---- Console ----
#> [0,1,2,3,4,5,6,7,8,9]

カーソル

seek( int ) #カーソル移動(絶対位置)

0からスタートの絶対値でカーソル位置を固定します。

ds.set("0123456789ABCDEF!")
ds.seek(10)
a = ds.read(7)
print(a)
#---- Console ----
#> ABCDEF!

cur #カーソル位置取得

カーソル位置を取得します。

ds.set("0123456789ABCDEF!")
a = ds.read(6)
print( ds.cur )
#---- Console ----
#> 6

seekadd( int ) #カーソル移動(相対位置)

今のカーソル位置を基準に+、ーでカーソル位置を変更します。

ds.set("0123456789ABCDEF!")
a = ds.read(6)
# ds.seek(ds.cur + 4) こんなことしなくてもいい
ds.seekadd(4)
b = ds.read(7)
print(a)
print(b)
#---- Console ----
#> 012345
#> ABCDEF!

文字列置換

write( string ) #現在のカーソル位置以降を置換する

カーソル位置以降の文字列を置換します。

print("write前:" + ds.read())
ds.seek(10)
ds.write("123456!")
print("write後:" + ds.read())
#---- Console ----
#> write前:0123456789ABCDEF!
#> write後:0123456789123456!

リストをもとに一括で分割

alldecode( list ) #一括で分割

文字数のリストを引数に指定します。その文字数ごとに分割したリストを返します。
一括で行うため、固定電文長の複数文字列を展開するのに便利です。

ds.set("HOGEhogeFUGAfuga!");
lists = [4,4,4,4,1]
ary = []
ary = ds.alldecode(lists)
print(ary)
#---- Console ----
#> ['HOGE', 'hoge', 'FUGA', 'fuga', '!']

まとめ

今回役立った点

最初はPythonのClass定義、関数定義に四苦八苦しながらもなんとか実装できた。

read関数にて使用した、指定なしで関数をコールする方法など、細かい技が入っている。

また、__curなどPrivate定義や、@propertyでプロパティ化するなど、C#ではよく行っていたようなモジュール設計の方法についても理解が進んだため、自分の中ではいい勉強になったかと思います。

最後にコードを掲載しておきます。気が向いたらGitHubとかに投稿できたらなぁと。

コード

DecordStream.py
class DecordStream(object):
    __line = ""
    __cur = 0

    @property
    def cur(self):
        return self.__cur

    def set(self, str):
        self.__line = str
        self.__cur = 0
        return

    def read(self, count = 0):
        if( count > 0 ):
            ret = self.__line[self.__cur:self.__cur+count]
            self.__cur += count
        else:
            ret = self.__line
        return ret

    def seek(self, offset):
        self.__cur = offset
        return

    def seekadd(self, add):
        self.__cur += add
        return

    def write(self,add):
        self.__line = self.__line[:self.__cur] + add
        return

    def alldecode(self, lists):
        ret = []
        for size in lists:
            ret.append(self.__line[self.__cur:self.__cur+size])
            self.__cur += size
        return ret