LoginSignup
14
13

More than 5 years have passed since last update.

pythonのioオブジェクトをtypingする

Last updated at Posted at 2018-08-21

TL;DR

Pythonのイマドキの書き方では、関数の引数および返り値の型を明示的に記載して書くことが多いと思う。
自分で作ったクラスや、よく使う型については問題ないんだが、IOに関連するオブジェクトのタイピングに戸惑った。
(教養のある人はたぶん、普通にわかる問題かもしれません)

※8/23 Union部分を修正しました

問題

今回、面倒な気分になったのは以下の場面です。

example.py
def func(fp):
    """
    Args:
        fp: file path 

    Returns:
        opened object
    """
    return open(fp, 'r')

要は、ファイルパスを渡して、開いたファイルを返すみたいなケース。

これの返り値の型指定に戸惑った。。。

結果

解決策はこういう感じ

sol.py
from typing import Union, IO
import os

def func(fp: Union[str, bytes, os.PathLike]) -> IO:
    """
    Args:
        fp (str, bytes or os.PathLike object) : File Path 

    Returns:
        IO
    """
    return open(fp, 'r')

typingのIOがTextIOを包括してくれるようでした。
Typingのドキュメントちゃんと読んでおけばもっと楽だったかもしれません。

詰まったところ

ちょっと戯れにストーリー仕立てにしてみます。

openの返り値わからない問題

わたし:
まあPythonがtyping出してるくらいやし、普通にOpen見たら型とか全部わかるやろ。
...(しばらくの間)
あれ?


python公式でとりあえず確認してみようと思い、見て見たらなんと、返り値の型の記載がないのです笑

まあ、厳密に言えば、下の方に

file を開き、対応する ファイルオブジェクト を返します。ファイルを開くことができなければ、OSError が送出されます。

とあるので、ファイルオブジェクトに飛ぶと

ファイルオブジェクトには実際には 3 種類あります: 生の バイナリーファイル、バッファされた バイナリーファイル、そして テキストファイル です。インターフェイスは io モジュールで定義されています。ファイルオブジェクトを作る標準的な方法は open() 関数を使うことです。

インターフェースはioで書かれているらしい。
バッファ付きでファイルロードとかその辺の話はJavaとかを思い出します。。。

ioから型をとってきたけど、Pycharmが許してくれない問題

わたし:
O...OK。わかったIOで見てみようじゃないか。
...
どれやねん...


ioモジュールを見ると、

I/O には主に3つの種類があります; テキスト I/O, バイナリ I/O, raw I/O です。

ほ、ほう。。。
じゃあ、openの方にはなんかかいてあったっけ?

open() 関数が返す file object の型はモードに依存します。 open() をファイルをテキストモード ('w', 'r', 'wt', 'rt', など) で開くのに使ったときは io.TextIOBase (特に io.TextIOWrapper) のサブクラスを返します。 ファイルをバッファリング付きのバイナリモードで開くのに使ったときは io.BufferedIOBase のサブクラスを返します。 実際のクラスは様々です。 読み込みバイナリモードでは io.BufferedReader を返します。 書き込みバイナリモードや追記バイナリモードでは io.BufferedWriter を返します。 読み書きモードでは io.BufferedRandom を返します。 バッファリングが無効なときはrawストリーム、すなわち io.RawIOBase のサブクラスである io.FileIO を返します。

えっ...(絶句)
これを書くのはめんどい。。。

じゃあ、とりあえず今開いてるファイルの型に合わせてtypingするか

>>> type(open(fp, 'r'))
<class '_io.TextIOWrapper'>

OK、じゃあ、io.TextIOWrapperをとってくればいいんやな。

from io import TextIOWrapper
from typing import Union
import os

def func(fp: Union[str, bytes, os.PathLike]) -> TextIOWrapper:
    """
    Args:
        fp (str, bytes or os.PathLike) : file path 

    Returns:
        opened object
    """
    return open(fp, 'r')

これを書いたら、pycharmが

TextIOWrapperが期待されてるけど、返ってくるのTextIOだよ

と教えてくれました笑
TextIOってのはioモジュールの中にはなく、あるのはTextIOWrapperかTextIOBase。。。
そんなこんなでtypingに戻ってみたら見つかるという流れでした

おわりに

pythonの公式ドキュメントはあんまりちゃんと読んでいないのが致命的でした笑
typingするとコードを受け取った人がすごく読みやすい(例えばOpenCVのC++部分とか読みやすい)し、APIとしても使いやすくなるので僕としては積極的に使っていきたいと思っています。

14
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
13