🐍

精読「独習Python」(標準ライブラリ 正規表現/ファイル操作/HTTP通信/数学演算など)

2024/12/30に公開



独習Python
「独習Python」は、初心者から中級者までを対象に、Pythonの基礎から応用までを体系的に学べる入門書で、豊富な例題と練習問題を通じて実践的なスキルを身につけられる一冊です。

関連記事

正規表現

あいまいな文字列パターンを表現するための記法[1]

正規表現の基本

  • 正規表現パターン:正規表現によって表された文字列パターンのこと
  • マッチ:与えられた正規表現パターンが、ある文字列の中に含まれること

文字列が正規表現パターンにマッチしたかを判定する

import re

# 正規表現パターンをコンパイル
pattern = re.compile(r"\d{3}-\d{3}-\d{4}")

# 検索対象の文字列
phone_number = "My phone number is 123-456-7890."

# re.search()を使って電話番号を検索
match = pattern.search(phone_number)

if match:
    print(f"Found a phone number: {match.group()}")
else:
    print("No phone number found.")
  • reモジュールcompile関数で正規表現パターンを準備する
  • そのとき、文字列リテラルでのエスケープシーケンスとの衝突を防ぐため、raw文字列(r'...')で表すのが安全)
  • compile関数は戻り値で正規表現オブジェクト(Pattern) を返すので、そのsearchメソッドで検索を実行する
  • groupメソッドでグループにマッチした文字列を取り出せる

マッチしたすべての文字列を取得する

re.findall() を使用すると、正規表現にマッチしたすべての文字列をリストとして取得できる

import re

# サンプルテキスト
text = "My phone numbers are 123-456-7890 and 987-654-3210."

# 正規表現パターン
pattern = r"\d{3}-\d{3}-\d{4}"

# findallを使ってマッチするすべての文字列をリストで取得
matches = re.findall(pattern, text)

# 結果を表示
print(matches) # 出力:['123-456-7890', '987-654-3210']

re.finditer() を使うと、正規表現にマッチした部分文字列をイテレータとして返す。各一致に対して、Match オブジェクトが返されるので、そのオブジェクトからマッチした文字列や位置などの情報を取得できる。

import re

# サンプルテキスト
text = "My phone numbers are 123-456-7890 and 987-654-3210."

# 正規表現パターン
pattern = r"\d{3}-\d{3}-\d{4}"

# finditerを使ってマッチするすべての一致をイテレータで取得
matches = re.finditer(pattern, text)

# 各一致に対して、文字列と位置を表示
for match in matches:
    print(f"Matched: {match.group()} at position {match.start()} to {match.end()}")

# Matched: 123-456-7890 at position 23 to 35
# Matched: 987-654-3210 at position 40 to 52

正規表現オプションでマッチング時の挙動を制御する

Patternクラスをインスタンス化する際に、第2引数に検索オプションを渡すこともできる

オプション 効果
re.IGNORECASE (re.I) 大文字と小文字を区別せずに検索 pattern = re.compile(r"hello world", re.IGNORECASE)
re.MULTILINE (re.M) ^$ が行の先頭と末尾にマッチ pattern = re.compile(r"^hello", re.MULTILINE)
re.DOTALL (re.S) . が改行文字にもマッチするようにする pattern = re.compile(r"Hello.*test", re.DOTALL)
re.VERBOSE (re.X) 正規表現のパターンを複数行で記述し、コメントを使える pattern = re.compile(r"""[a-zA-Z0-9._%+-]+ @ [a-zA-Z0-9.-]+ \. [a-zA-Z]{2,}""", re.VERBOSE)
  1. re.IGNORECASE または re.I大文字と小文字を区別せずに検索
import re

# サンプルテキスト
text = "Hello World! hello world!"

# 正規表現パターン(大文字と小文字を区別せずに検索)
pattern = re.compile(r"hello world", re.IGNORECASE)

# マッチを検索
matches = pattern.findall(text)

# 結果を表示
print(matches) # ['Hello World', 'hello world']
  1. re.MULTILINE または re.M複数行モードで検索(^ と $ が行の先頭と末尾にマッチ)
import re

# サンプルテキスト
text = """Hello World!
This is a test.
hello world!"""

# 正規表現パターン(行頭での一致を検索)
pattern = re.compile(r"^hello", re.MULTILINE)

# マッチを検索
matches = pattern.findall(text)

# 結果を表示
print(matches) # ['hello']
  1. re.DOTALL または re.S. が改行文字にもマッチするようにする
import re

# サンプルテキスト
text = """Hello World!
This is a test."""

# 正規表現パターン(任意の文字が改行を含んでもマッチ)
pattern = re.compile(r"Hello.*test", re.DOTALL)

# マッチを検索
matches = pattern.findall(text)

# 結果を表示
print(matches) # ['Hello World!\nThis is a test']
  1. re.VERBOSE または re.X:正規表現のパターンを複数行で記述し、コメントを挿入
import re

# サンプルテキスト
text = "My email is example@example.com"

# 正規表現パターン(VERBOSEモードで書く)
pattern = re.compile(r"""
    [a-zA-Z0-9._%+-]+     # ユーザー名部分
    @                     # @シンボル
    [a-zA-Z0-9.-]+         # ドメイン部分
    \.                     # ドット
    [a-zA-Z]{2,}           # ドメイン末尾部分
""", re.VERBOSE)

# マッチを検索
matches = pattern.findall(text)

# 結果を表示
print(matches) # ['example@example.com']

正規表現で文字列を置換する

re.sub()を使用して文字列の置換ができる(\gを使った例)

import re

# 元の文字列
text = "John is 25 years old."

# 名前と年齢をキャプチャするパターン
pattern = r"(\w+) is (\d+) years old."

# 名前と年齢を使って文字列を置換
# 置換後は「名前 is 年齢歳」→「年齢 years old, 名前」
result = re.sub(pattern, r"\g<2> years old, \g<1>", text)

print(result)  # 出力: 25 years old, John

正規表現で文字列を分割する

re.split()を使うと、指定した正規表現に基づいて文字列を分割できる

import re

# 元の文字列
text = "Hello   world   this  is  Python."

# 1つ以上の空白文字で分割
result = re.split(r'\s+', text)

print(result)  # 出力: ['Hello', 'world', 'this', 'is', 'Python.']

ファイル操作

例テキストファイルへの書き込み

# 'example.txt'というファイルを開き、書き込みモード('w')でファイルを開く
with open('example.txt', 'w') as file:
    # 'Hello, this is a test.'という文字列をファイルに書き込む
    file.write("Hello, this is a test.\n")  # \n は改行を意味
    # 'This is another line.'という文字列をファイルに書き込む
    file.write("This is another line.\n")  # 改行を追加
  • open('example.txt', 'w') でファイルを 上書きモード で開いている
  • write() メソッドで、指定した文字列をファイルに書き込む
  • with open() を使用することで、処理終了後にファイルが自動的に閉じられる

テキストファイルを読み込む

ファイルを1行ずつ読み込む

# ファイルを読み込みモード('r')で開く
with open('example.txt', 'r') as file:
    # ファイルの各行を1行ずつ読み込む
    for line in file:
        print(line.strip())  # .strip()で末尾の改行を取り除く

ファイルの全内容を一度に読み込む

# ファイルを読み込みモード('r')で開く
with open('example.txt', 'r') as file:
    content = file.read()  # ファイル全体の内容を一度に読み込む
    print(content)

ファイルを行ごとにリストとして読み込む

# ファイルを読み込みモード('r')で開く
with open('example.txt', 'r') as file:
    lines = file.readlines()  # ファイルの各行をリストとして読み込む
    print(lines)  # リストを表示

バイナリファイルの読み書き

読み書きを表すオープンモードにbを追加するだけ

# 書き込むバイナリデータを用意
binary_data = b"Example binary data.\nAnother line of data."

# ファイル名を指定
file_name = 'example.bin'

# バイナリデータを書き込む
with open(file_name, 'wb') as file:
    file.write(binary_data)
    print(f"Data written to {file_name}")

# バイナリデータを読み込む
with open(file_name, 'rb') as file:
    content = file.read()
    print(f"Data read from {file_name}:")
    print(content)

タブ区切り形式のテキストを読み書きする

import csv

# ファイル名を指定
file_name = 'example.tsv'

# 書き込むデータ(リストのリスト形式)
data_to_write = [
    ["Name", "Age", "City"],
    ["Alice", "30", "New York"],
    ["Bob", "25", "Los Angeles"],
    ["Charlie", "35", "Chicago"]
]

# タブ区切り形式のファイルにデータを書き込む
with open(file_name, 'w', newline='', encoding='utf-8') as file:
    writer = csv.writer(file, delimiter='\t')  # タブ区切りを指定
    writer.writerows(data_to_write)  # データ全体を一度に書き込む

# タブ区切り形式のファイルからデータを読み込む
with open(file_name, 'r', newline='', encoding='utf-8') as file:
    reader = csv.reader(file, delimiter='\t')  # タブ区切りを指定
    for row in reader:
        print(row)  # 読み込んだデータを1行ずつ表示

オブジェクトのシリアライズ

import pickle

# 保存するオブジェクト(例: 辞書)
data = {
    "name": "Alice",
    "age": 30,
    "skills": ["Python", "Data Analysis", "Machine Learning"]
}

# ファイル名
file_name = 'example.pkl'

# シリアライズしてファイルに書き込む
with open(file_name, 'wb') as file:
    pickle.dump(data, file)
    print(f"Serialized data written to {file_name}")

# デシリアライズしてファイルから読み込む
with open(file_name, 'rb') as file:
    loaded_data = pickle.load(file)
    print("Deserialized data:")
    print(loaded_data)

ファイルシステムの操作

フォルダー配下のファイル情報を取得する(1)

osモジュールlist関数を利用することで、指定されてフォルダー配下のサブフォルダー/ファイル情報をリストとして取得できる

import os

# 対象フォルダーのパス
folder_path = './example_folder'

# フォルダー内のエントリ(ファイルやフォルダー)を取得
entries = os.listdir(folder_path)

# 各エントリを確認
for entry in entries:
    entry_path = os.path.join(folder_path, entry)  # フルパスを生成
    if os.path.isfile(entry_path):  # ファイルの場合
        file_size = os.path.getsize(entry_path)  # ファイルサイズを取得
        print(f"File: {entry} - Size: {file_size} bytes")
    elif os.path.isdir(entry_path):  # フォルダーの場合
        print(f"Directory: {entry}")

フォルダー配下のファイル情報を取得する(2)

listdir関数がフォルダー/ファイルの名前だけを取得するのに対して、フォルダー/ファイルの情報をまとめて取得するscandir関数もある[2]

import os

# 指定したディレクトリ
directory = 'sample_directory'

# scandir()でディレクトリ内のエントリをリスト
with os.scandir(directory) as entries:
    for entry in entries:
        if entry.is_file():
            print(f"ファイル名: {entry.name}")
            print(f"絶対パス: {entry.path}")
            print(f"サイズ: {entry.stat().st_size} バイト")
            print(f"最終アクセス日時: {entry.stat().st_atime}")
            print("-" * 40)

フォルダー/ファイル情報を再帰的に取得する

walk関数を利用することで、サブフォルダーを再帰的に下って、フォルダー配下のすべてのサブフォルダー/ファイル情報を取得できる

import os

# 'sample_directory'を指定して、os.walk()で再帰的にファイル・ディレクトリを取得
for dirpath, dirnames, filenames in os.walk('sample_directory'):
    print(f"Directory: {dirpath}")
    for dirname in dirnames:
        print(f"  Subdirectory: {dirname}")
    for filename in filenames:
        print(f"  File: {filename}")

フォルダーを作成/リネーム/削除する

osモジュールの関数を利用することで、フォルダーを新規作成、リネーム、削除できる

import os

# フォルダーの作成
folder_name = 'my_folder'
os.mkdir(folder_name)  # 'my_folder' フォルダーを作成
print(f"フォルダー '{folder_name}' を作成しました。")

# フォルダー内にファイルを作成しておく(リネームと削除のため)
file_name = os.path.join(folder_name, 'sample.txt')
with open(file_name, 'w') as f:
    f.write("This is a sample file.")
print(f"ファイル '{file_name}' を作成しました。")

# フォルダーのリネーム
new_folder_name = 'my_renamed_folder'
os.rename(folder_name, new_folder_name)  # フォルダーの名前を変更
print(f"フォルダー名を '{folder_name}' から '{new_folder_name}' に変更しました。")

# フォルダーの削除
os.rmdir(new_folder_name)  # フォルダーとその中身を削除
print(f"フォルダー '{new_folder_name}' を削除しました。")

フォルダーを作成/リネーム/削除する(複数階層)

import os
import shutil

# フォルダーの作成(複数階層)
base_folder = 'parent_folder'
nested_folder = os.path.join(base_folder, 'subfolder1', 'subfolder2')

# まず親フォルダーを作成
os.makedirs(nested_folder)  # 指定した階層のフォルダーを一度に作成
print(f"フォルダー '{nested_folder}' を作成しました。")

# フォルダー内にファイルを作成(リネームと削除のため)
file_name = os.path.join(nested_folder, 'sample.txt')
with open(file_name, 'w') as f:
    f.write("This is a sample file.")
print(f"ファイル '{file_name}' を作成しました。")

# フォルダーのリネーム
new_nested_folder = os.path.join(base_folder, 'subfolder1', 'subfolder2_renamed')
os.rename(nested_folder, new_nested_folder)  # フォルダー名を変更
print(f"フォルダー名を '{nested_folder}' から '{new_nested_folder}' に変更しました。")

# フォルダーの削除(中身も削除)
shutil.rmtree(base_folder)  # base_folder とその中身を削除
print(f"フォルダー '{base_folder}' とその中身を削除しました。")

フォルダー/ファイルをコピーする

import shutil
import os

# コピー元のファイルとディレクトリのパス
source_file = 'source_folder/sample.txt'
source_folder = 'source_folder'
destination_file = 'destination_folder/sample.txt'
destination_folder = 'destination_folder'

# ファイルのコピー
if os.path.exists(source_file):
    shutil.copy(source_file, destination_file)
    print(f"ファイル '{source_file}' を '{destination_file}' にコピーしました。")
else:
    print(f"ファイル '{source_file}' は存在しません。")

# フォルダのコピー
if os.path.exists(source_folder):
    # コピー先のフォルダが既に存在する場合、削除して再作成
    if os.path.exists(destination_folder):
        shutil.rmtree(destination_folder)  # 既存のフォルダを削除
    shutil.copytree(source_folder, destination_folder)
    print(f"フォルダー '{source_folder}' を '{destination_folder}' にコピーしました。")
else:
    print(f"フォルダー '{source_folder}' は存在しません。")

HTTP経由でコンテンツを取得する

requestsモジュールの基本

import requests

response = requests.get('https://api.example.com/data')
print(response.status_code)  # HTTPステータスコード
print(response.text)         # レスポンスの本文(テキスト)
print(response.json())       # レスポンスのJSONデータ(辞書形式)

HTTP POSTによる通信

import requests

url = 'https://api.example.com/submit'  # サーバーのURL
data = {
    'username': 'user1',
    'password': 'mypassword'
}

# POSTリクエストを送信
response = requests.post(url, data=data)

# レスポンスの確認
print(response.status_code)  # ステータスコード(200は成功)
print(response.text)         # レスポンス本文

その他の機能

数学演算ーmathモジュール + 組み込み関数

import math

# 円の面積 (半径5の円)
radius = 5
area = math.pi * math.pow(radius, 2)
print(f"Area of the circle: {area}")  # 円の面積

# 角度をラジアンに変換して三角関数を使用
degree = 90
radian = math.radians(degree)  # 角度をラジアンに変換
print(f"sin({degree} degrees) = {math.sin(radian)}")

# 数値を丸める
value = 3.14159
print(f"Rounded value: {round(value, 2)}")  # 小数点第2位で丸める

# 2の累乗(2^3)
print(f"2 to the power of 3: {math.pow(2, 3)}")

乱数を生成するーrandomモジュール

import random

# 1から6までのサイコロを振る
dice_roll = random.randint(1, 6)
print(f"サイコロの目: {dice_roll}")

# 名前のリストからランダムに1人選ぶ
names = ['Alice', 'Bob', 'Charlie', 'David', 'Eve']
chosen_name = random.choice(names)
print(f"選ばれた名前: {chosen_name}")

# リストをシャッフル
items = ['apple', 'banana', 'cherry', 'date']
random.shuffle(items)
print(f"シャッフル後のリスト: {items}")

# 1から10までの範囲でランダムな浮動小数点数を生成
random_float = random.uniform(1.0, 10.0)
print(f"ランダムな浮動小数点数: {random_float}")

# 同じシードを使って乱数を再現可能に
random.seed(42)
repeated_roll = random.randint(1, 6)
print(f"シード42を使った再現可能なサイコロの目: {repeated_roll}")

データ型を変換/判定するーint/float関数など

# 数字の文字列を整数に変換
str_num = "123"
int_num = int(str_num)
print(f"整数に変換された値: {int_num} (型: {type(int_num)})")

# 小数の文字列を浮動小数点数に変換
str_float = "123.45"
float_num = float(str_float)
print(f"浮動小数点数に変換された値: {float_num} (型: {type(float_num)})")

# 整数を文字列に変換
int_value = 789
str_value = str(int_value)
print(f"文字列に変換された値: {str_value} (型: {type(str_value)})")

# 浮動小数点数を整数に変換(小数部分が切り捨てられます)
float_value = 45.67
int_converted = int(float_value)
print(f"浮動小数点数から整数に変換された値: {int_converted} (型: {type(int_converted)})")

# 型を判定する関数
print(f"int_valueが整数かどうか: {isinstance(int_value, int)}")
print(f"float_valueが浮動小数点数かどうか: {isinstance(float_value, float)}")
print(f"str_valueが文字列かどうか: {isinstance(str_value, str)}")

モジュール/クラスに含まれる要素を確認する

dir()関数を利用すれば、オブジェクトが持っている属性やメソッドの一覧を返すことができる

# 文字列型のオブジェクトに対してdir()を使う
s = "Hello, World!"
print(dir(s)) # ['__class__', '__delattr__', '__dict__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'upper', 'zfill']

参考

https://amzn.to/41RlbuT

脚注
  1. たとえば、[0-9]{3}-[0-9]{4}は郵便番号の形式(「数値3桁-数値4桁」)を簡潔に表す ↩︎

  2. listdir関数では「フォルダー/ファイルの数 x 取得したい情報の種類」だけファイルシステムへのアクセスが発生するから ↩︎

Discussion