読者です 読者をやめる 読者になる 読者になる

ファイル操作の練習とStringIO(1)

ファイルにアクセスして、色々と細かい操作をしないといけないことはあるかと思いますが、ぼくは正直ファイル操作処理がOS任せになってブラックボックスなところもあって馴染めません。さらにファイル操作した後、ファイルの内容が変更してしまっては、くり返しテストを実行することもできません。

ファイルの操作をしてから、ファイルの中身を元に戻すだけでもすこし面倒です。例えば、c言語で"hello, "(ファイル名は_filse/hello.txt)を"hello, world"に変更し、また"hello, "に戻す操作(write_and_truncate(void))を5回実行するというコードを書いてみると、こんな感じになります。

// for ftruncate
#include <unistd.h> 
#include <sys/types.h>
#include <stdio.h>

// for open(2)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
// for fstat
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

// for lseek
#include <sys/types.h>
#include <unistd.h>
//for exit
#include <stdlib.h>
//others
#include <assert.h>
#include <string.h>

static int count;
off_t tell(int fd) {
  return lseek(fd, 0, SEEK_CUR);
}
void write_and_truncate(void) {
  int fd = open("hello.txt", O_RDWR);
  if(fd < 0) {
    fprintf(stderr, "open failed\n");
    exit(1);
  }
  struct stat st;
  if(0 > fstat(fd, &st)) {
    fprintf(stderr, "fstat failed\n");
    exit(1);
  }

  assert((off_t)0 == tell(fd));
char buf[80];
  memset(buf, 0, sizeof buf);
  assert(st.st_size == read(fd, buf, st.st_size));
  assert(!strcmp("hello, \n", buf));
  assert((off_t)8 == tell(fd));

  lseek(fd, -1, SEEK_END);
  assert(6 == write(fd, "world\n", 6));
  
  lseek(fd, 0, SEEK_SET);
  read(fd, buf, st.st_size+5);
  assert(!strcmp("hello, world\n", buf)); 
  
  lseek(fd, 7, SEEK_SET); 
  write(fd, "\n", 1);
  assert((off_t)8 == tell(fd));
  assert(!ftruncate(fd, 8));
  close(fd);
  count++;
}
int main(void) {
  int i;
  count = 0;
  for(i = 0; i < 5; i++)
    write_and_truncate();
  assert(5 == count); 
  return 0;
}

lseekはファイルポインタをずらすシステムコールです(lseekはwebサーバーの実装なんかで使れるところを見ました)。ftruncateは指定したサイズに引き伸ばしたり、切ったり(トランケートするわけです)する関数です。Pythonで書いたところでそんなに変わらないです。

path = '_files/hello.txt'
for i in range(5):
  f = open(path, 'r+')
  assert('hello, \n' == f.read()) 
  f.seek(-1, 2)
  assert(7L == f.tell())
  f.write('world')
  f.seek(0)
  assert('hello, world' == f.read())
  f.seek(7)
  f.write('\n') 
  f.truncate(8)
  f.seek(0)
  assert('hello, \n' == f.read()) 
  f.close()

c言語のただのラッパーにすぎないことが分かるかと思います。ファイル操作系の関数の処理はOSがやることなので、試行錯誤するしかない上にファイルを元に戻すのも面倒だと思います。メモリ上で話を閉じれたらファイル処理の絡んだテストが書きやすくなるのにと思うかもしれません。ファイルの生成は文字列をコンストラクタなどに文字列を渡すだけで良くなります。ファイルはテストの後では、変化していても良くなります。幸いPythonには、StringIO(ピュアpythonライブラリ), cStringIO(c拡張モジュール)があり、ほぼその役割を果たしてくれます。StringIOはPythonコードを読めば良いだけなのですぐに理解できるはずです。次回からStringIOについてゆっくりと見ていこうと思います。