メモリ上への一時ファイルの作り方

#概要
一時ファイルを作成して、別の処理に渡して直ぐに破棄することがよくある。通常、tempfileライブラリーをインポートすればいい。
だが、ディスクへのI/Oを減らそうとすれば、RAMDISKに書き込む方がいい。/tmp以下にうまいことtmpfsというRAMDISKが割当られている場合は問題ない。
web上にて[memory-tempfile](https://pypi.org/project/memory-tempfile/)という新しいライブラリがあったので修正しながら使ってみた。

#環境
LinuxMint19.1+
python3

#まずはテスト

```sh
pip3 install memory-tempfile
```
とすると導入できる。

```python
#memory_tempfileを使う場合
 #必ず初期化後、インスタンスでメソッドを用いる
 from memory_tempfile import __version__


def test_version():
    assert __version__ == '0.1.0'


def example1():
    from memory_tempfile import MemoryTempfile

    mem_tempfile = MemoryTempfile()
    print(mem_tempfile.fallback)
    
    with mem_tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as td:
        print(td.name)
        pass

example1()

```
以上のファイルを実行するとエラーが出て動かない。そこで、memory_tempfileのライブラリが格納されている場所(例:~/.locla/lib/python3.6/memory_tempfile/memory_tempfile.py等)を見る。

#原因
memory_tempはtempfileのオーバーライドであることが分かった。
そして、__init__内で何処がRAMDISKとして利用できるか調べて、tempfileのメソッド取る引数のdirを設定している。
(例:NamedTemporaryFile(dir=/hoge/hoge))
dirにはRAMDISKを指定したい。(例:/dev/shm等)だけどうまく動作していないようである。
修正は３箇所
* OSの判定
* RAMDISKの場所
* オーバーライドの引数


##OSの判定
osがLinuxならば引っかかる用に直した。(import reが必要)

```python
if platform.platform() == "Linux":
```

修正後

```python
os_platform = platform.platform()
        
        if re.search("Linux", os_platform):
```

##RAMDISKの場所
次に、RAMDiskを調べる部分である。
ファイルシステムとしてtempfs,ramfsというのがRAMDISKになっている。だが、調べるべき部分が変数mpのリストの中で8番目となっている。中身をみると9番目がファイルシステムを示している。
さらに、dict形式のfor文において、items()をつけないとうまく行かない。この場合はself.usable_pathsが辞書形式である。self.usable_pathsはキーとしてファイルの場所を示し、値としてRAMDISKかどうかを保持している。(#の部分の付近が修正した場所である。)

```python
try:
                    dev = os.stat(path).st_dev
                    major, minor = os.major(dev), os.minor(dev)
                    mp = mnt_info.get("{}:{}".format(major, minor))
                    if mp and mp[8] in self.filesystem_types:
                        self.usable_paths[path] = mp
                except FileNotFoundError:
                    pass
            
            for k, v in self.usable_paths:
                if not v:
                    del self.usable_paths[k]
            
            if len(self.usable_paths) > 0:
                self.tempdir = self.usable_paths.keys()[0]
```

修正後

```python
try:
                    dev = os.stat(path).st_dev
                    major, minor = os.major(dev), os.minor(dev)
                    mp = mnt_info.get("{}:{}".format(major, minor))
                    if mp[9] in self.filesystem_types:  #I think that 9 number is "tmpfs" or "ramfs"
                        self.usable_paths[path] = mp[9]
                except FileNotFoundError:
                    pass
            
            tmp_del_element = []
            for k,v in self.usable_paths.items():
                if not v:
                    tmp_del_element.append(k)  #select dir(check False)
            for l in tmp_del_element:
                del self.usable_paths[l]
            
            if len(self.usable_paths) > 0:
                list_usable_paths_keys = list(self.usable_paths.keys())  #OrderdDict.keys() is not list type
                self.tempdir = list_usable_paths_keys[0]  #We get ramdisk dir!
```

これで、RAMDISKのディレクトリがself.tempdirに入るようになった。
だが、オーバーライドの部分に問題がある。

##オーバーライドの修正
引数にてbufferingをNoneから-1にする。
tempfileのメソッドをオーバーライドしている。これまで２つの修正によりself.tmpdirにRAMDISKの場所は指定できた。それでも、一部のメソッドにおいて問題がある。
 * SpooledTemporaryFile
 * NamedTemporaryFile
 * TemporaryFile
[公式](https://docs.python.org/ja/3/library/tempfile.html)を見ると、引数のbufferingはNoneである。だが、open関数の引数に準拠すると書いている。[open()](https://docs.python.org/ja/3/library/functions.html#open)によるとbufferingは整数であり、デフォルトは-1となっている。
bufferingを指定しなければ、pythonに怒られない。しかし、オーバーライドでNoneを指定しているためエラーが出てしまう。
公式ではNone指定がデフォルトとなっているため齟齬が生じているようだ。これは公式が違うのかどうかは私自身はわからない。

```python
    def SpooledTemporaryFile(self, max_size=0, mode='w+b', buffering=None, encoding=None, newline=None,
                             suffix=None, prefix=None, dir=None):
        return tempfile.SpooledTemporaryFile(max_size=max_size, mode=mode, buffering=buffering, encoding=encoding,
                                             newline=newline, suffix=suffix, prefix=prefix,
                                             dir=self.tempdir if not dir else dir)

    def NamedTemporaryFile(self, mode='w+b', buffering=None, encoding=None, newline=None,
                           suffix=None, prefix=None, dir=None, delete=True):
        return tempfile.NamedTemporaryFile(mode=mode, buffering=buffering, encoding=encoding, newline=newline,
                                           suffix=suffix, prefix=prefix, dir=self.tempdir if not dir else dir,
                                           delete=delete)
    
    def TemporaryFile(self, mode='w+b', buffering=None, encoding=None, newline=None,
                      suffix=None, prefix=None, dir=None):
        return tempfile.TemporaryFile(mode=mode, buffering=buffering, encoding=encoding, newline=newline,
                                      suffix=suffix, prefix=prefix, dir=self.tempdir if not dir else dir)
```

修正後

```python
    #bufferingはopenの引数と同義なので、Noneでなくて-1のはず
    def SpooledTemporaryFile(self, max_size=0, mode='w+b', buffering=-1, encoding=None, newline=None,
                             suffix=None, prefix=None, dir=None):
        return tempfile.SpooledTemporaryFile(max_size=max_size, mode=mode, buffering=buffering, encoding=encoding,
                                             newline=newline, suffix=suffix, prefix=prefix,
                                             dir=self.tempdir if not dir else dir)

    def NamedTemporaryFile(self, mode='w+b', buffering=-1, encoding=None, newline=None,
                           suffix=None, prefix=None, dir=None, delete=True):
        return tempfile.NamedTemporaryFile(mode=mode, buffering=buffering, encoding=encoding, newline=newline, suffix=suffix, dir=self.tempdir if not dir else dir, delete=delete)
    
    def TemporaryFile(self, mode='w+b', buffering=-1, encoding=None, newline=None,
                      suffix=None, prefix=None, dir=None):
        return tempfile.TemporaryFile(mode=mode, buffering=buffering, encoding=encoding, newline=newline,
                                      suffix=suffix, prefix=prefix, dir=self.tempdir if not dir else dir)
```

#利用方法
以上の修正をすると利用できるが注意点として、
tempfileと同様には使えない。一旦、MemoeryTempFile()としてインスタンスを作成した上で各メソッドが利用できる。
私の場合だと/run/user/1000にファイルが作成されていた。もちろん再起動すると無くなっている。
メモリ上の一時ファイル利用の一助になれば思う。

#参照場所
[公式ドキュメント](https://docs.python.org/ja/3/)
[memory-tempfile](https://pypi.org/project/memory-tempfile/)
2019年2月にできたばかりで、現在でも初期バージョンのままである。 
2020年1月現在は公式も更新されて2.2.2となり修正が入っているようだ。実験はしてないが、ソースも見る限り問題はないようだ。そして、ドキュメントにもこの記事と同様な指摘がなされている部分がある。