プロジェクト

全般

プロフィール

Python言語の書き方

プログラム(ツール)構成

最初に、Pythonで記述するプログラムの構成概要をまとめます。

ファイル

Pythonはスクリプト言語なので、Pythonスクリプトを記述したファイルをそのまま指定して実行します。

python hello.py

UNIX系の環境で、シェルスクリプトのようにPythonスクリプトファイルを直接指定して実行したいときは、ファイルの先頭にShebangを記述します。

#!/usr/bin/env python3
  :

エントリーポイント

Python はスクリプト言語なので、ファイルに書かれた命令を上から順に実行していきます。
モジュールとしてimportされたときもファイルに書かれた命令が実行されます。

スクリプトとして実行されたとき、標準入力から読み込まれたとき、__name__ 変数には main が設定されます。
スクリプトファイルに次の記述を入れておくと、そのファイルがスクリプトとして実行されたときに実行されます。

def main():
    # logic

if __name__ == '__main__':
    main()

コマンドライン引数の取得

sysモジュールのargvリストに格納された引数を個々に文字列で取得する方法と、argparseモジュールのArgumentParserを使って引数の取得・解析、ヘルプの表示、説明の表示などを行う方法があります。

Python_コマンドライン引数ライブラリ

以下は標準ライブラリで用意されたsysモジュールとargparseモジュールのコマンドライン解析の概要です。

argv

コマンドラインに羅列された引数をそれぞれ文字列としてリストに格納する、きわめて簡潔なコマンドライン引数ライブラリです。

import sys

args = sys.argv
print(f'引数の個数は{len(args)}')
for arg in args:
    print(f'{arg=})
  • args[0]にはスクリプトファイル名
  • args[1:]で純粋に引数名リスト
argparse

引数の解析を行い、取得できるほか、必須引数、オプション引数、引数名と値、ヘルプの表示などを行う高度なコマンドライン引数ライブラリです。
https://docs.python.org/ja/3/library/argparse.html#module-argparse

import argparse

parser = argparse.ArgumentParser(description="ログを読み込み使用状況をファイルに生成するプログラム")  # ヘルプの説明文を添えてパーサー生成 
parser.add_argument('input')    # 1つ目の必須引数を定義(input は後で引数の値を参照するための識別子となる)
parser.add_argument('output')   # 2つ目の必須引数を定義(output は後で引数の値を参照するための識別子となる)
parser.add_argument('values', nargs='+')  # 3つ目以降複数引数(任意個数)を定義(valuesは後で引数の値を参照する識別子となる。nargsで個数を指定)
parser.add_argument('--line')  # 値を伴うオプション引数を定義(--line NNN と指定、line が後で引数の値を参照するための識別子となる)
parser.add_argument('-w', '--word', action='append')  # オプションを複数指定(配列)(-w alfa -w bravo と指定すると['alfa', 'bravo']とリストで取得)(短縮形の -w charlie と --word charlie との指定が可能)

args = parser.parse_args()      # コマンドライン引数の取り込み
print(f'{args.input=}')         # 1つ目の必須引数を参照
print(f'{args.output=}')        # 2つ目の必須引数を参照
print(f'{args.values=}')        # 3つ目以降の必須引数(複数個)を参照、リストで取得される
print(f'{args.lines=}')         # オプション引数の値を参照

必須引数を指定しないと、ヘルプを表示して終了します。

  • 任意個数の引数 nargs
    nargsに引数の個数を指定します。0個以上:'*', 1個以上:'+', 指定個数はその数を指定します。
    • オプション引数の任意個数指定の場合、action='append'とする方法あり
      -a alfa -a bravo -a charlie とすると、['alfa', 'bravo', 'charlie']とリストになる
  • オプション引数の指定を必須とする required
  • 引数の型を指定する type
    デフォルトは文字列。int, float, ascii, ord, open, argparse.FileType, pathlib.Path, 関数指定も可
    • openは引数で指定されたファイルパスのファイルオブジェクトを返す(open済み)
  • 引数の指定を規定の選択肢から選択する choices
  • 引数の指定がない時の値を指定する default
  • 引数をフラグとして使う action='store_true'

外部設定ファイル

プログラムの動作をカスタマイズ可能にする方法の一つとして、プログラム実行時に設定ファイルを読み込み、その設定に応じた振る舞いをするsettingsライブラリがあります。これは Python 3標準ライブラリです。Pythonプログラムと同じディレクトリに、settings.py の名前で設定ファイルを保管します。settings.pyに記述された変数を実行時に読み込み変数の値を使用します。

settingsライブラリ

パッケージ

少し複雑なプログラムになると、複数のファイル(=モジュール)から構成するようになります。
アプリケーションも1つのパッケージとして管理することができます。

init.py

project
  +-- myapp
  |     +-- __init__.py
  |     +-- app.py

最初は、空の__init__.pyを用意するだけで構いません。

Python 3.3以降で、__init__.py を置かないディレクトリは名前空間として扱われるようになりました。
ですが、__init__.pyを定義して通常のパッケージとして管理した方が良いでしょう。

init.py の応用例
  • パッケージ内のモジュールを簡潔な記述でimportできるよう定義を記述可能
  • パッケージ内グローバルな変数を定義可能
  • パッケージの初期化処理を記述可能
    • 設定ファイルを読み込みパッケージ内グローバル変数を定義
    • ログの初期設定

プログラムの終了

exit

sys.exit でスクリプトの実行を終了します。引数に指定した値をOSに戻すことができます。

  • sys.exit() リターンコード0でプロセス終了
  • sys.exit(1) リターンコード1でプロセス終了
  • sys.exit('Fatal Error: exit') 標準エラー出力にメッセージを表示し、リターンコード1で終了

構造

クラス

空のクラス

class Alfa:
    pass

passは、classが空であることを指示します。

クラスからオブジェクトを作成するには、an_alfa = Alfa() とクラス名に丸括弧を付与して呼び出します。

クラス属性

class Alfa:
    cell = 'H8'

クラス属性へのアクセスは、クラス名.属性名(Alfa.cell) でも、インスタンス名.属性名(an_alfa.cell)でも可能です。
クラス属性を変更すると、変更後にクラスから生成したインスタンスには変更が反映されます。
変更前にクラスから生成したインスタンスには変更が反映されます。

インスタンス属性

インスタンス属性は、class定義には明記されず、最初にインスタンス名.属性名でアクセスしたときに生成されます。
通常、クラスの初期化メソッド__init__() で定義します。

class Bravo:
    def __init__(self):
        self.x = 0
        self.y = 0
  • Pythonの属性は公開
  • インスタンス生成後に属性の追加が可能だが、その場合同じクラスの他のインスタンスには属性が追加されない(そのインスタンス固有)

初期化メソッド

class Alfa:
    def __init__(self):
        pass

インスタンス初期化の特殊なメソッド __init__() を定義します。クラスからインスタンス生成時に初期化するメソッドです。
第1引数はselfとします。

メソッド

  • デコレータのないメソッドはインスタンスメソッド
  • @classmethod デコレータがついたメソッドはクラスメソッド
  • @staticmethod デコレータがついたメソッドは静的メソッド
クラスメソッド
class Alfa:
    @classmethod
    def order(cls):
        pass

第1引数はクラスであり、慣用的に cls を使います。

静的メソッド
class Alfa:
    @staticmethod
    def count():
        pass

プロパティ

class Alfa:
    def __init__(self, in_name):
        self.__name = in_name
    @property
    def name(self):
        return self.__name
    @name.setter
    def name(self, in_name):
        self.__name = in_name
  • propertyデコレータ を付けたメソッドは、@a_alfa.name のように属性の参照であるかのように記述して呼び出す
  • name.setterデコレータを付けたメソッドは、@a_alfa.name = 'abc' のように属性への設定であるかのように記述して呼び出す
  • __ を接頭辞に付けた変数(属性)は外部からアクセスできない。

継承

class Bravo(Alfa):
    pass

クラスAlfaを継承したクラスBravoを定義します。

ミックスイン

データクラス

from datacalss import dataclass

@dataclass
class Charlie:
    name: str
    age: int = 0
  • charlie = Charlie('Angel', 16)
  • charlie = Charlie(name='Angel', age=16)
from dataclass import dataclass

@dataclass
class Weather:
    temperature: float
    humidity: float
    id: str = 'NA'

関数

関数の書式

defに続き関数名を書き、入力引数を丸括弧に囲んで書き、末尾にコロンを書きます。次のインデントされたコード行が関数の本体となります。

def my_function():
    pass
  • 引数が無しのときは、丸括弧の中が空
  • 戻り値型の指定は関数書式にはなく、本体でreturnを使って値を返す

引数

def my_func(alfa, bravo):
    print(f'{alfa=}, {bravo=})

引数はカッコの中に引数の変数名を列挙して関数を定義します。

デフォルト値

引数を伴う関数を呼ぶときは、必ず引数を指定するのですが、デフォルト値を引数に持たせると、関数の呼び出し時に引数の指定を省略することができます。

def my_func(alfa, bravo='2')

この場合呼び出し時に my_func(3) と指定すると、bravoにはデフォルト値の2が設定されます。

デフォルト値は省略可能なので、複数の引数をもつ関数を定義するときは、デフォルト値を持つ引数の後にはデフォルト値のない引数を定義することはできません。def my_func(alfa=1, brave): は定義ができません。

複数の引数にデフォルト値を指定した関数を呼び出すときに、任意の引数だけ値を指定して呼び出すときは、名前を付けて引数を指定します。
def my_func(alfa=1, bravo=3, charlie=5) と定義した関数で、bravoにだけ値を指定するときは、my_func(bravo=7) と指定します。

戻り値

複数の戻り値を返す
def multi_values():
    return 404, "not found" 

カンマ区切りの値は、タプルと扱うので、これは (404, "not found")のタプルを1つ返す関数となります。

  • タプルを受け取って各要素にアクセス
ret = multi_values()
print(f'{ret[0]=}, {ret[1]=}')
  • 分解して受け取る
code, message = multi_values()

モジュール

  • ファイルがモジュール(alfa.pyファイルならalfaがモジュール名)

インポート

  • import alfa は、alfa.pyファイルに定義される要素を、alfa.要素で呼び出せるようにする
  • from alfa import bravo は、alfaモジュールの中のbravo要素を、直接bravoで呼び出せるようにする
  • 別名でインポートすることができる
    • import alfa as a は、alfa.pyファイルに定義される要素を、a.要素で呼び出せるようにする
    • from alfa import bravo as phonetic_bravo は、alfa.pyファイルに定義されるbravo要素を、別名phonetic_bravoで呼び出せるようにする
  • モジュールを定義するファイルに書かれている実行文は、最初のインポート時に実行される
インポートの注意点

プログラムコードのファイル名やディレクトリ名が、importする何らかのモジュール名と同じ場合、循環参照エラーとなります。

パッケージ

モジュール(ファイル)が増えたときに、ディレクトリを階層化してモジュールを配置

指針

クラス、モジュール

  • 同じ振る舞いで異なる内部状態のインスタンスが複数あるときは、クラス
  • 何かを1つだけ用意するときはモジュール(シングルトンとして使える)
  • 複数の値をまとめて捌く(関数の引数に渡すなど)にはクラス
    • タプル、名前付きタプルで賄えるならそれがいい
    • データクラス(Python 3.7以降)
  • 単純な方法を使う
  • 既存の構造を使う。数値、文字列、タプル、リスト、集合、辞書、コレクションライブラリ

データ

Python のデータ型

基本データ型

名称
bool 真偽値 True, False
int 整数 正負の整数値、大きさの制限はほぼない
float 浮動小数点数 64bit浮動小数点数
complex 複素数
str 文字列
list リスト
tuple タプル
bytes バイト
bytearray バイト配列
set 集合
frozenset 不変集合
dict 辞書
  • 他のプログラミング言語にある配列(固定長で特定の型の連続を添字で指定し読み書き)はありません。リストは可変長で、異なる型を保持することができます。

空のデータ

空のリテラル
str ''
list []
tuple ()
dict {}
set set()

文字列

クォート

文字列の生成は、シングルクォートまたはダブルクォートで文字を囲みます。
'Alfa' "Bravo"
どちらのクォートを使用しても同じに扱われます。クォートが2種類あることで、クォート文字を含む文字列が生成できます。
'He said "I am a Hero."'

複数行文字列

複数行文字列はトリプルクォートを使います。

multiline = """Hello, Alfa.
How are you?
Bye
""" 

f-文字列リテラル

f'文字列' とfで始まる文字列は、文字列内で波括弧で囲まれた文字列を式として評価し、結果文字列に置換えます。

name = "Alfa" 
age = 20
print(f'{name} is {age} years old.')

これは、Alfa is 20 years old. と出力されます。

波括弧内で、式文字列の後に=を置くと、式=値と置換されます。

print(f'{name=}')

これは、name='Alfa'と出力されます。

3連引用符にf文字列リテラルを使用することもできます。

print(f'''
Usage {sys.argv[0]} [options]
    -l: long format
    -h: show usage'''

b-文字列リテラル

b'文字列' とbで始まる文字列は、bytesリテラルでバイト配列の文字列表現を記述します。文字列にはASCII文字またはASCIIコードのエスケープ表現のみが許容されます。

james = b'\x30\x30\x37'

jamesは、bytes型で3つの要素 0x30, 0x30, 0x37 を保持します。

正規表現

import re で正規表現モジュールをインポートします。

使い方
import re
  :
my_pattern = re.compile(r'^(\d+)\s+(\w+).*$')
  :
data = '1324   ALFA_OMEGA comments'
matched = my_pattern.match(data)
if matched:
    p1 = matched.group(1)
    p2 = matched.group(2)

正規表現文字列は、r文字列で指定すると、バックスラッシュが特別扱いされないので、正規表現のバックスラッシュを文字列中で2重にしなくていいのが便利です。

使い方(compileなし)
import re

data = '2024-01-20'
matched = re.match(r'\d{4}-\d{2}-\d{2}', data)
記法メモ
  • 非貪欲(non-greedy)なマッチ: *?, +?, ??

数値と文字列の変換

  • 文字列からint
    int('13579')
  • 文字列からfloat
    float('246.8')

文字列処理あれこれ

指定の文字列を含んでいるか
  • 含んでいる場合にTrue
    if "alfa" in text:
  • 含んでいない場合にTrue
    if "alfa" not in text:
文字列を指定の区切り文字で分割(split)
  • デフォルトの区切り文字(スペース、タブ、改行)で文字列を分割しリストを返却
    words = text.split()
  • 区切り文字をしてして分割
    words = text.split(',')
文字列を改行で分割(splitlines)

改行コードはOSによって異なるので、改行で分割する場合は、splitlinesを使うと便利です。

  • Windows: \r\n
  • Linux: \n

タプル

p1 = 3.1, 2.4
p2 = 1.2, 1.8

p1 == p2 # False
p1 > p2  # True
p1 < p2  # False

x1, y1 = p1  # x1=3.1 x2=2.4
print(f'{p1[0]=}, {p2[0]=}')  # p1[0]=3.1, p1[1]=2.4

名前付きタプル

from collections import named tuple

Point = namedtuple('Point', ('x', 'y'))
:
p1 = Point(3.1, 2.4)
print(f'{p1.x=},{p1.y=}')

辞書

キーと値の組み合わせを複数保持するデータ型です。

datum = { 'TIME': time, 'USER': user, 'VIRT': vert, 'RES': res }
print(f"{datum['TIME']} {datum['VIRT']} {datum['RES']}"}

要素へのアクセス

  • キーに値をセット。キーが存在すればその値を変更し、キーが存在しなければ新たなキーと値としてセットします。
    datum['TIME'] = datetime.time(2,4,6)
  • キーの値を取得(1)。キーが存在しないと例外 KeyError が発生します。
    t = datum['TIME']
  • キーの値を取得(2)。キーが存在しないとNoneが取得
    t = datum.get('TIME')
  • キーが存在することを確認してからキーの値を取得
    if 'TIME'  in datum:
        t = datum['TIME']
    
  • イテレート
    for k,v in datum.iteritems():
        print(k, v)
    
  • 全てのキーを取得
    datum.keys()
  • 全ての値を取得
    datum.values()
  • キーと値のペアをタプルのリストで取得
    datum.items()

値からキーを逆引き

リスト内包表記を使って、キーと値を取り出し値と一致した要素のキーを取得します。

  • 実装1)値に対応するキーを取得、同じ値が複数のキーに登録されているときは、キーのリストを取得
def keys_by_value(dic, value):
    return [k for k, v in dic.items() if v == value]
  • 実装2)値が重複しないことを前提に値に対応するキーを取得
def key_by_value(dic, value):
    keys = [k for k, v in dic.items() if v == value]
    if keys:
        return key[0]
    return None

リスト

順序が保証された値のリストを持つデータ型です。
my_list = [1, 3, 5, 7, 11, 13]

  • 要素数:len(my_list)
  • インデックスアクセス:my_list[3] -> 7
  • スライス:my_list[2:4] -> [5, 7, 11]
    • 最後の要素 my_list[-1:] -> [13](要素1つのリスト)、my_list[-1] -> 13 要素
  • max(my_list) -> 13
  • min(my_list) -> 1
  • my_list * 2 -> [2, 6, 10, 14, 22, 26]
  • 要素の実在:11 in my_list -> True
  • 要素を末尾に追加:my_list.append(17)
  • リストを末尾に追加:my_list.extend([17, 19]) -> [1, 3, 5, 7, 11, 13, 17, 19]
  • insert, remove, pop, sort, reverse, copy, ...

多次元リスト

my_multi_list = [[1,3], [5,7], [11,13,17]]

  • my_multi_list[1] -> [5,7]
  • my_multi_list[1][1] -> 7

入れ子のリストを平坦化

my_list = [[1,3,5], 7, [11, 13]] 不規則な入れ子リスト

  • 問題点) リストの要素にリストや非リストが混在する場合
    • itertools.chain やリスト内包表記では平坦化できません

次のように再起的に平坦化する関数を定義します。

def flattern(l):
    for el in l:
        if isinstanceof(el, collections.abc.Iterable) and not isinstanceof(el, (str, bytes)):
            yield from flatten(el)
        else:
            yield el
  • 出典)https://note.nkmk.me/python-list-flatten/

リスト内包表記

for/inによる反復処理の結果をリストに生成する記述方法です。

書式: [ 式 for <item> in <iterable> ]

  • 例1 range関数で出力したデータ群をリストにする
    [n for n in range(1, 7)] -> [1,2,3,4,5,6]
    [n -1 for n in range(1, 7)] -> [0,1,2,3,4,5]
  • 例2 range関数で出力したデータ群のうち奇数のデータをリストにする
    [n for n in range(1,7) if n % 2 == 1] -> [1,3,5]

リストの要素 k と k+1 との差分のリスト

例えば、[2, 3, 5, 7, 11, 13, 17] のリストに対して、要素と次の要素との差のリスト [1, 2, 2, 4, 2, 4]を生成したいとします。
まず、対象リストから、先頭を除くリストを生成します。[3, 5, 7, 11, 13, 17]
zip関数で、2つのリストの要素を先頭から順番に取り出します。

primes = [2, 3, 5, 7, 11, 13, 17]
diffs = [i-j for i,j in zip(primes, primes[1:])]

集合

順序のない重複排除した値の集合を保持するsetです。
my_set = {'Hello', 'Python', 'World'}

集合演算ができます。細部は次のWikiページに記載します。
Python_集合(set)

日時型

参考:Pythonで日時を扱う

datetime

標準のdatetimeモジュールがあります。

  • datetime.datetime 日付と時刻
  • datetime.date 日付
  • datetime.time 時刻
  • datetime.timedelta 時間(時刻差)
コンストラクタから生成
import datetime

dt1 = datetime.datetime.now()  # 今の日時をローカルタイムで取得
dt2 = datetime.datetime(2023, 1, 15, 12, 30, 0)  # 2023年1月15日12時30分のローカルタイムを生成
dt3 = datetime.datetime(2023, 1, 15, 3, 30, 0, tzinfo=datetime.timezone.utc)  # 2023年1月15日3時30分のUTC日時を生成
文字列から生成
import datetime

dt1 = datetime.datetime.strptime("2023-02-10 01:23:45", "%Y-%m-%d %H:%M:%S")
t1 = datetime.datetime.strptime("01:23:45", "%X").time()
  • strptime関数は、datetimeクラスおよびdateクラスのクラスメソッドで、文字列を指定したパターンで解析してdatetimeオブジェクトを生成します。
    残念ながら、timeクラスにはstrptimeメソッドが用意されていないので、datetimeで文字列をパースしてから、timeメソッドでtimeオブジェクトに変換します。

演算子

  • 四則演算子 + - * /
    • 剰余 %
    • 除算(切り捨て) //
    • 冪乗 **
  • 比較演算子
    • 等しい == is
    • 等しくない != is not
    • 小なり <
    • 大なり >
    • 以下 <=
    • 以上 >=
    • 含む in
    • 含まない not in
  • 論理演算子
    • 論理積 and
    • 論理和 or
    • 否定 not
  • ビット演算子
    • 反転 ~
    • AND &
    • OR |
    • XOR ^
    • 左シフト <<
    • 右シフト >>

制御

繰り返し制御

for

for ターゲット in オブジェクト:
    処理
else:
    処理

elseは、whlieを正常に抜けた時に実行されます。break時は実行されません。
使用例として、forの中で終了条件を満たさない場合にelse句に非正常系の処理を書くことができます。

典型的には、リストから要素を順次取り出しループ処理をします。

numbers = [1, 3, 5, 7, 11, 13]
for n in numbers:
    print(n)
  • range(0, 10, 2) のようにrange関数を使って開始値、終了値(含まない)、増分を指定してループさせることができます

インデックスを参照したいときは、

numbers = [1, 3, 5, 7, 11, 13]
for i, n in enumerate(numbers):
    print(f'({i=}, {n=}')

とリストをenumerate()で包み、インデックスとリストの要素を順次取り出します。

要素を列挙したくない時は、range関数などで範囲指定することができます。

for n in range(5):
    print(n)
  • range(5) は、0から5の整数を生成
  • range(3, 6) は、3から5までの整数を生成

while

while 条件式:
    処理
else:
    処理
  • break文でwhileループを抜ける(else句は実行されない)
  • continue文で次のループを開始する

分岐

if

if 条件式:
    処理
elif 条件式:
    処理
else:
    処理

elifは必要に応じて記述でき、複数記述可能です。
elseは必要に応じて記述できます。最後に1つ記述します。

パターンマッチ(match-case)

Python 3.10以降で導入された文法です。

match value:
    case 1:
        print('One')
    case 2:
        print('Twe')
    case _:
        print('Many')
  • caseには、ガード条件(case 3 if flag)でifを指定することができます。case x if x > 3のような指定も可能です。
  • caseには、複数の値(case 3 | 4 | 5:)を指定することができます。複数の条件は、この例のようにパイプ区切りの他、リストで指定することができます。

例外

エラーの通知方法

例外を使わないエラー通知

関数の戻り値で正常終了かエラー終了かを戻り値で通知します。

  • (あまり良くない方法)正常終了時は関数の値を返し、エラー終了時はNoneを返す
  • タプルで、正常・異常の識別と、正常時の関数の値を返す

エラーの握りつぶしが発生しやすいのがデメリットです。
関数を呼び出した側で、正常・異常のチェックをしないと、エラーに気づかず処理が進んで値(None)を参照するところでプログラムが異常発生で停止し、原因が分かりづらいといった状況が起きます。

例外を使うエラー通知

raise文で例外を発生させます。関数呼び出し側で try構文でエラーをハンドリングします。ハンドリングしない場合、ランタイムが例外で停止します。

例外の使い方

例外は、BaseExceptionから派生したクラスのインスタンスです。ユーザー定義例外は、Exceptionから派生します。

標準例外

組み込み例外のクラス階層を 表示

例外の補足

try:
    例外がスロー去れるかもしれない処理
except FileNotFoundError:
    ファイルがない時のエラー処理
except ValueError as err:
    値のエラー処理、errで発生した例外にアクセスする
except:
    上述の例外以外がスローされた時の処理
else:
    例外がスローされなかった時に行う処理
finally:
  例外の発生有無によらず最後に実行する処理

独自例外の定義

class MyApplicationException(Exception):
    pass

独自例外の定義は、Exception(またはその派生クラス)を派生します。

for data in list:
    if not data.isValid():
        raise MyApplicationException('data is not valid')

入出力

コンソールの入出力

print関数

引数に指定した文字列を標準出力に出力し改行します。

改行を抑制するには、print('Hello,', end='') とend引数に空文字列を指定します。デフォルトでend='\n'

文字の色付けをして表示

エスケープシーケンスで色指定が可能です。
print('\033[31m' + 'Hello, Red' + '\033[0m')

[31m の31が赤を指定します。他の値として次を指定可能です。
30:黒、31:赤、32:緑、33:黄、34:青、35:紫、36:水色、37:白

文字の背景色付けをして表示

T.B.D.

ファイルの入出力

詳細は、Python_ファイル入出力ライブラリページ参照。

ファイルのパス指定

OS固有のパス指定
  • Linux/MacOS
    '/home/foo/work/data.txt'
    '/Users/foo/Documents/data.txt'
  • Windows
    'C:\\Users\\foo\\Documents\\data.txt'
    r'C:\Users\foo\Documents\data.txt'
    Windowsはパスセパレータがバックスラッシュなので、文字列中でエスケープ文字となり、2重に重ねて指定するか、raw文字列で指定します。
OS非依存のパス指定

pathlib ライブラリ(Python 3.4以降)を使うと、文字列でパスセパレータを指定しないのでOS固有のパスセパレータ使用を回避でき、OSポータブルな記述が可能です。

from pathlib import Path

data_file = Path('Documents') / 'work' / 'data.txt'

print(data_file.name) # data.txt
print(data_file.suffix) # .txt
print(data_file.stem) # data

Pathは、openに渡すことができます(Python 3.6以降)。

ファイル・ディレクトリの取得

指定したディレクトリの直下にあるファイルおよびディレクトリを取得します。

  • os.listdir関数を使う方法(古い方法)
  • pathlibパッケージを使う方法(新しい方法)

おすすめはpathlibなので、こちらを調べます。

pathlibパッケージを使う
from pathlib import Path

p = Path('/mnt/data/alfa')
items = p.glob("*")    # Pathの直下にあるファイルおよびディレクトリをリスト化
print(items)

プリントされる例は、次のようにパスの種類とフルパスとなります。

[PosixPath('/mnt/data/alfa/readme.md'),
 PosixPath('/mnt/data/alfa/config.dat'),
 PosixPath('/mnt/data/alfa/old')]
ファイルのみを表示したい
from pathlib import Path

p = Path('/mnt/data/alfa')
items = [p for p in p.glob("*") if p.is_file()]    # Pathの直下にあるファイルをリスト化
print(items)

ここでは。リストの内包表記を使っています。イテラブルなオブジェクトに対して、条件式がTrueのもののみを変数に順次入れ、式で評価した値をリストに詰めていきます。

[ 式 for 変数 in イテラブル if 条件式 ]

p.glob("*") は、パス(ディレクトリ)の下にあるワイルドカードに合致するファイルまたはディレクトリを取得します。

ファイル名だけをprintしたいときは、
items = [p.name for p in p.glob("*") if p.is_file()]

ファイルの存在確認

  • ディレクトリまたはファイルの存在
    os.path.exists(r"C:\Users\foo\work")
  • ファイルの存在
    os.path.isfile(r"C:\Users\foo\work\input.txt")
  • ディレクトリの存在
    os.path.isdir(r"C:\Users\foo\work\outdir")

ファイルの読み込み(行単位のテキストファイル)

注)open関数でファイルを開いた時は、ファイルの処理が終わったらclose関数でファイルを閉じる必要があります。
with構文を使用すると最後にcloseが呼ばれます。

行単位に読み込み

open()関数でファイルをオープンし、イテレータで1行ずつ読み込みます。

with open('input.txt', 'r') as f:
    for line in f:
        print line
全ての行を読み込み
with open('input.txt', 'r') as f;
    lines = f.readlines()
for文でopen

for l in open('input.txt', 'r'): のようにファイルをopenしてすぐfor文でイテレートする書き方を見かけます。
with文と同様、for文を抜けると自動的にcloseされますが、リソースの自動的なクローズはwith文に統一した方が一貫性が得られます。

CSVファイル

import csv

reader = csv.reader(file('input.csv', 'r'))
for row in reader:
    print(row[0])
  • 読み込んだデータはすべて文字列となるので数値として扱うには、int(row1)などのように変換が必要

ロギング

標準の logging モジュールを使用してロギングを行います。
Logger、 ログレベル、ハンドラー、フォーマッターなどの機能が提供されています。

ロギング概要

Loggerには、デフォルトのルートロガーと、アプリケーションやライブラリで固有に設けるロガーがあります。
ロガーはレベルに対応したメソッド(debug, info, warning, error, critical)が提供しているので、これらのメソッドに文字列を渡してログ出力します。

デフォルトのロガーを用いる例を次に示します。

import logging

logging.debug('Hi DEBUG!')
logging.info('Hi INFO!')
logging.warning('Hi WARNING!')
logging.error('Hi ERROR!')
logging.critical('Hello CRITICAL!')

デフォルトでは、ルートロガーとハンドラーそれぞれWARNINGレベル以上のログレベルのメッセージのみ出力されます。ルートロガーの設定をいじることは可能ですが(logging.basicConfig)、前述の通りアプリケーションやライブラリで専用のロガーを作成してそれを設定します。

ロガーの作成

名前を付けてロガーを作成、ロガーに出力レベルと出力先を設定します。

import logging

logger = logging.getLogger(__name__)  # 明示的にロガー名を指定するか、__name__でモジュール名を設定
logger.setLevel(logging.INFO)  # ログの出力をINFOレベル以上にする
logger.addHandler(logging.StreamHandler())  # ログの出力先(Handler)を標準エラー出力に
  • addHandlerで標準エラー出力を指定しています。指定しない場合、ルートロガーのHandlerを継承して使用しますが、ルートロガーの設定を引きずってしまいます。
  • コンソールとファイルと両方にログを出したい場合は、StreamHandlerとFileHandlerを生成し、それぞれをロガーにaddHanlderします。
  • ログレベルのフィルターはロガーだけでなく、それぞれのHanlderにも設定可能です。
標準エラー出力にフォーマットを指定して出力する独自ロガーを作成
    import logging

    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    handler = logging.StreamHandler()
    formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s - %(message)s')
    handler.setFormatter(formatter)
    logger.addHandler(handler)

出力メッセージ例

2023-06-04 17:22:14,072 INFO main - Processing 1440 messages
  • フォーマッターの指定
    • %(asctime)s は、そのメッセージのログを生成した時刻
    • %(levelname)s は、そのメッセージのログレベル名
    • %(funcName)s は、そのログが要求された関数名
    • %(message)s は、ログのメッセージ文字列

クリップボードの入出力

TkInterを使ったクリップボード

Python標準搭載のTkInter ライブラリを使ってクリップボードとテキストの入出力を行います。
GUIを開かなくてもコマンドラインツールからクリップボードへのアクセスが可能です。

クリップボードの内容を取得
import tkinter

tk = tinter.Tk()
tk.state("iconic")  # 1)
try:                # 2)
    clip_text = tk.clipboard_get()
    lines = clip_text.splitlines()
except:
    lines = ''

for line in lines:
    print(line)
  • 1)tkの初期化でトップレベルウィンドウが生成・表示されるので邪魔な場合は最小化します
  • 2)クリップボードが空の時などで例外が発生することがあるので、clipboard_getはtry文とともに実行します。

スタイル

命名規約(PEP 8)

Pythonコードの標準ライブラリで採用されているスタイルガイド PEP8 があります。

PEP8 日本語訳

ファイル

  • ソースコードのファイルはUTF-8を使用する

スタイルに関する規約

インデント、行の長さ、行間の空行などに関する規約

  • 1レベルインデントはスペース4つ
  • インデントにはタブは使わずスペースを使う
  • 1行の長さは最大79文字まで
    docstringやコメントは72文字まで
  • トップレベルの関数やクラスは2行ずつ空ける
  • クラス内部ではメソッドは1行ずつ空ける

命名

識別子の種類 命名ルール
モジュール名 全て小文字の短い名前、アンダースコアを交えてよい
パッケージ名 全て小文字の短い名前、アンダースコアは使わない
クラス名 (パスカルケース)
関数名 小文字のみ、アンダースコアを交えてよい
変数名
メソッド名
インスタンス変数名
インスタンスメソッドのはじめの引数名 self
クラスメソッドのはじめの引数名 cls
定数名 大文字で単語をアンダースコアで区切る

Docstring(ドキュメント)

ソースコード中に、モジュールや関数の仕様や使い方を記述するDocstringの書き方をまとめます。

Pythonでは、主に次の3つのDocstringのスタイルがあります。

  • reStructuredText スタイル
    Python公式スタイルですが、記述行が多く、他のスタイルに比べると可読性に若干劣ります。
  • Google スタイル
    Googleのスタイルガイドです。
  • Numpy スタイル
    ライブラリ Numpyで採用されているスタイルです。

Docstringは、モジュール(ファイル)、関数、クラスについて記載します。3連のダブルクォートで文字列として記述します。

  • 1行で簡潔に書く場合
    """簡潔な1行説明""" 
    
  • 複数行で記述する場合
    """概要を1行目で説明
    
    概要の後に1行開けてから詳しい説明を複数行に渡って記載する。
    """ 
    

Google スタイルのDocstyle

モジュール(ファイル)のDocstring
"""1行でこのモジュール/プログラムの概要を記述.

1行の空行を入れて、このモジュール/プログラムの全体的な説明を記述。
補足として、公開するインタフェース、実行例を書いても良い。
""" 
クラスのDoctring
関数のDoctoring
def sum(a: int, b: int) -> int:
    """2つの整数値を加算する

    詳しい説明があれば、概要の後に空行を入れて説明を記載する。

    Args:
        a (int): 被加数の値
        b (int): 加数の値

    Returns:
        int: 加算結果

    Raises:
        XxError: 加算時にXxエラー
    """ 
  • 引数(Args:)、戻り値(Returns:)以外にも、使用例(Examples:)、例外(Raises:)、TODO:、などが記載可能

参考文献

書籍

  1. Introduction Python SECOND EDITION, Bill Lubanovic, O'REILLY, 2021
    邦訳「入門 Python 3 第2版、鈴木駿 監訳、オライリージャパン、2021」

インターネット

  1. Python 3.10 ドキュメント
    https://docs.python.org/ja/3.10/
  2. Python コードのスタイルガイド(PEP8)
    https://pep8-ja.readthedocs.io/ja/latest/


7日前に更新