操作
第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' # 実際にはこのように保持されている (\ が \\ に置換されている) - クォート前に r を付ける
- 文字列リテラルの接続
- 文字列リテラルをスペースでつなげると、接続された単一文字列扱い
>>> 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 の出現位置を返す
無い場合は -1split(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 中に
- フォーマットの指定方法
-
f-string でコロンを使ったフォーマット書式を記述することで、出力をフォーマットできる
書式 解説 :<30:>30:^30指定幅で左寄せ、右寄せ、中央寄せ :-<30:->30:-^30指定幅で左寄せ、右寄せ、中央寄せし、その際にスペースではなく指定文字 (左記例では -) を埋める:b:o:d:x:X2進数、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.'
- f-string は Python3.6 で導入、その前は
-
%演算子- C言語の printf 第1引数と同様の文字列を使う (代入される位置に
%s%dなどを書く) - 前記文字列リテラルの後に
%に続けて代入する値(式)を記述する (複数の場合はタプルを指定)>>> name = "smith" >>> num = 20 >>> "Hello, %s" % "everybody" 'Hello, everybody' >>> "%s has %d items." % (name, num) 'smith has 20 items.'
- C言語の printf 第1引数と同様の文字列を使う (代入される位置に
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
-
正規表現オブジェクトを作成して、そのメソッドでマッチングを行う方法もある
- 検索対象の文字列を指定せず、正規表現オブジェクトを
re.compile()によって作成する - 正規表現オブジェクトのメソッドで、検索対象の文字列を指定して結果を得る
メソッド名 解説 戻り値 search(string[, pos[, endpos]])指定した文字列がマッチするかを返す posendposはマッチ処理の対象となる位置を指定マッチオブジェクト、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: 正規化の形式指定 (NFCNFKCNFDNFKDのいずれか) -
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 - 全角と半角、数字と丸数字・下付き数字・上付き数字
- 正準等価ではない
- 互換等価である
-
NFCNFKCの「合成」 : 濁音などは「分解」でわかれるが、それを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件の履歴