Otsuhachi’s diary

プログラミングのことや覚書などその他いろいろ

当ブログの内容を使用したり参考にした場合に生じた問題の責任は負いかねます。

Python組み込み関数1 -print編-

特にネタもないのでよく使う型や関数、ライブラリなんかを自分なりにまとめてみる。
一応制約として公式ドキュメントのみ参照*1として、読む癖をつける訓練も兼ねる。

今回はHello Worldでも使用するprint関数について。
正直、キーワードなし引数任意個をsepで指定した字句で区切って出力できて、endで末尾の字句を指定できるくらいしか知らない。
ネタついでに勉強になることに期待。

読み込むドキュメントはこちら

構成

何気なく使っているprint関数は、詳細にはこんな感じらしい。

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

ドキュメントを読む限りでも基本的に上記解釈で問題なさそう。
ネタとしてあんまりなのでもう少し水増ししてみる

使ってみる

  1. 自作クラスPerson__str__()を定義して出力
  2. 自作クラスPersonをリストmemberに入れ、memberを出力

実行するコードは以下の通り。

class Person:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    def __str__(self) -> str:
        me = f'{self.name}、{self.age}歳です!'
        return me


me = Person('Otsuhachi', 28)
member = [me]
print(me)
print(member)
Otsuhachi、28歳です!
[<__main__.Person object at 0x0000023C5175FCA0>]

自作クラスを出力するときは__str__()が呼び出されているのがわかる。
一方で、リストの中の自作クラスは人が見てもよくわからない出力がされている。
つぎに以下のことを試す。

  1. Personを継承したPerson2__repr__()を定義して出力
  2. Person2をリストmemberに入れ、memberを出力

実行するコードは以下の通り((Personクラスは前のコードを再利用する))。

class Person2(Person):
    def __repr__(self) -> str:
        me = f'{self.name}({self.age})'
        return me


me = Person2('Otsuhachi', 28)
member = [me]
print(me)
print(member)
Otsuhachi、28歳です!
[Otsuhachi(28)]

コンテナオブジェクト内のクラスを出力するときは__repr__()が呼び出されているのがわかる。

キーワード引数file

出力先をfile引数で指定できるそうなので、そのあたりについていくつか試す。

Path

個人的にかなり使用頻度の高いライブラリ。
Windows, Linux, Mac問わずファイルなどのパスを扱いやすくしてくれる……らしいがWindows以外を意識したことがないのでそこはなんとも。

実行するためのコードは以下の通り。

from pathlib import Path

file = Path('sample_output.txt')
print('Hello', 'World', sep='\t', file=file)

Linterのあるコーディング環境だとすでにエラーの波線が出ている。
ダメ元で実行してみると……。

Traceback (most recent call last):
  File "~\sandbox.py", line 4, in <module>
    print('Hello', 'World', sep='\t', file=file)
AttributeError: 'WindowsPath' object has no attribute 'write'

ドキュメントに書いていた通り、writeがないということでAttributeErrorが発生した。

TextIOWrapper

型名よりもopen(file)したら勝手に生成されてるといった方が伝わりそうな型。
この型はwrite(str)できるので、無事出力される……ハズ。

コードはさっきのを少しいじって以下の通り。

from pathlib import Path

file = Path('sample_output.txt')
with open(file, 'w', encoding='utf-8') as f:
    print('Hello', 'World', sep='\t', file=f)

正常に出力されていれば、コードを実行したフォルダにsample_output.txtというファイルができ、タブ区切りでobjectsが出力されている。

Hello   World

きちんと出力することができた。

自作クラス

write(str)できる自作のクラスを定義してみる。
出力先にGUIでメッセージを出したりするのは面倒なので、標準出力に文字を追加するような処理にする。
コードは以下の通り。

class Person:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    def write(self, text: str):
        t = f'{self.name}({self.age}): '
        print(t + text)


me = Person('Otsu8', 28)
print('Hello', 'World', file=me)

個人的には期待を裏切られる結果になったので、結果を予測してから進んでみてほしい。

ちなみに予想はOtsu8(28): Hello Worldだった。















Otsu8(28): Hello
Otsu8(28):
Otsu8(28): World
Otsu8(28):

予想通りだっただろうか?
流れ的には'{sep}'.join(objects)+endのように出力文字列を整形してからme.writeに渡すと思っていたが、実際には以下の順にme.writeに渡されていた模様。

  1. objects[1]((この場合はHello))
  2. sep((この場合は))
  3. objects[2]((この場合はWorld))
  4. end((この場合は\t))

なぜか?
冷静に考えてみれば、最終出力内容がメモリに収まりきるかどうかなど、予測できないからであると思われる。
基本1, 2文くらいしか同時に出力しないので失念していた。*2

自作クラスで試して得た学び

今までファイルに複数行の出力をしたいとき、大体以下の2通りを使っていた。

  1. lines'\n'.join(lines)TextIOWrapper.writeに渡す
  2. linesfor文を使って2行目以降に改行を挿入しながらTextIOWrapper.writeに渡す

上記方法にそこまで大きな不満があったわけではないが、以下のように書くことでスマートに記述できそうである。

print(*lines, sep='\n', end='', file=<TextIOWrapper>)

from pathlib import Path

lines = ['Hello', 'World', 'Pythonのprint文', 'おさらいしてよかった!']
file = Path('sample_output.txt')
with open(file, 'w', encoding='utf-8') as f:
    # 1.の方法
    # これでも一見良さそうだが、メモリ不足が考慮されていない
    # f.write('\n'.join(lines))
    
    # 2.の方法
    # メモリ不足は心配なさそうだが記述が多い
    # for i, line in enumerate(lines):
    #     if i:
    #         line = '\n' + line
    #     f.write(line)
    
    # 今回の学びで得た方法
    print(*lines, sep='\n', end='', file=f)

出力結果はどの方法でも以下の通り。
ファイル末尾に空行がほしい場合も引数endを指定しないだけでよいのがGood

Hello
World
Pythonのprint文
おさらいしてよかった!

締め

常識だったのかもしれないが、今回の発見は大きな収穫だった。
いつか同じくドキュメントを読み込まない誰かが見て、驚いてくれればうれしい。

flushについてはバッファ化などよくわかってない部分の理解も必要そうなので、今回は保留とする。
ドキュメントを読まず、なんとなくで使用している他の機能についてもこのブログを通して発見していけたらと思う。

2時間位掛けての本記事……続けたいが気力的に続くかどうか。
今回のprint関数が思いのほかアタリだったので、次回以降のネタに満足できるかどうか……。
頑張らない範囲で頑張ろうと思う。

*1:わかりやすく解説している他サイトを見ない

*2:言い訳