Windowsで作ったファイルのパスをそのままPythonに貼りつけたら、なぜかファイルが見つからない。print()で表示してみると、パスの途中で改行が入っていた。そんな不思議な体験をしたことはありませんか?
コードを見直しても、どこにも改行なんて書いていないはずです。それなのに文字列が崩れてしまうので、初めて出会うと本当に戸惑いますよね。
この現象の正体は、エスケープシーケンスという仕組みです。この記事では、バックスラッシュ(\)が文字列の中で何をしているのかを、基本から落とし穴まで順番にほどいていきます。
サンプルの実行結果は、手元のPython 3.11から3.13で確認したものです。バージョンによって動きが違う部分は、そのつど書き添えます。
エスケープシーケンスとは何か¶
最初に、言葉の意味を押さえておきましょう。エスケープシーケンスとは、バックスラッシュとそれに続く文字の組み合わせで、特別な文字を表す書き方のことです。
たとえば改行やタブは、キーボードで打っても文字列の中にきれいに書けません。そこで\nと書けば改行、\tと書けばタブ、というように2文字で1つの特別な文字を表す約束になっています。
日本語のキーボードやフォントの設定によっては、バックスラッシュが円マーク(¥)で表示されることがあります。見た目は違っても中身は同じ文字なので、この記事の\は¥と読み替えてください。
実際に動かしてみるのがいちばんわかりやすいはずです。次のコードを手元で試してみてください。
print("1行目\n2行目")
# 1行目
# 2行目
print("名前\t点数")
# 名前 点数
print(len("\n"))
# 1
最後の行に注目してください。\nはソースコード上では2文字に見えますが、文字列としては改行1文字として数えられています。
つまりエスケープシーケンスは、Pythonがソースコードを読み込む段階で特別な1文字に置き換えられているのです。この置き換えが、あとで説明する落とし穴の原因にもなります。
よく使うエスケープシーケンスの一覧¶
Pythonで使えるエスケープシーケンスはいくつもありますが、日常的に使うものは限られています。よく見かけるものを表にまとめました。
| 書き方 | 意味 | 使う場面の例 |
|---|---|---|
\n |
改行 | 複数行のメッセージを作る |
\t |
タブ | タブ区切りのデータを作る |
\\ |
バックスラッシュそのもの | Windowsのパスを書く |
\' |
シングルクォート | '...'の中で'を使う |
\" |
ダブルクォート | "..."の中で"を使う |
\r |
復帰(行頭に戻る) | Windowsの改行コード\r\nを扱う |
\xhh |
16進数2桁で表す文字 | \x41はA |
\uxxxx |
Unicodeのコードポイント4桁 | \u3042はあ |
\N{名前} |
Unicodeの文字名 | \N{HIRAGANA LETTER A}はあ |
まずは上から5つ、改行とタブ、バックスラッシュ、2種類のクォートを覚えておけば十分です。残りはこういうものもあると知っておき、必要になったら表を見返すくらいで構いません。
クォートを文字列の中で使う¶
クォートのエスケープは、文字列を囲んでいる記号と同じ記号を中で使いたいときに出番があります。次の2行はどちらも同じ文字列になります。
print('It\'s fine')
print("It's fine")
# どちらも It's fine
実務では、エスケープするよりも囲む記号を変えるほうが読みやすくなります。中にシングルクォートがあるならダブルクォートで囲む、という使い分けを覚えておくと便利です。
Unicodeで文字を指定する¶
\uや\N{...}は、画面では区別しにくい文字を確実に書きたいときに役立ちます。たとえば見た目が普通の空白と同じ全角スペースは、\u3000と書いておけば読み手にも意図が伝わります。
print("\u3042") # あ
print("\N{HIRAGANA LETTER A}") # あ
print("A\u3000B") # A B(間に全角スペース)
文字列に紛れこんだ見えない文字を調べたいときは、repr()で表示するとエスケープシーケンスの形で見えるようになります。repr()の詳しい使い方は、次の記事でまとめています。
【関連記事】Pythonのstr()とrepr()の違いとは?__str__と__repr__の書き分けと、print()では見えない空白や型の違いを見抜くコツを初心者向けに解説
WindowsのパスC:\newが改行に化ける理由¶
ここで冒頭の謎を解いてみましょう。Windowsのフォルダのパスは、区切りにバックスラッシュを使います。
たとえばC:\new\table.csvというパスを、そのまま文字列に書いたとします。すると何が起きるでしょうか。
path = "C:\new\table.csv"
print(path)
# C:
# ew able.csv
print(len(path))
# 14
\nが改行に、\tがタブに置き換えられてしまいました。パスの中にたまたま\nや\tの組み合わせが含まれていたせいで、Pythonは特別な文字だと解釈したわけです。
厄介なのは、エラーが出ずにそのまま動いてしまう点です。ファイルを開こうとして初めて、見つからないというエラーになります。
私は10年ほどエンジニアとして開発に関わってきましたが、新人のころにこの現象で半日近く悩んだことがあります。同じコードがあるフォルダでは動き、別のフォルダでは動かないので、パスの文字列を疑うまでにずいぶん時間がかかりました。
フォルダ名がnやtで始まるときだけ壊れるので、再現条件がわかりにくいのです。この仕組みを知っているだけで、同じ失敗はかなり防げます。
raw文字列でバックスラッシュをそのまま扱う¶
この問題を解決する方法のひとつが、raw文字列です。文字列の前にrを付けて、r"..."と書きます。
rawは英語で生のという意味です。その名のとおり、raw文字列ではバックスラッシュがエスケープとして扱われず、書いたとおりの文字になります。
path = r"C:\new\table.csv"
print(path)
# C:\new\table.csv
print(len(path))
# 16
print(len(r"\n"))
# 2(バックスラッシュとnの2文字)
今度はパスが崩れずに表示されました。\nも改行ではなく、バックスラッシュとnの2文字として残っています。
ここで大事なのは、raw文字列が特別な型ではないという点です。作られるのは普通のstr型の文字列で、違うのはソースコードの読み方だけです。
ちなみに、バックスラッシュを2つ重ねて"C:\\new\\table.csv"と書いても同じ結果になります。ただ、パスが長くなると数え間違いやすいので、raw文字列のほうが読みやすいでしょう。
パスはpathlibに任せるのがおすすめ¶
実はPythonでは、Windowsのパスでもスラッシュ(/)で区切って書けます。さらに標準ライブラリのpathlibを使えば、OSの違いを気にせずにパスを組み立てられます。
from pathlib import Path
path = Path("C:/new") / "table.csv"
print(path)
# Windowsでは C:\new\table.csv と表示される
これならバックスラッシュをそもそも書かずに済むので、エスケープの心配がなくなります。pathlibの基本は、次の記事でくわしく紹介しています。
【関連記事】Pythonのpathlibとは?ファイルパス操作を初心者向けに解説
正規表現ではraw文字列が定番¶
raw文字列がもっとも活躍するのは、正規表現を書くときです。正規表現とは、文字列のパターンを記号で表して検索や置換をする仕組みのことでした。
正規表現では、\dが数字、\bが単語の区切りというように、バックスラッシュを多用します。ところが、これを普通の文字列で書くと、Pythonと正規表現の両方がバックスラッシュを解釈しようとして混乱が起きます。
次の例を見てください。catという単語だけを探したいのに、普通の文字列で書くと何も見つかりません。
import re
text = "cat concat"
print(re.findall("\bcat\b", text))
# [](見つからない)
print(re.findall(r"\bcat\b", text))
# ['cat']
普通の文字列では、\bがPythonによってバックスペースという制御文字に置き換えられてしまいます。正規表現のエンジンには単語の区切りという意味が届かないので、何もヒットしないのです。
raw文字列にすれば、\bはそのまま正規表現に渡ります。正規表現のパターンは常にrを付けて書く、と決めておくとこの種の事故は起きません。
Python公式ドキュメントのreモジュールの説明でも、パターンにはraw文字列を使うことが勧められています。正規表現そのものの書き方は、次の記事を参考にしてください。
【関連記事】正規表現とは?複雑な文字列検索や置換を一瞬で終わらせる
Python 3.12から出るSyntaxWarningに注意¶
では、\dのようにPythonが知らないエスケープシーケンスを普通の文字列に書いたらどうなるでしょうか。実は、これまでのPythonはバックスラッシュをそのまま残してくれていました。
そのため、"\d+"のようにrを付け忘れた正規表現でも、たまたま正しく動いてしまいます。ここが落とし穴の入り口です。
Python 3.12からは、このような認識できないエスケープシーケンスに対してSyntaxWarningという警告が表示されるようになりました。以前のバージョンでもDeprecationWarningは出ていましたが、こちらは通常の実行では表示されない種類の警告でした。
実際に、同じファイルを3つのバージョンで動かした結果を比べてみましょう。ファイルの中身は次の2行です。
import re
print(re.findall("\d+", "a12b345"))
これを各バージョンで実行すると、結果は次のようになりました。
| バージョン | 表示される警告 | 実行結果 |
|---|---|---|
| Python 3.11 | なし(通常の実行では見えない) | ['12', '345'] |
| Python 3.12 | SyntaxWarning: invalid escape sequence '\d' | ['12', '345'] |
| Python 3.13 | SyntaxWarning: invalid escape sequence '\d' | ['12', '345'] |
どのバージョンでも結果は正しく出ていますが、3.12以降では警告が目に入るようになりました。Pythonの更新履歴には、将来のバージョンではこれをSyntaxErrorにする予定だと書かれています。
つまり、今は動いているコードでも、いずれ動かなくなる可能性があるということです。警告が出たら、rを付けるかバックスラッシュを重ねて直しておきましょう。
Pythonのバージョンを上げた途端に、古いスクリプトから大量の警告が出てきた。そんな経験をした方もいるかもしれません。私の現場でも、3.12への移行時に古い集計スクリプトから同じ警告がいくつも見つかり、まとめて直したことがあります。
raw文字列の落とし穴¶
便利なraw文字列にも、知っておきたい注意点があります。ここでは初心者がつまずきやすい2つを紹介します。
1つ目は、raw文字列の最後をバックスラッシュ1つで終えられないことです。フォルダのパスの末尾に\を付けたくなる場面で、よく引っかかります。
folder = r"C:\data\"
# SyntaxError: unterminated string literal
raw文字列の中でも、バックスラッシュの直後のクォートは文字列の終わりとは見なされません。そのため閉じのクォートが見つからず、文字列が閉じていないというエラーになるのです。
この場合は、末尾の\を付けずに書くか、普通の文字列で\\と重ねて書くのが確実です。パスの組み立てなら、先ほど紹介したpathlibを使うのがいちばんすっきりします。
2つ目は、raw文字列の中では改行の\nも効かなくなることです。メッセージの一部だけをraw文字列にしたつもりが、改行まで文字どおり表示されてしまうことがあります。
print(r"パス: C:\new" + "\n次の行")
# パス: C:\new
# 次の行
このように、raw文字列にしたい部分と普通の文字列にしたい部分を分けてつなげれば、両方を同時に扱えます。raw文字列はバックスラッシュを多く含む部分だけに使う、と覚えておくとよいでしょう。
普通の文字列とraw文字列の使い分け¶
ここまでの内容を、場面ごとの使い分けとして整理しておきます。迷ったときはこの表を思い出してください。
| 場面 | おすすめの書き方 | 理由 |
|---|---|---|
| 改行やタブを入れたい | 普通の文字列 "...\n..." |
エスケープを効かせたい |
| 正規表現のパターン | raw文字列 r"..." |
\dや\bをそのまま渡せる |
| Windowsのパス | pathlibかスラッシュ区切り | バックスラッシュを書かずに済む |
| パスを文字列で書くしかない | raw文字列 r"C:\..." |
末尾の\だけは避ける |
| 見えない文字を確かめたい | repr()で表示 | エスケープの形で見える |
基本は普通の文字列を使い、バックスラッシュを書く必要があるときだけraw文字列を検討する。この順番で考えると判断に迷いません。
ファイルを読み書きするときのパスの扱いも、ここでの考え方がそのまま使えます。open()を使ったファイル操作の基本は、次の記事で紹介しています。
【関連記事】Pythonのファイル読み書きとは?open()とwith文の基本を初心者向けに解説
まとめ¶
最後に、この記事のポイントを振り返っておきましょう。
エスケープシーケンスは、バックスラッシュと文字の組み合わせで改行やタブなどの特別な文字を表す仕組みです。ソースコードを読み込む段階で1文字に置き換えられるので、Windowsのパスのように意図せず置き換わってしまうことがあります。
raw文字列r"..."を使えば、バックスラッシュを書いたとおりに残せます。正規表現のパターンは常にraw文字列で書き、パスはpathlibに任せるのが安全な習慣です。
Python 3.12からは、認識できないエスケープシーケンスにSyntaxWarningが出るようになりました。警告が出たら放置せずにrを付けて直すことを、ぜひ今日から意識してみてください。
ここまでお読みいただきありがとうございました。