LoginSignup
219
179

Python で文頭に記載する文字コードの「アレ」(なんちゃら UTF-8 みたいなやつ)の名称と仕様

Last updated at Posted at 2018-10-22

shebang(シバン) ではない。Python に限ったものではないので、それはわかる。

知りたいのは Python の教科書などで「ソースの文頭に書け」と言われる、冒頭に記述する utf-8 とかの文字コードのアレ

これ↓(この名称と書き方を忘れる。てか知らない)
# -*- coding: <encoding name> -*-

#使用例: # -*- coding: utf_8 -*-
たまにこれ↓(-*-はいずこ?)
# coding: <encoding name>

#使用例: # coding: utf_8
たまにアレがないことも...
#

python 文頭に記載する アレ」とか「python 文頭 記述 文字コード アレ」の Qiita 記事をググっても出てこなかったので、自分のググラビリティ(備忘録)として。

TL; DR (今北産業)

  1. アレは英語で Magic comment と言います。
  2. Python 3 の場合、ソースが UTF-8 の時は記載は不要(むしろ非推奨)です。
  3. Python 3 で使えるコーデック文字コードのエンコード一覧はこちらになります。

TS; DR Python3 ソースコードの文字コード指定を完全に理解するコマケーこと

取りまとめ

  • 英語で Magic comment と言います

    Magic comment の日本語表記について

    英語の文献(PEP-263)には「magic comment」という記載がありました。

    To define a source code encoding, a magic comment must be placed into the source files either as first or second line in the file

    (「PEP 263 Defining Python Source Code Encodings」 @ PEP より、赤字は筆者)

    【筆者訳】

    ソースコードのエンコーディングを定義するには、ソースファイルの 1 行目もしくは 2 行目にマジックコメントを設置する必要があります

    Ruby などの「マジックコメント1」と同様なようです。しかし、Python の公式文献(日本語版)には、具体的な和名は見つかりませんでした。

    • ソースコードの文字コード指定の特別なコメント
    • ソースコードのエンコーディング
    • 文字セットエンコーディングの宣言

    (「ソースコードの文字コード」| インタプリタとその環境 @ Python3 公式マニュアル より)
    (「PEP 263: ソースコードのエンコーディング」| What’s New in Python 2.3 @ Python2 公式マニュアル より)

    「特別なコメント」が「magic comment」の訳だと思いますが、magic の訳し方が難しかったのだと思います。

    「ソースコードに魔法のコメントで文字コードを指定します」だと微妙、みたいな。「おいしくなぁれ」のような謎の仕様に思われるからでしょうか。

    普通に「文字コード指定」「エンコーディング指定」「エンコーディング宣言」「文字セットエンコーディングの宣言」などで通じそうです。深く考えすぎてしまいました。

    コンピューターの世界では「何を言っているのかわからないも、唱えると動いちゃうし、むしろそれがないと正常に動かない」系のものを「マジックなんとか」と呼ぶ習慣があります。マジックナンバーなどは有名です。しかし、ドキュメントにそこまで説明的に書くのはナンセンスですし「初心者に優しい言語」と、うたっている Python ゆえの葛藤だったのかもしれません。知らんけど。


  • Python 3 の場合、ソースが UTF-8 の時は記載は不要
    PEP-8 ではむしろ記載は非推奨 😱)

    • Python 2 の場合、ソースが UTF-8 の時は逆に記載が必須
  • 文字コード宣言を記載する場合:

    • 1 行目 or 2 行目に記載すること
    • 以下のいずれの記述方法でも構わない(その理由は後述
      • # coding: [encoding]
      • # coding:[encoding]
      • # coding=[encoding]
      • # coding= [encoding]
      • # -*- coding: [encoding] -*- (Emacs 併用)
      • # -*- coding:[encoding] -*- (Emacs 併用)
      • # vim:fileencoding=[encoding] (Vim 併用)
  • [encoding] 部分の仕様

    • 正規表現 ([-\w.]+) にマッチすること
      • - a-z A-Z 0-9 _ . の連続文字列
      • _\w の正規表現に含まれています。(詳しくはコメント参照)
    • 大文字・小文字は問わない
  • [encoding] 部分で指定可能な UTF-8 の一覧(エイリアス含む)

    • utf8, utf-8, utf_8, u8, utf, cp65001
  • Python3 ソースコードのエンコーディング指定で利用可能な文字コード名一覧

PEPとは

PEP の基本概要

PEPとは何か?

公式のガイドラインには以下のような説明があります。

PEP は Python 拡張提案(Python Enhancement Proposal)を表しています。PEP は Python のコミュニティに対して情報を提供したり、Python の新機能やプロセス、環境などを説明するための設計書です。PEP は、技術的な仕様と、その機能が必要な論理的な理由を提供しなければなりません。

我々は、PEP を、新機能を提案したり、課題に関するコミュニティの意見を集約したり、Python に取り込まれることになる、設計上の決定をドキュメント化するための、主要なメカニズムであると位置づけています。PEP の作者は、コミュニティの中でコンセンサスを取ったり、反対意見の記録を取る責任を持ちます。

PEP はテキストファイルの形式でバージョン管理されたリポジトリの中に保管されます。そのリポジトリの変更履歴は、機能提案の経緯の記録となります。

(PEP-0001「PEPの目的とガイドライン(日本語訳)」@ Sphinx の日本ユーザ会 より)

なかなか、何を、言っているか、わからない、感じの、説明です。

一言で言うと「PEP は Python のスタンダード(標準)をまとめたもの」です。

つまり、ISOIEEEW3C のような、Python における各種スタンダード(標準化された規格)が制定されたものが取りまとめられた、いわば Python のプロトコル CODE です。

例えば「Python のコーディング規約は PEP-8 で制定されている」といった具合です。

PHP の PSR のようなものですが、外部団体でなく Python 自体が制定しているので「Python はコードが読みやすい」理由にもなっているのかもしれません。最初から整頓されていて、さすが Python パイセン。PHP より年上とは思えない几帳面さ。


ミッフィーちゃんみたいなやつ

(・×・) のような顔文字っぽいのは薄っすらと覚えているのです。しかし、それが -*- だったか _*_ だったか (⊃*⊂) だったか思い出せないのです。

文字コードの指定方法も /*-utf-8-*/ だったか lang=ja_JP.utf-8 だったか encode=utf-8 だったかすらも思い出せないのです。

そこで、パーツを2つにわけて覚えようとしました。「前後の記号」部分(A)と「エンコード指定」部分(B)です。

前後の記号部=A、エンコード指定部=B
[A] [      B        ] [A]
-*- coding=[encoding] -*-

まずは「前後の記号」(A の -*- の部分)です。

「迫るパイソンに、冷静に口を開いて威嚇いかくするミッフィー
-x-(威嚇前)→ -*-(威嚇中)

せっかく覚えたのですが、「エンコード指定部」の文言もんごん( B のcoding: utf-8 の部分)を調べていくうちに Emacs ユーザーでない場合はミッフィーちゃんは不要であることがわかりました。

どうやら、-*- coding: ... -*- と書くのは Emacs 互換のための記述のようです。

しかも、GNU Emacs も v23 からデフォルトエンコーディングが UTF-8 になりました。

文字コード宣言の仕様

残念ながら「ミッフィーちゃんは必須ではない」らしい情報は得たものの、続けて「エンコード指定」の部分を調べていると、同じ UTF-8 でも人によって書き方にバラツキがあることに気付きました。

  • coding=utf8
  • coding=utf-8
  • coding:utf-8
  • coding:utf8
  • coding: utf8
  • coding=utf_8
  • coding:utf_8
  • encoding:utf-8
  • encoding:UTF8
  • etc...

どうやら、これも覚えられなかった一因のようです。むしろ「ウォーリーを探せ」くらいの微妙な違いです。(実は、このことが検証時にさらなる悲劇を生むことになります。こうご期待)

そこで、仕様的に正しくはどう書くのか調べたので報告いたします。

If a comment in the first or second line of the Python script matches the regular expression coding[=:]\s*([-\w.]+), this comment is processed as an encoding declaration;

Encoding declarations | Lexical analysis @ 英語版公式 Python3 言語リファレンスより)

【筆者訳】
Python スクリプトの 1 行目または 2 行目のコメントが正規表現 coding[=:]\s*([-\w.]+) にマッチした場合、このコメントはエンコーディングの宣言文として扱われます。

上記仕様によると、Python では以下の2つの条件にマッチすると「文字コード指定行」と判断されます。

  1. 1行目もしくは2行目のコメント行であること

  2. coding[=:]\s*([-\w.]+)」の正規表現にマッチすること

    正規表現の説明

    コメント行は Bash で言う head -2 | grep -e "^#" と同等で、最初の 2 行のうち # から始まるものをコメント行として扱います。そしてコメント行を正規表現でチェックします。

    coding[=:]\s*([-\w.]+)」の正規表現の意味は以下の通りです。

    • coding[=:]
      • coding= もしくは coding: を含む
      • Coding: など大文字で始まるとマッチしないので注意
    • \s*
      • 0個以上のスペースが続く
    • [-\w.]+
      • - [a-zA-Z] [0-9] _ . の任意の1つ以上の連続文字。これがエンコード名。
      • _\w の正規表現に含まれています。詳しくはコメント参照。

つまり、仕様上は -*- は必要ないのです。

これがミッフィーちゃんが不要な理由です。逆に言えば以下の指定でも動いてしまうということです。

# 💩💩💩💩 uncoding=utf-8 💩💩💩💩

同様に、これが VIM 併用の記述としても使える理由です。

# vim:fileencoding=utf-8

また検証の結果、文字コードの UTF-8 utf-8大文字・小文字は区別しませんでした。調べたところ、内部で小文字に変換しているそうです。(後述)

となると不思議な点が出てきます。

coding[=:]\s*([-\w.]+) の仕様はわかったのですが、utf_8 のように _(アンダースコア、アンダーバー)を使った記述はどうなっているのでしょうか。

Python3 のソースコードのエンコーディングで利用可能な文字コード名

UTF-8 と UTF_8 の違い

先に結論から言うと「UTF-8」も「UTF_8」も違いはありません。内部で変換されてから正規表現を通して utf_8 と認識されるためです。

しかし、「正しくは(仕様的には)どちらで書いた方が好ましいのか」気になったので続けて調べてみました。

日本語の Python リファレンスには以下のように書かれています。

エンコーディングが宣言される場合、そのエンコーディング名は Python によって認識できなければなりません。宣言されたエンコーディングは、例えば文字列リテラル、コメント、識別子などの、全ての字句解析に使われます。

(「エンコード宣言(encoding declaration)| 字句解析」@ 公式 Python3 言語リファレンス より)

「エンコーディング名は Python によって認識できなければなりません」とあるのですが、どのようなものが認識されるのか明記されていませんでした。(2020/10/27 現在)

しかし、英語の方の公式 Python3 のドキュメントには、より具体的な記述がありました。

To declare an encoding other than the default one, a special comment line should be added as the first line of the file. The syntax is as follows:

# -*- coding: encoding -*-

where encoding is one of the valid codecs supported by Python.

Source Code Encoding | The Interpreter and Its Environment | 英語版公式 Python3 チュートリアル より)

【筆者訳】
デフォルト以外のエンコーディングを宣言するには、ファイルの1行目に特別なコメント行を追加する必要があります。構文は以下の通りです。

# -*- coding: [encoding] -*-

ここでの "encoding" は、Python でサポートされている有効なコーデックを指定します。

とリンク付きであります。

さらに英語版の方では、ありがたいことに「ソースコードの文字コードが Windows-1252 の場合、encoding 部分は cp1252 であるべき」というサンプルも記載されていました。

For example, to declare that Windows-1252 encoding is to be used, the first line of your source code file should be:

# -*- coding: cp1252 -*-

Source Code Encoding | The Interpreter and Its Environment | 英語版公式 Python3 チュートリアル より)

【筆者訳】
例えば、Windows-1252 エンコーディングを使用することを宣言するには、ソースコードファイルの 1 行目を以下のように記述します。

# -*- coding: cp1252 -*-

つまり、この codecs の対応表を探せば「Python3 のソースコードのエンコーディングで利用可能な文字コード名」とやらがわかりそうです。

ちゃんとライブラリのドキュメントの codecs 欄に対応表の一覧がありました。

そこから URL をいじって日本語版を開いてみます。

  • Python3 のソースコードのエンコーディングで利用可能な文字コード名

🐒 Python のドキュメントは整頓されているものの、意外に探しづらい気がするのは筆者だけでしょうか。情報の並びが東急ハンズに見えるのに、ドンキーで探し物をしているような印象を受けます。

Codec Aliases Languages
... ... ...
utf_8 U8, UTF, utf8, cp65001 all languages
... ... ...

なんと、ここで UTF-8 の内部コーデック名が utf_8_(アンダースコア)で指定されています。また、エイリアスとして別名でも指定できることがわかりました。

つまり U8utf8cp65001 と指定しても、内部で utf_8 に変換されるということです。

しかし、エイリアスの一覧に utf-8-(ハイフン)の記述がありません。

謎は深まるばかりです。

すると、一覧の上にサラっと以下の一文が記載されていました。

Notice that spelling alternatives that only differ in case or use a hyphen instead of an underscore are also valid aliases; therefore, e.g. 'utf-8' is a valid alias for the 'utf_8' codec.

【筆者訳】
注意点として、エイリアス以外でつづり・・・の違いが有効なのは、「大文字と小文字の違い」と「アンダースコア(_)の代わりにハイフン(-)を使用する場合」のみです。したがって、たとえば 'utf-8''utf_8' コーデックの有効なエイリアスとなります。

つまり、-(ハイフン)は _(アンダースコア、アンダーバー)に変換されるということです。

以上を踏まえると、utf-8utf_8 なら、utf_8 と記載した方が良いことになります。置換処理を必要としないストレートな処理ができるためです(ゆうて大した速度の違いはないのですが)。

大いなる誤解

実は、以前の記事の版では「_ は使わずに - を使え。Python2 で coding:utf_8 で動くのはたまたま」と言っていました。しかも、律儀に検証実証まで添えて。

というのも、coding[=:]\s*([-\w.]+) の正規表現には _ が含まれないと勘違いしていたのです。\w の正規表現はwordw だから [a-z][A-Z][0-9] だけだと重いコンダラ状態だったです。

ところが、コメントを読んでビックリしました。

... そう ... 実は小生、_ の挙動を色々と検証している中で、ずーっと codingcodeing と typo していたのでごザルよ!!そりゃ動かないわな!

スクリーンショット 2022-01-15 16.08.02.png
ザルな検証のしすぎで、検証猿でごザルの例

そこで、確認のために公式ドキュメントを読むと、なんと!Python の正規表現の \w には _(アンダースコア、アンダーバー)も含まれていたのです!!

\w
Unicode (str) パターンでは:
Unicode 単語文字にマッチします。これはあらゆる言語で単語の一部になりうるほとんどの文字、数字、およびアンダースコアを含みます。ASCII フラグが使われているなら、 [a-zA-Z0-9_] のみにマッチします。
正規表現のシンタックス | re --- 正規表現操作 | v3 | JA @ docs.python.org より)

ここでもドキュメント嫁にしかられてしまいました ... ... よくよく考えてみれば PHP の word character も同じ letter, number, underscore でした。とほほ。

このように、coding:utf_8 でも動いてしまう理由は「たまたま」ではなく、\w の正規表現に _ が含まれていることと、-_ に置換されるからでした! > @JsM さん、ありがとう!

まとめ

まとめると、渡されたエンコーディング名は前処理として以下を行い、codec と完全一致した場合に有効なエンコード指定と判断されるということです。

  1. コメント行の 1 〜 2 行目を取得する
  2. 正規表現でフィルターをかける
  3. 小文字に変換する
  4. -_ に変換される
  5. Aliases 一覧にマッチした場合は、対応する Codec に置換する
  6. 変換/置換後の結果が Codec 一覧のいずれかにマッチした場合は有効なエンコード指定とする

今度は、ちゃんとした実証実験として、# coding:utf_8 と宣言した Python ファイルを Python2 で実行すると、ちゃんと動きました!

$ python3 sample.py
これはUTF-8ですよ

$ python2 sample.py
これはUTF-8ですよ

文字コードを記載しない人たちの理由 (PEP-8に準拠している人たち)

さて、文字コードについて完全に理解したつもりでいたところ、「そもそも文字コードを指定していないソースコード」も見かけました。

「(おいおい。そんな装備で大丈夫かぁ)」とニヤニヤしながら調べたところ、そもそも Python3 では UTF-8 の場合は文字コードは指定しないことが推奨されていることがわかりました。

PEP8を読んでいたらこんな記述に気づいた。

ASCII (Python 2) や UTF-8 (Python 3) を使用しているファイルにはエンコーディング宣言を入れるべきではありません。
はじめに — pep8-ja 1.0 ドキュメント

 えぇぇぇぇ!? と思ったんだけど、何回読み直しても「デフォルトエンコーディング使うならエンコーディング宣言は書くなよ!」と書いてあるようにしか読めない。

(「# coding: utf-8はもうやめる」 @ 静かなる名辞 より)

マスオさんどころか、私もビックリえぇぇぇぇ!?⤴️です。

確かに、リンク先のドキュメントや PEP-8 の英語の原文を読んでも、「Python2 の場合 ASCII、Python3 の場合 UTF-8 で書かれたソースコードでは文字コードの宣言文はいれない」という規約があるようです。(コメントも 120% の確信がなければ日本語も不可って随分と厳しいルールですね。まぁ、レベルが should 以下なので絶対というわけではないですけど)

Python3 の公式マニュアルでは、「デフォルトでは、Python のソースコードは UTF-8 でエンコードされているものとして扱われます。」とあります。

そのため、UTF-8 の場合は基本的に不要というのはわかりました。そして、Python2 の場合は、デフォルト・エンコードが ASCII であるため、逆に UTF-8 の場合は必須ということです。

おそらく、名著とされる本でも文字コードを書くように勧めるのは、Python2 から Python3 への過渡期であるいま、両バージョンを網羅しないといけないからなのかもしれません。また、SJIS-win から UTF-16 への移行過渡期にある Windows ユーザーのことも考えてポカヨケとして記載させているのかもしれません。

PHP4 時代、メール送信の実装でガラケー対応など、混沌とした文字コードを吸った揉んだしようとしたホロ苦い経験から、内部では UTF-8 で処理を統一して、出力時に文字コードを変換するというプラクティスがありました。そのため、むしろ Python3 の UTF-8 がデフォルトというのはシックリと来ます。

さて、ここまで調べた結果の結論を言うと、Python3 で行くと決めたし、UTF-8 で統一しているから「冒頭に記載するアレ」は不要という、ナンタルチアな答えに。とほほ。

あわせて読みたい

その他参考文献

  1. magic_comment | 多言語化 @ Ruby 公式ドキュメント

219
179
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
219
179