Pythonの新しい文字列フォーマット : %記号、str.format()から文字列補間へ

「ほとんどの状況への対処について、一つの正しいやり方にフォーカスする」言語であるPythonですが、その文字列フォーマットは非常に悩ましく、また年々、多様化が進んでいます。
Python 3.6では、文字列をフォーマットする方法には3通りあります(簡単な結合やstring.Templateの使用を除きます)。

  • %演算子
  • str.format関数
  • 文字列の補間

(もし、この記事を全部読むつもりがないようであれば、2016年2月に開催されるPyGrazの会合に関する記事で、追加の例を含めてもう少し幅広くご紹介したいと思います)

%形式の文字列フォーマット

%形式は、少なくとも1.0バージョンからPythonに組み込まれているフォーマットです。Python 3以前のバージョンから使用している方には馴染みがあるでしょう。

"%s %s" % ('Hello', 'World',)

多少の相違はあるものの、これはC言語のsprintfと同等の関数です。やるべき役割は果たしますが、かなりの作業が必要となります。

%形式は限られた型しかサポートしていないため、カスタムオブジェクトをこれに渡したい場合はその前にサポートされている型に変換しなければなりません。

何年も経ってから、ネイティブの文字列データ型にformatメソッドが拡張されました。

str.format()

str.format関数は、2008年10月にリリースされたPython 2.6にコンテキストマネージャと同時に追加されました。詳細はPEP-3101に記載されていますが、従来の%の二項演算子が持ついくつかの問題を解決しています。例えば、サポートされる型の制約や、特定の条件でエラーを引き起こす式全体の右辺の扱いなどが挙げられます。

>>> "%s" % ("lala",)
'lala'
>>> "%s" % "lala"
'lala'

formatは(バイナリメソッドにマッピングされた)演算子ではなくメソッドであることから、引数の扱いはより明確になります。つまり、文字列を渡すと文字列として読み取られ、1つの文字列を含んだタプルを渡すと、1つの文字列を含んだタプルとして読み取られます。

>>> "{}".format("lala")
'lala'
>>> "{}".format(("lala",))
"('lala',)"

%形式と比較すると、こちらは辞書を使わなくても、パラメータ名を与えられるようになっています。

"{firstname} {lastname}".format(firstname="Horst", lastname="Gutmann")

当初は、%演算子と完全に置き換えられる予定でしたが(Python3.1で従来のスタイルのフォーマット機能は非推奨化される予定でした)、いまだに置き換えは行われていません。この文字列フォーマッタの主な機能は従来の%演算子とほぼ同じですが、シンタックスが多少異なりますし、私の印象としては、より直観的になっています。実際、Ulrichと私が作成したpyformat.infoによって新しいシステムへの移行に役立っています。

しかし、明白なのは、PEP-3101では古い機能群が取り除かれているだけでなく、カスタムクラスでより幅広いインタラクションが可能なプロトコルも導入されているということです。

class Country:
    def __init__(self, name, iso):
        self.name, self.iso = name, iso

    def __format__(self, spec):
        if spec == 'short':
            return self.iso
        return self.name

country = Country("Austria", "AUT")

print("{}".format(country))
print("{:short}".format(country))

__format__メソッドは、オプションを渡すことができる文字列フォーマットのための__str__と考えることができます。オブジェクトに__format__メソッドを実装した時には、formatメソッドを使った場合、__str__の代わりに使用できます。(ただし"{!s}".format(country)"のように書いた場合は除きます)。

実際、Python 3.4のdatetime.dateクラスに、良い使用例があります。

class date:
    ...
    def __format__(self, fmt):
        if len(fmt) != 0:
            return self.strftime(fmt)
        return str(self)

これで「親クラス」の文字列フォーマットに直接日付の書式を設定することができるので、最初に日付データを文字列に変換してから、文字列フォーマッタに渡す必要がなくなります。

import datetime
print("Today is {:%A}".format(datetime.datetime.now()))
# Today is Thursday

PEP-0498: 文字列の補間

現時点で、.formatは文字列のフォーマット方法として推奨されていますが、とても冗長です。

a = "Hello"
b = "World"
"{} {}".format(a, b)
# vs.
"%s %s" % (a, b,)

PEP-0498では、かなり以前からRubyやScala、Perlなどのプログラミング言語に共通する機能を提供して、この状況を改善しようとしています。それが文字列の補間です。この機能では、式は文字列自身に直接統合され、このことは明示的に他のいかなる関数も呼び出す必要が無いことを意味しています。

ES2015は、”つい最近”この機能を「テンプレートリテラル」と呼び、JavaScriptの世界に導入しました。

const username = "Horst";
const welcomeMsg = `Hello, ${username}!`;

Pythonでは、バッククォートはPython3.0のリリース以前にちょっとした経緯があり、使用することができません。この記号を再び導入すると、言語の基本的な構文に、またしても影響を及ぼすでしょう。その代わりとして、導入されたのがリテラルのfプリフィックスです。

a = "Hello"
b = "World"
f"{a} {b}"
f"{a + ' ' + b}"

もはや明示的にその文字列の.format()メソッドを呼び出す必要は無く、単にfプレフィックスを使った形式で表し、最終的な文字列に式を埋め込みます。埋め込まれていなければ、.format()で得られる機能と同じものを提供することになっています。フォーマットされたこれらの文字列は、ドキュメンテーションでは「f文字列」と呼ばれています。

なかなか良さそうに思えますが、Python 3.6のリリースはまだ1年先なので、もう少し待たなければなりません。しかし、コードはすでに存在しているので、Python 3.6のプレリリース版やpyenvで使っているようなティップを入手することができます。試してみてください😊

もう一つ別のPEP(0501)では、文字列を遅延評価するi文字列を導入しています。例えば、I18Nに対応したり、最後の評価の前にセキュリティチェックを行ったりすることができます。更なる議論が行われるまで、その提案が延期されているとはいえ、素晴らしい考えだと思います。

f文字列に戻ります。文字列の補間がこれまでどのようにして解決されたのかを、もっと詳しく知りたいのであればPEP-0502を見てください。そこにはこの機能の背景にある意図や他の言語から受けた影響など、もっと詳しい考察が掲載されています。