Python 入門講座 第6回: 残そう未来のために。

今日の目標 >

ファイルを使うことで、プログラムの実行結果を記録に残せます。またその記録を再利用できます。

  • ファイル の操作
    • open/close/flush : file mode、 ファイルオブジェクト
    • read/write/readline/readlines/writelines
  • with
  • import

そのまえに、前回の質問について

前回2つの質問をお尋ねしました。 まずはこの質問について解説しておきます。

ここで質問です。(その1)

ここでクイズです。

a=100
b=100

とした時、

print( a == b, a is b, id(a) == id(b))

はどんな結果になるでしょう? 結果を予想してから実行してみましょう。

次のプログラムをみてみましょう。

予想とあっていましたか?

他の値でも試してみましょう。

Pythonでは256以下の正の整数は特別扱いされています。 これらの数値については、インタプリタの初期化時にその数値のオブジェクトが生成/登録されています。 (短い文字列についても同様の特別の処理が行われています。)

変数への値の代入(PythonとCの比較)

「変数へ値を代入する」、「変数の値を更新する」といった操作の意味はプログラム言語によって 若干の違いがあります。ここではPythonとC言語を例に、これらの操作を図解してみました。

 C の代入  Pythonの代入
Cの代入  Pythonの代入

C言語などの伝統的はプログラム言語では、変数名はデータを保持する実体(メモリ)につけられています。この関係は、プログラムの実行中に変わることはありません。

それに対して、Pythonなどのインタプリタ型言語などでは、変数名はデータの実体を参照する際に使われます。 変数名と参照される実体との関係はプログラムの実行に従って変わっていきます。

ここで質問です。(その2)

>

((r:=1) or (r:=r+2)) and (r:=r+4)

および

(r:=1) and (r:=r+2)) or (r:=r+4)

の結果はそれぞれ何になりますか?

試してみましょう。

式の評価の様子を一ステップ毎に見ていきましょう。

((r:=1) or (r:=r+2)) and (r:=r+4)

Pythonは式を左から見て行って、評価の必要な項から順次評価を進めます。 最初の代入式でrは 1 に設定され、代入式の値自体も 1 になります。

(1 or (r:=r+2)) and (r:=r+4)

ここで、(1 or (r:=r+2))の論理式としてはTrueであることが確定しますから、(r:=r+2)を計算する必要はありません。 従って、rは1のままです。

1 and (r:=1+4)

この式の値は、r:=1+4の値が必要です。この式の値は 1+4つまり5で、同時にrの値も5となります。

1 and 5 
5

ということで、((r:=1) or (r:=r+2)) and (r:=r+4)の値は 5 となります。

次に第二の式をみてみましょう。

((r:=1) and (r:=r+2)) or (r:=r+4)

( 1 and (r:=1+2)) or (r:=r+4)

( 1 and 3) or (r:=3+4)

3 or (r:=3+4) # 第2項(r:=3+4)は評価の必要なし,rは3のまま

3

と評価がすすみ、((r:=1) and (r:=r+2)) or (r:=r+4)の値は3となりました。

or演算子の右側の項は、評価されない(実行されない)可能性があるということです。

練習として、

print(f"{((r:=0) or (r:=r+2)) and (r:=r+4) = :}")
print(f"{((r:=0) and (r:=r+2)) or (r:=r+4) = :}")

を考えてみましょう。

答えは、....

となりました。

今日の目標

Pythonでのファイルの取り扱いの基礎を学びます。 

ファイル内容の表示

シェルコマンド cat を使えば、ファイルの中身を端末に印刷することができます。

pythonによる  "cat" プログラム

このcatと同じように、ファイルの中身を端末に印刷するプログラムを作ってみます。

早速実行してみましょう。

catコマンドの様にファイルの中身が印刷されました。 プログラムの中身を順番にみて行きましょう。

ファイルを開く : open(fn,mode="rt")

プログラム中でファイルを使うに、まずファイルを開きます。 pythonプログラム中で、ファイルを開くにはopen()関数を使います。 open()関数には、開くファイルのファイル名と、ファイルの使い方を示すmodeを与えます。

fin=open("sample.txt",mode="rt")

は、"sample.txt"という名前のファイルを, 読み出し用(r)のテキストファイル(t)として開きます。 開いたファイルはopenが返すオブジェクト fin を通じて操作(.read/.writeなどがある)をします。

open関数のモード

文字 意味
'r' 読み込み用に開く (デフォルト)
'w' 書き込み用に開き、まずファイルを切り詰める
'a' 書き込み用に開き、ファイルが存在する場合は末尾に追記する
'x' 排他的に生成して開く、ファイルが存在する場合は失敗する
'b' バイナリモード
't' テキストモード (デフォルト)
'+' ファイル内容の更新 (読み書き)

w+r+の違いは オープンした時に内容が切り詰められる(w+)か内容に追加(r+)かの違いがある。

Notes:: 別のプログラムによって、ファイルを書き換えられないようにロックするには、

  1. os.open関数を使う。
  2. fcntl.lockfあるいは fcntl.flock関数 を使う。

といった方法があります。

モード指定(r/a/w)による違い

ファイル オープン時の モード指定 (r/a/w/x)によるopen直後の読み書き位置とファイル内容の違いを図で示します。

ファイルを閉じる .close()

ファイルを使い終わったら、ファイルを閉じる.close()ことが 必要 です。 さもないと、せっかく書き込んだデータが失われるかもしれません。

with文を使って,自動的に、ファイルを閉じる。: with open(fn,mode="rt") as fin:

ファイルを使い終わった時に、ファイルが確実に閉じられるように次の構文(with文)を使います。

with open(fn,mode="r") as fin:
    ... # finを通じてファイルの操作(read/writeなど)を行う。
    ...

... # ここに到達すると ファイル fin は閉じられている。

Note:

.close() メソッドを使って、明示的にファイルを閉じることもできますが、 閉じ忘れを避けるために、 with文の使用をお勧めします。

fin=open("sample.txt",mode="rt")
...
fin.close()

プログラムが正常に終了した場合には、計算機システムが適切に終了処理を行います。

停電を含む異常な状況で終了した場合には、終了処理(close処理)が適切に行われない場合があります。 プログラム中では、ファイルを開いておく必要がなくなれば、閉じておくことが推奨されます。 with文はその振る舞いをより簡潔にしてくれるプログラム言語の機能です。

with文はファイルだけでは、コンテキストマネージャと呼ばれる機能を持つオブジェクトに対して、利用することができます。

ファイルの中身を順番に読む

ファイルの中身を一行毎に読み込んで、処理する には、for文を次の形で使います。

for line in fin:
            print(line,end="")

行の終わりは、\n, \r\n \r のいずれでも対応します(Universal newlines). 読み込んだ行末のEOLは"\n"となっています。 (newline 引数を指定することで、変更可能です。)

Note:

ファイルオブジェクトの .readlines()メソッドや、.readline()メソッド を使ったほぼ等価なプログラムも可能です。

for line in fin.readlines():
   ...

あるいは

while (line:=fin.readline()):
   ...

しかしながら、大きなファイルを読み込む場合には、最初の方法が読みやすさ/メモリ効率/実行時間 の面から有利です。

Notes::

newlineオプション ファイルをオープンする際にnewlineオプションを指定することで、EOL文字の取り扱いが変わります。

  • 入力:
    • newline=None (default) : ユニバーサル改行モードが有効になります。入力中の行は '\n', '\r', または '\r\n' で終わり、呼び出し元に返される前に '\n' に変換されます。
    • newline='' (空文字列) : ユニバーサル改行モードは有効になりますが、行末は変換されずに呼び出し元に返されます。
    • newline= '\n', '\r', '\r\n'のいずれかの場合:入力行は与えられた文字列でのみ終わり、行末は変換されずに呼び出し元に返されます。
  • 出力:
    • newline=None (default) :出力文字列中の全ての '\n' 文字はシステムのデフォルトの行セパレータ os.linesep に変換されます
    • newline='' (空文字列) または '\n' : 行末文字は変換されません
    • newline = '\r' または '\r\n' : 出力文字列中の全ての '\n' 文字は与えられた文字列に変換されます。

別の方法(1):一行ずつ読む。

歴史的な事情もあり、推奨の方法以外にも行毎の処理を行う方法がpythonには存在します。

ここで、それらの方法も紹介しますが、 明確な理由(例えば、古いバージョンのPythonしか使えないとか)がない限り、 推奨の方法を使うのが良いでしょう。

別の方法(2):まとめて読む。

.readlines().read()メソッドを使うと、ファイル中の全てのデータを一気に読み出すことができます。

ファイルのサイズが非常に大きい時には、大量のメモリが必要となります。また、全てのデータを 読み込むまで、プログラムはそこで待たされることになります。

ということで、.readlines().read()は特別な事情がない限り、使わない方が良いでしょう。

Notes::

引数付きのread(n)はバイナリモードでファイルを取り扱う際には、頻繁に使われるでしょう。

テキストファイルとバイナリファイル

ファイルの本体は全てバイトデータの並びです。その意味では、全てのファイルはバイナリファイルとして読み込めます。

テキスト、画像、音声、動画、その他さまざまなデータをそれらのデータ形式に合った形で読み込むことが必要です。

open()関数では、ファイルの内容がテキストであることがわかっていれば(t) エンコーディング(encoding)を指定することで、テキスト(ユニコード)として読むために必要な処理をプログラム処理系が行なってくれます。

Note: 画像/音声/動画などが収められたファイルでは、それらのデータを適切に取り扱うための関数が用意されていることが普通です。

未知のデータファイルあるいは独自に定義されたでーた形式を取り扱うためには、バイナリファイルとして取り扱う必要がありますが、 一般ユーザーがそのような場面に遭遇する機会は無くなってきています。

ファイルへの出力

次に、以前に作成した数表印刷プログラムを元に、数表をファイルに書き出すプログラムを作ってみます。

print文を使った、数表印刷プログラム

def 数表の印刷():
    print ("フィボナッチ数の数表".center(66,"_"))
    print ( "   {0}".format(
        ",".join(("    {0:02d}".format(c) for c in range(10))))
      )
    for r in range(0,30,10):
        print("{0:02d}:{1}".format(
            r,
            ",".join(("{0:6d}".format(fib(c)) for c in range(r,r+10)))
        ))

端末に文字列を書き出す関数print()をファイルへの書き出しの関数(メソッド).write()に書き換えればよさそうです。

試してみましょう。

数値は表示されましたが、改行されずに一行に出力されてしまいました。

print()とは異なり、ファイルの.write()改行を追加してくれません。 明示的に改行コード "\n" をファイルに書き込む必要があります。

各行を書き出す際に "\n" を追加するように書き換えてみます。

試してみましょう

import

数表保存のプログラムで使ったfib()関数は別ファイル"fibonacci.py"で定義されています。(次ページを参照)

この様に、作成済みのプログラムをimport文を使って、再利用することができます。 importされるブログラムを**モジュール(module)と呼びます。モジュールの利用法の代表的な方法には次の様なものがあります。

インポート法  利用法 意味
from fibonacci import fib print (fib(10)) モジュール fibonacci から fibをインポート
from fibonacci import * print (fib(10)) モジュール fibonacci から 全てをインポート
import fibonacci print( fibonacci.fib(10) モジュールfibonacci をインポート

Note:

モジュール中の関数を利用するためには、

from  fibonacci import fib

としてモジュール中の特定の関数をインポートする方法と、

from  fibonacci import *

によって、fibnacciモジュール中の全ての関数や変数をインポートする方法があります。

いずれの場合にも、fibonacci.pyで定義した関数fib()

print ( fib(10))

という様に利用することができます。

また、

import fibonacci

をプログラムの初めに書くことで、fibonacci.pyで定義した関数fib()をそのプログラム中で使うことができます。

print( fibonacci.fib(10) )

モジュール名が長い、あるいは深い階層をもったモジュールに別名をつけてインポートすることもできます。

import fibonacci as fibm
import matplotlib.pyplot as pyplot
print( fibm.fib(10))
pyplot.plot([fibm.fib(i) for i in range(100)])

fibonacchi.pyの中身

fibonacchi.pyの中身は次のようになっています。

#!python3
def fib(n:int)->int:
    """
    returns the n-th fibonacci number.
    """
    if n in (0, 1):
        return 1
    a=b=1 
    while (n := n-1 ) > 0:
        a, b = a+b, a # a: f_{k}, b:f_{k-1}
    return a

if __name__ == "__main__":
    for i in range(10):
        print(f"{i=:}, {fib(i)=:})

if __name__ == "__main__":以下の部分はimportの際には実行されません。 このモジュールの単独動作確認の際に役立ちます。

Note: モジュールがみつからない!?

モジュールとして使いたいファイルは確かに存在するのに、Pythonでimportするとエラーになる。 といった時はPythonのサーチパスを確認しましょう。

pythonモジュールのサーチパスは、

import sys
print(sys.path)

を実行して確認しましょう。

名前の衝突を避けるために、

複数のモジュールを同時に使う場合、同じ名前の関数名が別モジュールにもある可能性があります。

そのような場合には、名前の付け替え(import-as)や階層的な名前を使うことで、名前の衝突を避けることができます。

名前の付け替え:

from  fibonacci import fib as FIB
print( FIB(10) )

として、 関数名FIBを使います。

階層的な名前:

import fibonacci 
print( fibonacci.fib(10)

のように、モジュールをimportした後に、モジュール名と関数名を組み合わせたfibonacci.fib()を関数名として使います。

モジュール名は ファイルシステムと同じく階層的な構造を持っていることがあります (例:.matplotlib.pyplot)。

import matplotlib.pyplot as pyplot
pyplot.plot([fib(i) for i in range(0,10)])

本日のまとめ

  1. ファイルの操作: open/.close/.readline/.write
  2. モジュール(import 文): import <module>/from <mdule> import <name>
  3. with 文 : with ... as ... :

この先にあるものは?

input/print

端末(キーボード)からの入力はsys.stdin.readlineを、端末ディスプレイへの出力はsys.stdout.writeを使えば実現可能です。

stdin/stdout/stderr

オープンしたファイルには、ファイル番号が付けられています。 例えば、つぎのようになります。

>>> open("sample.txt",mode="rt").fileno()
3

初めて開いたファイルにも関わらず、0でも1でもなく3になっています。

これは、pythonでは0,1,2のファイル番号はそれぞれ stdin, stdout, stderrと呼ばれる三つのファイルが標準入出力として予約されているためです。

>>> import sys
>>> sys.stdin.fileno(),sys.stdout.fileno(),sys.stderr.fileno(),
(0, 1, 2)

端末への入出力はこれらのファイルを使うことで実現できます。

jupyter/jupyterlabのノートブックではinputsys.stdoutの取り扱いが異なるため、 スクリプトとして実行した場合と結果が異なります。