プロジェクト

全般

プロフィール

操作

第6章 テキストの処理

6.1 一般的な文字列操作を行う str, string

  • 文字列リテラル

    • シングルクォート、ダブルクォート
      • 文字列定義は1行で行う (開始クォートと終了クォートの間に、ソースコードの改行を行うとエラー)
      • 表現される文字列に改行を入れる場合はエスケープシーケンス \n
      • シングルクォート、ダブルクォートに差は無い
        • 終了クォートが異なるので、文字列中に他方のクォートを使用したい場合等で使い分ける
        • (例) "I'm a champion" 'say "HELLO", please'
      >>> s = "aaa\nbbbb\nccccc"
      >>> print(s)
      aaa
      bbbb
      ccccc
      >>> s
      'aaa\nbbbb\nccccc'  # 実際にはこのように保持されている
      
    • 3つのシングルクォート、3つのダブルクォート
      • 文字列定義が複数行にわたっても良い (開始クォートと終了クォートの間にソースコードの改行を行うと、そこは文字列でも改行扱い)
      • エスケープシーケンス \n を使っても改行される
      >>> s2 = """AAA\nBBB
      ... BBB
      ... CCC\n\nDDD"""
      >>> print(s2)
      AAA
      BBB
      BBB
      CCC
      
      DDD
      >>> s2
      'AAA\nBBB\nBBB\nCCC\n\nDDD'  # 実際にはこのように保持されている
      
    • raw文字列
      • クォート前に r を付ける r"How are you ?\nI'm fine."
      • エスケープシーケンスが無視され、書いた通りに保持される
      >>> s3 = r"AAA\nBBB"
      >>> print(s3)
      AAA\nBBB
      >>> s3
      'AAA\\nBBB'  # 実際にはこのように保持されている (\ が \\ に置換されている)
      
    • 文字列リテラルの接続
      • 文字列リテラルをスペースでつなげると、接続された単一文字列扱い
        >>> s4 = "AAA" "bbb"    "CCC"
        >>> s4
        'AAAbbbCCC'
        
      • () で囲んで、文字列リテラルを複数行に分けて書くと、接続された単一文字列扱い
        • 注意 カンマを書くとタプル扱いとなってしまう
        >>> s5 = (
        ...     "[1]This is"
        ...     "[2]other"
        ...     "[3]lines string literal"
        ...     )
        >>> s5
        '[1]This is[2]other[3]lines string literal'
        
        >>> s6 = (
        ...     "string1",
        ...     "string2"
        ...     )
        >>> s6
        ('string1', 'string2')  # タプル
        
  • 文字列以外のオブジェクトを文字列へ変換

    • 関数 str() へオブジェクトを渡すことで文字列を取得する
    • この際に取得される文字列は、オブジェクトの特殊メソッド __str__() の値
  • 文字列のチェックメソッド (str オブジェクトのチェック用メソッド)

    メソッド名 解説
    isalnum() 数字と文字
    isalpha() 文字のみ
    isdecimal() 10進数字
    isdigit() 数字を表す文字
    isidentifier() 識別子として使用できる
    islower() 小文字
    isnumeric() 数を表す文字列 (漢数字なども含む)
    isprintable() 印字可能
    isspace() スペース、タブなどの空白文字
    istitle() 単語の先頭のみ大文字であとは小文字
    isupper() 大文字
  • 文字列の変換メソッド (str オブジェクトの変換メソッド)

    メソッド名 解説
    upper()
    lower()
    swapcase() 大文字を小文字に、小文字を大文字に
    capitalize() 先頭1文字を大文字に、それ以外を小文字に
    title() 単語毎に大文字1文字 + 小文字に
    replace(old, new[, count]) old を new へ置換
    (count 指定の場合は、先頭から指定数まで変換)
    strip([chars]) 先頭および末尾から、指定した文字列中の 文字 をすべて除去
    chars 指定なしの場合は空白文字が削除される
    lstrip([chars]) 先頭から (同上)
    rstrip([chars]) 末尾から (同上)
    zfill(width) 長さが width になるように、左に 0 を詰める
    removeprefix(prefix, /) 先頭から prefix で指定した 文字列 を除去
    removesuffix(suffix, /) 末尾から suffix (同上)
  • その他文字列メソッド

    メソッド名 解説
    find(sub[, start[, end]]) sub の出現位置を返す
    無い場合は -1
    split(sep=None, maxsplit=-1) sep で分割したリストを返す (デフォルトは空白文字で分割)
    join(iterable) 引数それぞれを文字列で連結した単一文字列を返す
    startswith(prefix[, start[, end]]) prefix を先頭に持つかを判定
    prefix はタプルで複数候補を指定可能
    endswith(suffix[, start[, end]]) suffix を末尾に持つかを判定
    suffix はタプルで複数候補を指定可能
    encode(encoding="utf-8", errors="strict") encoding で指定したエンコード形式に変換する
    errors は変換できない文字があった場合の対応方法 strict:例外発生、ignore:対応文字無視、replace:対応文字を ? に変換
  • 文字列定数

    定数名 解説
    string.ascii_lowercase 英小文字 "abcdefghijklmnopqrstuvwxyz" (以下同様)
    string.ascii_uppercase 英大文字
    string.ascii_letters 英小文字、英大文字
    string.digits 0-9
    string.hexdigit 0-9, a-f, A-F
    string.octdigit 0-7
    string.punctuation 記号
    string.whitespace 空白 (スペース、タブ、改行等)
    string.printable ascii_letter, digit, punctuation
  • in によるチェック

    • 部分文字列 in 文字列 により bool が返る
      >>> value = 'Python'
      >>> 'P' in value  # 文字列に'P'が含まれている
      True
      >>> 'yth' in value  # 文字列に'yth'が含まれている
      True
      >>> 'x' in value  # 文字列に'x'が含まれていない
      False
      >>> 'xyz' in value  # 文字列に'xyz'が含まれていない
      False
      >>> 'p' in value  # 文字列に'p'は含まれていない(大文字と小文字は区別される)
      False
      

6.2 フォーマットと文字列リテラル f-string

  • f-string の書き方
    • 文字列に f または F を前置する
    • f-string 内の {expression} は、expression を Python が式として処理した結果となる
      >>> name = "smith"
      >>> num = 20
      >>> f"{name} has {num} items."
      'smith has 20 items.'
      
      >>> a = 12
      >>> b = 3
      >>> f'a + b = {a + b}'
      'a + b = 15'
      >>> f'a / b = {a / b}'
      'a / b = 4.0'
      
  • = を付けた出力
    • f-string 中に {変数名=} を書くと、その部分は「変数名 = 値」と出力される
      >>> num = 20
      >>> name = "smith"
      >>> f"{name=}, {num=}"
      "name='smith', num=20"
      
  • フォーマットの指定方法
    • f-string でコロンを使ったフォーマット書式を記述することで、出力をフォーマットできる

      書式 解説
      :<30 :>30 :^30 指定幅で左寄せ、右寄せ、中央寄せ
      :-<30 :->30 :-^30 指定幅で左寄せ、右寄せ、中央寄せし、その際にスペースではなく指定文字 (左記例では -) を埋める
      :b :o :d :x :X 2進数、8進数、10進数、16進数(小文字)、16進数(大文字) に変換する
      :f 固定小数点の文字列へ変換
      :% 百分率表記へ変換
      :, 数値3桁ごとにカンマを挿入する
      :6.2f 表示桁数指定 (左記例では 6:全体桁数、2:小数点以下桁数)
      ※全体桁数は小数点を含む文字数で、右寄せスペース埋め
      :%Y-%m-%d :%H:%M:%S 日付型の書式
      >>> import math
      >>> value = 'left align'
      >>> f'|{value:<30}|'  # 文字列を左に寄せて、30文字になるようにスペースで埋める
      '|left align                    |'
      >>> value = 'right align'
      >>> f'|{value:>30}|'  # 文字列を右に寄せて、30文字になるようにスペースで埋める
      '|                   right align|'
      >>> value = 'center'
      >>> f'|{value:^30}|'  # 文字列を中央にそろえて、30文字になるようにスペースで埋める
      '|            center            |'
      >>> f'{value:-^30}'  # 文字列を中央にそろえて、30文字になるように「-」で埋める
      '------------center------------'
      >>> value = 1000
      >>> f'{value:b} {value:o} {value:d} {value:x} {value:X}'  # 2進数、8進数、10進数、16進数(小文字)、16進数(大文字)に変換
      '1111101000 1750 1000 3e8 3E8'
      >>> f'{math.pi} {math.pi:f}'  # 「:f」で固定小数点数の文字列に変換
      '3.141592653589793 3.141593'
      >>> value = 0.045
      >>> f'{value:%}'  # 百分率での表記に変換
      '4.500000%'
      >>> value = 10000000000000
      >>> f'{value:,}'  # 数値に3桁ごとにカンマを挿入
      '10,000,000,000,000'
      >>> f'{math.pi:>5.2f}'  # 小数点以下が2桁になるよう変換し、文字列を右に寄せて全体が5桁になるようスペースで埋める
      ' 3.14'
      >>> value = 0.045
      >>> f'{value:>8.2%}'  # 小数点以下が2桁の百分率になるよう変換し、文字列を右に寄せて全体が8桁になるようスペースで埋める
      '   4.50%'
      >>> from datetime import datetime
      >>> now = datetime.now()
      >>> f'Today is {now:%Y-%m-%d}'  # 年月日に変換
      'Today is 2021-06-06'
      >>> f'Current time is {now:%H:%M:%S}'  # 時分秒に変換
      'Current time is 23:01:21'
      
    • 小数フォーマット実験

      >>> f"{50.1:10.3f}"
      '    50.100'         # 右寄せ、スペース埋め
      >>> f"{50.1:2.1f}"
      '50.1'               # 必要桁数は出力される (全体2桁指定でも4桁になる)
      >>> f"{50.1:2.2f}"
      '50.10'              # 小数点以下桁数が優先されて、全体は必要桁数となる
      
  • f-string 導入前のフォーマット方法
    • f-string は Python3.6 で導入、その前は str.format() メソッドを使用していた
      # インデックスで指定
      >>> name = "smith"
      >>> num = 20
      >>> "{0} has {1} items.".format(name, num)
      'smith has 20 items.'
      >>> "{1} is {1}".format(num, name)
      'smith is smith'
      >>> "{1} has {3} items.".format(name, num)  # 引数の位置指定は 0, 1, ...
      Traceback (most recent call last):
        File "<python-input-24>", line 1, in <module>
          "{1} has {3} items.".format(name, num)
          ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
      IndexError: Replacement index 3 out of range for positional args tuple
      
      >>> name = "smith"
      >>> num = 20
      # 宣言順で指定
      >>> "{} has {} items.".format(name, num)
      'smith has 20 items.'
      
      # キーワードで指定
      >>> name = "smith"
      >>> num = 20
      >>> "{first} has {second} items.".format(first=name, second=num)
      'smith has 20 items.'
      
    • 書式指定も可能
      >>> name = "smith"
      >>> num = 20
      >>> "{0:=<10} has {1} items.".format(name, num)
      'smith===== has 20 items.'
      >>> "{} has {:5.2f} items.".format(name, num)
      'smith has 20.00 items.'
      >>> "{first:=^20} has {second} items.".format(first=name, second=num)
      '=======smith======== has 20 items.'
      
  • % 演算子
    • C言語の printf 第1引数と同様の文字列を使う (代入される位置に %s %d などを書く)
    • 前記文字列リテラルの後に % に続けて代入する値(式)を記述する (複数の場合はタプルを指定)
      >>> name = "smith"
      >>> num = 20
      >>> "Hello, %s" % "everybody"
      'Hello, everybody'
      >>> "%s has %d items." % (name, num)
      'smith has 20 items.'
      

6.3 正規表現を扱う re

正規表現を扱うには re モジュールを使う

  • 関数 search, match

    • search(pattern, string, flags=0)
      • pattern 正規表現文字列
      • string 正規表現にマッチするか確認する文字列
      • flags 正規表現コンパイル時の振る舞いを変更するフラグを指定
      • 戻り値はマッチオブジェクト (re.Match のインスタンス)
    • match(pattern, string, flags=0)
      • search() と同じだが、文文字の先頭にのみマッチ検査する
      • ※複数行文字列でも、先頭の一か所のみ
    >>> import re
    >>> re.search("m.", "Smismith")
    <re.Match object; span=(1, 3), match='mi'>  # ヒットすると Match オブジェクトが返る
    >>> re.match("m.", "Smismith")
    >>>  # match では先頭からのみ検索するので、ヒット無しで None が返る
    
  • re モジュールのフラグ定数

    • 正規表現コンパイル時に指定するフラグ
    • re.search() re.match() での flags 引数部分に指定するもの
      • (補足) re.search() re.match() では、コンパイルと同時に検索が実行されるが、コンパイルした正規表現オブジェクト (re.compile のインスタンス) については後述

        定数名 解説
        A, ASCII \w, \s などのマッチングで Unicode ではなく ASCII 文字列のみを使用する
        I, IGNORECASE 大文字小文字を区別せずマッチ
        M, MULTILINE 複数行テキスト指定の際 ^ $ が各行の先頭と末尾にマッチする
        S, DOTALL . が改行文字も含めてマッチする
        >>> import re
        >>> re.search("s..", "Smismith")
        <re.Match object; span=(3, 6), match='smi'>
        >>> re.search("s..", "Smismith", re.I)
        <re.Match object; span=(0, 3), match='Smi'>
        
  • 正規表現オブジェクト re.Pattern

    • 正規表現オブジェクトを作成して、そのメソッドでマッチングを行う方法もある

      1. 検索対象の文字列を指定せず、正規表現オブジェクトを re.compile() によって作成する
      2. 正規表現オブジェクトのメソッドで、検索対象の文字列を指定して結果を得る
      メソッド名 解説 戻り値
      search(string[, pos[, endpos]]) 指定した文字列がマッチするかを返す
      pos endpos はマッチ処理の対象となる位置を指定
      マッチオブジェクト、None
      match(string[, pos[, endpos]]) 指定文字列の先頭がマッチするかを返す
      search() と同様
      マッチオブジェクト、None
      fullmatch(string[, pos[, endpos]]) 指定文字列全体が正規表現にマッチするかを返す マッチオブジェクト、None
      split(string, maxsplit=0) 指定文字列を、正規表現にマッチした文字列で分割する
      maxsplit は分割の最大数
      文字列 list
      sub(repl, string, count=0) 指定文字列のなかで正規表現にマッチした文字列を repl に置き換える
      count は置換回数上限
      str
      findall(string[, pos[, endpos]]) 指定文字列中で正規表現にマッチした文字列すべてを返す 文字列 list
      finditer(string[, pos[, endpos]]) 指定文字列中で正規表現にマッチした「マッチオブジェクト」をイテレータで返す イテレーター (re.Match)
      • search() の例
        >>> import re
        >>> pattern = re.compile("s..")  # 正規表現 "s.." のオブジェクト生成
        >>> type(pattern)
        <class 're.Pattern'>  # 正規表現オブジェクトは re.Pattern 型
        >>> pattern
        re.compile('s..')
        >>> pattern_i = re.compile("s..", re.I)  # フラグ付きの 正規表現オブジェクト
        >>> pattern_i
        re.compile('s..', re.IGNORECASE)
        >>> target = "Smismith"
        >>> pattern.search(target)
        <re.Match object; span=(3, 6), match='smi'>  # re.search() と同じ動作
        >>> pattern_i.search(target)
        <re.Match object; span=(0, 3), match='Smi'>
        
      • 他メソッドの例
        >>> import re
        >>> import string
        >>> pat.fullmatch(string.ascii_lowercase)
        >>> pat.fullmatch(string.ascii_lowercase, 2, 14)
        <re.Match object; span=(2, 14), match='cdefghijklmn'>
        
        >>> import re
        >>> pat = re.compile(r"\s+")
        >>> pat.split("I am Smith. Nice to meet you.")
        ['I', 'am', 'Smith.', 'Nice', 'to', 'meet', 'you.']
        >>> pat.sub(" & ", "I am Smith.")
        'I & am & Smith.'
        
        >>> import re
        >>> pat = re.compile(r"\b\d{3}-\d{4}\b")  # 郵便番号の形式をイメージ
        >>> pat.findall("111-0000 22-0000 3333-0000 444-000 555-00000 666-0000 777-9999")
        ['111-0000', '666-0000', '777-9999']
        >>> ite = pat.finditer("111-0000 22-0000 3333-0000 444-000 555-00000 666-0000 777-9999")
        >>> for postnum in ite:
        ...     print(postnum)
        ...
        <re.Match object; span=(0, 8), match='111-0000'>  # 取得されているのはマッチオブジェクト
        <re.Match object; span=(45, 53), match='666-0000'>
        <re.Match object; span=(54, 62), match='777-9999'>
        
    • 同じ正規表現でマッチングを繰り返すなら、正規表現オブジェクトを生成して使うべき (ただし、re.search() 等でもキャッシュ機能はある)

  • マッチオブジェクト

    • re モジュールの関数やメソッドで返されるオブジェクト

    • 正規表現中に () で囲んだ部分は「サブグループ」となり、その部分文字列だけを取得できる

    • マッチオブジェクトからのサブグループ取得方法は以下の2種類

      • group() メソッド
      • [] でのインデックス指定
      >>> import re
      >>> pat = re.compile(r"(\d\d) ([^ ]+) (\d{4}/\d{2}/\d{2}) (\d{2}:\d{2}:\d{2}) (.+)")
      >>> pat.match("04 process-001 2025/06/27 12:36:59 START")
      <re.Match object; span=(0, 40), match='04 process-001 2025/06/27 12:36:59 START'>
      >>> mobj = pat.match("04 process-001 2025/06/27 12:36:59 START")
      >>> mobj[0]  # インデックス数値指定 0 は全体
      '04 process-001 2025/06/27 12:36:59 START'
      >>> mobj[1]  # インデックス数値指定 1 が最初の (...) 部分
      '04'
      >>> mobj[2]
      'process-001'
      >>> mobj.group(3)  # group() 数値指定
      '2025/06/27'
      >>> mobj.group(4)
      '12:36:59'
      >>> mobj.group(5)
      'START'
      >>> mobj.group(3,4,5)  # 複数指定でタプルを取得
      ('2025/06/27', '12:36:59', 'START')
      >>> mobj.group(6)  # (...) は 5 個なので、6 は範囲外エラー
      Traceback (most recent call last):
        File "<python-input-46>", line 1, in <module>
          mobj.group(6)
          ~~~~~~~~~~^^^
      IndexError: no such group
      
    • 正規表現に名前付きグループを指定することで、サブグループ取得時にグループ名を使用可能

      • 名前付きグループ (?P<name>...)
      >>> import re
      >>> pat = re.compile(r"(?P<num>\d\d) (?P<id>[^ ]+) (?P<date>\d{4}/\d{2}/\d{2}) (?P<time>\d{2}:\d{2}:\d{2}) (?P<message>\.+)")
      >>> mobj = pat.match("04 process-001 2025/06/27 12:36:59 START")
      >>> mobj[2]
      'process-001'
      >>> mobj["id"]
      'process-001'
      >>> mobj.group(2)
      'process-001'
      >>> mobj.group(3)
      '2025/06/27'
      >>> mobj.group("message")
      'START'
      
    • マッチオブジェクトのメソッド

      メソッド名 解説 戻り値
      groups(default=None) パターンにマッチしたサブグループの文字列をタプルで返す
      defalt はマッチ文字列が無い場合に返す値の指定
      tuple
      groupdict(default=None) groups() と同様だが、グループ名をキーとした辞書を返す
      グループ名を付けていない場合は空の辞書
      dict
      expand(template) 文字列 template で \1 \g<name> 形式でサブグループを指定すると、マッチした文字列に置換されて返される str
      >>> import re
      >>> pat = re.compile(r"(\d\d) ([^ ]+) (\d{4}/\d{2}/\d{2}) (\d{2}:\d{2}:\d{2}) (.+)")
      >>> pat_n = re.compile(r"(?P<num>\d\d) (?P<id>[^ ]+) (?P<date>\d{4}/\d{2}/\d{2}) (?P<time>\d{2}:\d{2}:\d{2}) (?P<messag\e>.+)")
      >>> mobj = pat.match("04 process-001 2025/06/27 12:36:59 START")
      >>> mobj_n = pat_n.match("04 process-001 2025/06/27 12:36:59 START")
      >>> mobj.groupdict()
      {}
      >>> mobj_n.groupdict()
      {'num': '04', 'id': 'process-001', 'date': '2025/06/27', 'time': '12:36:59', 'message': 'START'}
      >>> mobj.groups()
      ('04', 'process-001', '2025/06/27', '12:36:59', 'START')
      >>> mobj_n.groups()
      ('04', 'process-001', '2025/06/27', '12:36:59', 'START')
      >>> mobj.expand(r"log:  \3 \2 \5")
      'log:  2025/06/27 process-001 START'
      

6.4 Unicode データベースへアクセスする unicodedata

  • Unicode 「文字」と「文字の名前」を変換する
    • 関数 lookup(name) : 指定された名前に対応する文字を返す
      • 存在しない場合は KeyError を返す
    • 関数 name(chr[, default]) : 文字 chr に対応する名前を返す
      • 名前が定義されていない場合、default が指定されていればその値、そうでなければ ValueError を返す
      >>> import unicodedata
      >>> unicodedata.lookup('BEER MUG')
      '🍺'
      >>> unicodedata.name('')
      'HIRAGANA LETTER A'
      >>> unicodedata.name('')
      'HIRAGANA LETTER TA'
      >>> unicodedata.name('')
      'CJK UNIFIED IDEOGRAPH-77F3'
      
  • Unicode 文字列の正規化
    • 関数 normalize(form, unistr) で、正規化した文字列を取得できる
      • form : 正規化の形式指定 (NFC NFKC NFD NFKD のいずれか)
      • unistr : 正規化する文字列
      • 戻り値 : 正規化された文字列
      >>> import unicodedata
      >>> target = 'あかざだぱ①②ⅠⅡ¹²₁₂'
      >>> target
      'あかざだぱ①②ⅠⅡ¹²₁₂'
      >>> unicodedata.normalize('NFC', target)
      'あかざだぱ①②ⅠⅡ¹²₁₂'
      >>> unicodedata.normalize('NFD', target)
      'あかざだぱ①②ⅠⅡ¹²₁₂'
      >>> unicodedata.normalize('NFKC', target)
      'あかざだぱ12III1212'
      >>> unicodedata.normalize('NFKD', target)
      'あかざだぱ12III1212'
      
    • 正規化形式について (独自追加調査)
      正準等価性で分解 互換等価性で分解
      正準等価性で合成 NFC NFKC
      合成無し NFD NFKD
      • 全角と半角、数字と丸数字・下付き数字・上付き数字
        • 正準等価ではない
        • 互換等価である
      • NFC NFKC の「合成」 : 濁音などは「分解」でわかれるが、それを1文字として再合成する
      >>> import unicodedata
      >>> target = 'あかざだぱ①②ⅠⅡ¹²₁₂'
      
      # NFC: 正準分解、合成
      >>> list(unicodedata.normalize('NFC', target))
      ['', '', '', '', '', '', '', '', '', '¹', '²', '', '']
      
      # NFD: 正準分解、合成無し (list にすると、濁音が分かれている)
      >>> list(unicodedata.normalize('NFD', target))
      ['', '', '', '', '', '', '', '', '', '', '', '', '¹', '²', '', '']
      
      # NFKC: 互換分解(丸数字、上下付き数字が通常の数字に)、合成
      >>> list(unicodedata.normalize('NFKC', target))
      ['', '', '', '', '', '1', '2', 'I', 'I', 'I', '1', '2', '1', '2']
      
      # NFKC: 互換分解(丸数字、上下付き数字が通常の数字に)、合成無し (list にすると、濁音が分かれている)
      >>> list(unicodedata.normalize('NFKD', target))
      ['', '', '', '', '', '', '', '', '1', '2', 'I', 'I', 'I', '1', '2', '1', '2']
      
    • 日本語の正規化には NFKC がよく使用される
    • 検索などで、表記ゆれを吸収して検索するのに使用される

Tatsuya ISHIGAKI さんが6ヶ月前に更新 · 1件の履歴