88効率的なPythonプログラミングの標語(2):文字列を実際にフォーマットできますか?

目次

1. Cスタイルの文字列フォーマット

2.組み込みのformat関数とstr.formatメソッド

3. fストリング

総括する:

 

WeChatパブリックアカウント「Geek Origin」に595586と入力して、「88 Catchments for Efficient Python Programming」シリーズのすべての記事をご覧ください。

Python言語では、文字列には多くの用途があります。ユーザーインターフェイスとコマンドラインユーティリティでメッセージを表示するために使用できます。ファイルとソケットにデータを書き込むために使用されます。「異常な」メッセージを指定するために使用されます。プログラムのデバッグに使用されます。

書式設定は、事前定義されたテキストとデータを人間が読めるメッセージに結合するプロセスです。Pythonには、文字列をフォーマットする4つの異なる方法があります。これらの4つの方法の一部は言語レベルでサポートされ、一部は標準ライブラリでサポートされています。それらの1つを除いて、他のフォーマット方法には重大な欠点があります。これらを使用する場合は、これらを回避する必要があります。

1. Cスタイルの文字列フォーマット

Python言語で文字列をフォーマットする最も一般的な方法は、%書式演算子を使用することです。事前定義されたテキストテンプレートは、フォーマット文字列の形式で%演算子の左側に配置され、テンプレートに挿入されるデータは%演算子の右側にあります。これらのデータは、単一の値またはタプル(リストではない)にすることができます。つまり、テンプレートに複数の値を挿入します。たとえば、ここでは%演算子を使用して、読み取りにくい2進値と16進値を整数文字列に変換しています。

a = 0b10111010
b = 0xc5c
print('二进制:%d, 十六进程:%d' % (a, b))

このコードを実行すると、次のように出力されます。

二进制:186, 十六进程:3164

フォーマット文字列は、プレースホルダーとしてフォーマット指定子(%dなど)を使用します。プレースホルダーは、%演算子の右側の値に置き換えられます。形式指定子の構文は、Python(および他のプログラミング言語)に継承されているC言語のprintf関数から来ています。Pythonは、一般的に使用されるすべてのprintf関数フォーマットオプションをサポートしています。%s、%x、%fなどのフォーマット指定子や、小数点以下の桁数、パディング、パディング、配置の制御など。Pythonに慣れていないプログラマの多くは、使い慣れていて使い慣れているため、Cスタイルのフォーマット文字列から始めます。

ただし、Cスタイルのフォーマット文字列メソッドを使用すると、次の4つの問題が発生します。

質問1:

フォーマット式の右側のタプル内のデータ値のタイプまたは順序を変更すると、互換性のないタイプ変換が原因で例外がスローされる場合があります。たとえば、次の単純なフォーマット式は機能します。

key = 'my_key'
value = 1.234
formatted = '%-10s = %.2f' % (key, value)
print(formatted)

このコードを実行すると、次のように出力されます。

my_key     = 1.23

しかし、キーと値の値を交換する方法は、ランタイム例外をスローします:

key = 1.234
value = 'my_key'
formatted = '%-10s = %.2f' % (key, value)
print(formatted)

このコードを実行すると、次の例外がスローされます。

Traceback (most recent call last):
  File "/python/format.py", line 12, in <module>
    formatted = '%-10s = %.2f' % (key, value)
TypeError: must be real number, not str

同様に、%of rightのタプル内の値の順序が変わると、例外もスローされます。

formatted = '%-10s = %.2f' % (key, value)

この問題を回避するには、%演算子の両側のデータ型が一致するかどうかを常に確認する必要があります。コードを変更するたびに、データ型が一致するかどうかを手動で確認する必要があるため、このプロセスではエラーが発生しやすくなります。

質問2:

Cスタイルのフォーマット式の2番目の問題は、文字列としてフォーマットする前に値を少し変更する必要がある場合、非常に一般的な要件である読み取りが困難になることです。ここでは、インライン変更を行わずにキッチンパントリーの内容をリストしました。

pantry = [
    ('avocados', 1.25),
    ('bananas', 2.5),
    ('cherries', 15),
]
for i, (item, count) in enumerate(pantry):
    print('#%d: %-10s = %.2f' % (i, item, count))

このコードを実行すると、次の結果が出力されます。

#0: avocados   = 1.25
#1: bananas    = 2.50
#2: cherries   = 15.00

ここで、より有用な情報を出力するために、フォーマットする値にいくつかの変更を加えました。これにより、フォーマット式のタプルが長くなりすぎて、複数行に分割する必要があり、プログラムの可読性が損なわれます。

for i, (item, count) in enumerate(pantry):
    print('#%d: %-10s = %d' % (
        i + 1,
        item.title(),
        round(count)))

このコードを実行すると、次のコンテンツが出力されます。

#1: Avocados   = 1
#2: Bananas    = 2
#3: Cherries   = 15

質問3:

式のフォーマットに関する3番目の問題は、フォーマット文字列で同じ値を複数回使用する場合、右側で値を複数回繰り返す必要があることです。

template = '%s loves food. See %s cook.'
name = 'Max'
formatted = template % (name, name)
print(formatted)

このコードを実行すると、次のコンテンツが出力されます。

Max loves food. See Max cook.

これらの繰り返し値にいくつかの小さな変更を加える必要がある場合、これは特に煩わしく、エラーが発生しやすくなります。この問題を解決するには、タプルの代わりにディクショナリを使用して、フォーマット文字列のデータを提供することをお勧めします。辞書の値を参照する方法は%(key)です。次の例を参照してください:

old_way = '%-10s , %.2f, %-8s' % (key, value,key)  # 重复指定key


new_way = '%(key)-10s , %(value).2f, %(key)-8s' % {
            'key': key, 'value': value}            # 只需要指定一次key 


print(old_way)
print(new_way)

このコードを実行すると、次のコンテンツが出力されます。

key1       , 1.13, key1    
key1       , 1.13, key1

%の右側の値を繰り返し引用する必要がある場合、タプルを使用する場合、これらの値は、この例のキーなど、繰り返し指定する必要があることがわかります。辞書を使用するには、キーを1回指定するだけで済みます。

次に、辞書を使用して文字列をフォーマットすると、他の問題が発生し、悪化する可能性があります。上記の質問2では、フォーマット前の値のわずかな変更により、%演算子の右側にキーとコロン演算子が存在するため、フォーマット式が長くなり、視覚的に煩雑になります。次のコードでは、この問題を説明するために、ポインタを使用せずに辞書を使用して同じ文字列をフォーマットしています。

for i, (item, count) in enumerate(pantry):
    before = '#%d: %-10s = %d' % (
        i + 1,
        item.title(),
        round(count))


    after = '#%(loop)d: %(item)-10s = %(count)d' % {
        'loop': i + 1,
        'item': item.title(),
        'count': round(count),
    }


    assert before == after

質問4:

辞書フォーマット文字列を使用すると、4番目の問題も発生します。つまり、各キーを少なくとも2回指定する必要があります。1回はフォーマット指定子で、もう1回はディクショナリでキーとして指定します。ディクショナリ値自体が変数の場合、再度指定する必要があります。

soup = 'lentil'
formatted = 'Today\'s soup is %(soup)s.' % {'soup': soup}   # 这里再次指定了变量soup
print(formatted)

出力は次のとおりです。

Today's soup is lentil.

文字の繰り返しに加えて、この冗長性により、辞書を使用した長いフォーマット式が生じる可能性もあります。これらの式は通常複数行にまたがる必要があり、フォーマット文字列は複数の行に連結され、ディクショナリの割り当てには、フォーマットする値ごとに1行しかありません。

menu = {
    'soup': 'lentil',
    'oyster': 'kumamoto',
    'special': 'schnitzel',
}
template = ('Today\'s soup is %(soup)s, '
            'buy one get two %(oyster)s oysters, '
            'and our special entrée is %(special)s.')
formatted = template % menu
print(formatted)

出力は次のとおりです。

Today's soup is lentil, buy one get two kumamoto oysters, and our special entrée is schnitzel.

書式文字列は非常に長く、複数行にわたる場合があるため、文字列全体が表現したい内容を理解するには、眼鏡を上下左右に移動する必要があり、検出されたはずのエラーを無視するのは簡単です。文字列をフォーマットするためのより良いソリューションはありますか?次の部分をご覧ください。

2.組み込みのformat関数とstr.formatメソッド

Python 3では、%演算子を使用したCスタイルのフォーマットされた文字列よりも表現力のある高度な文字列フォーマットのサポートが追加されています。個々の値については、組み込み関数をフォーマットすることにより、この新しい機能にアクセスできます。たとえば、次のコードはいくつかの新しいオプションを使用して(千桁区切り、および中央揃えに^)、値をフォーマットします。

a = 1234.5678
formatted = format(a, ',.2f')
print(formatted)


b = 'my string'
formatted = format(b, '^20s')    # 居中显示字符串
print('*', formatted, '*')

結果は次のとおりです。

1,234.57
*      my string       *

文字列のformatメソッドを呼び出すことで、複数の値をフォーマットできます。formatメソッドは、%dのようなCスタイルのフォーマット指定子を使用する代わりに、プレースホルダーとして{}を使用します。デフォルトでは、フォーマット文字列のプレースホルダーは、フォーマットメソッドの対応する位置にあるプレースホルダーに、それらが表示される順序で渡されます。

key = 'my_var'
value = 1.234


formatted = '{} = {}'.format(key, value)
print(formatted)

結果は次のとおりです。

my_var = 1.234

各プレースホルダーでは、コロン(:)の後にフォーマット指定子を指定して、値を文字列に変換する方法を指定できます。コードは次のとおりです。

formatted = '{:<10} = {:.2f}'.format(key, value)
print(formatted)

結果は次のとおりです。

my_var      = 1.23

formatメソッドの動作原理は、組み込み関数formatにフォーマット指定子と値(上記の例ではformat(value、 '。2f'))を渡すことです。次に、対応するプレースホルダーを関数の戻り値に置き換えます。__format__メソッドを使用して、各クラスのフォーマット動作をカスタマイズできます。

Cスタイルのフォーマット文字列の場合、%演算子を変換してエスケープする必要があります。つまり、プレースホルダーと間違われないように、2つの%を記述します。str.formatメソッドを使用して、中括弧もエスケープする必要があります。

print('%.2f%%' % 12.5)
print('{} replaces {
    
    {}}'.format(1.23))

出力は次のとおりです。

12.50%
1.23 replaces {}

中括弧内では、プレースホルダーを置き換えるためにformatメソッドに渡されるパラメーターの位置インデックスを指定することもできます。これにより、フォーマットメソッドで渡される値の順序を変更せずに、フォーマット文字列内のプレースホルダーの順序を変更できます。

formatted = '{1} = {0}'.format(key, value)
print(formatted)

出力は次のとおりです。

1.234 = my_var

位置インデックスを使用するもう1つの利点は、フォーマット文字列で値を複数回引用する場合に、フォーマットメソッドを通じて値を渡すだけでよいことです。この値は、同じ位置インデックスを使用して、フォーマット文字列で複数回参照できます。

formatted = '{0} loves food. See {0} cook.'.format(name)
print(formatted)

出力は次のとおりです。

Max loves food. See Max cook.

残念ながら、formatメソッドは上記の問題2を解決できないため、(パラメーターの位置を揃える必要があるため)フォーマットする前に値に小さな変更を加えることはより困難です。次のコードでは、%演算子とformatメソッドを比較していますが、実際に同時に読み取ることも簡単ではありません。

for i, (item, count) in enumerate(pantry):
    old_style = '#%d: %-10s = %d' % (
        i + 1,
        item.title(),
        round(count))
    new_style = '#{}: {:<10s} = {}'.format(
        i + 1,
        item.title(),
        round(count))


    assert old_style == new_style

formatメソッドで使用されるフォーマット指定子には、プレースホルダーでの辞書キーとリストインデックスの組み合わせの使用、Unicodeとrepr文字列への値の強制など、より高度なオプションがあります。

formatted = 'First letter is {menu[oyster][0]!r}'.format(
    menu=menu)
print(formatted)

結果は次のとおりです。

First letter is 'k'

ただし、これらの機能は、問題4の重複キーの冗長性を減らすのに役立ちません。たとえば、ここでは、Cスタイルの書式式で辞書を使用することの冗長性と、キーパラメータをformatメソッドに渡す新しいスタイルを比較します。

old_template = (
    'Today\'s soup is %(soup)s, '
    'buy one get two %(oyster)s oysters, '
    'and our special entrée is %(special)s.')
old_formatted = template % {
    'soup': 'lentil',
    'oyster': 'kumamoto',
    'special': 'schnitzel',
}


new_template = (
    'Today\'s soup is {soup}, '
    'buy one get two {oyster} oysters, '
    'and our special entrée is {special}.')
new_formatted = new_template.format(
    soup='lentil',
    oyster='kumamoto',
    special='schnitzel',
)
assert old_formatted == new_formatted

このスタイルは、ディクショナリの一部の引用符とフォーマット指定子の一部の文字を削除するため、ノイズが少なくなりますが、完全ではありません。さらに、プレースホルダーで辞書キーとインデックスを使用する高度な機能は、Python式機能のほんの一部しか提供しません。この表現力の欠如により、フォーマットメソッド全体の価値が失われます。

これらの欠点とCスタイルのフォーマット式の問題(上記の質問2と4)を考慮すると、str.formatメソッドをできるだけ使用しないことをお勧めします。フォーマット指定子(コロンの後のすべて)で使用される新しいミニ言語と、フォーマットの組み込み機能の使用方法を理解することは非常に重要です。

3. fストリング

Python 3.6では、これらの問題を完全に解決するために、補間形式文字列(f文字列と呼ばれる)が追加されています。この新しい言語構文では、フォーマット文字列の前にf文字を付ける必要があります。これは、バイト文字列の前にb文字を付けたものと同様であり、元の(エスケープされていない)文字列の前にr文字を付けます。

f-stringは、フォーマット文字列の表現力を最大化し、フォーマットされるキーと値を提供する冗長性を完全に排除することにより、問題4を完全に解決します。これは、現在のPythonスコープ内のすべての変数をフォーマット式の一部として参照できるようにすることで行われます。

key = 'my_var'
value = 1.234


formatted = f'{key} = {value}'
print(formatted)

出力は次のとおりです。

my_var = 1.234

f-stringのプレースホルダーの後のコロンの後に、フォーマットされた組み込みミニ言語のすべての同じオプションを使用できます。または、値をstr.formatメソッドと同様に、Unicodeおよびrepr文字列に強制できます。

formatted = f'{key!r:<10} = {value:.2f}'
print(formatted)

出力は次のとおりです。

'my_var' = 1.23

すべての場合において、f-stringを使用したフォーマットは、%演算子とstr.formatメソッドを使用したCスタイルのフォーマットされた文字列を使用したフォーマットよりも短くなります。ここでは、簡単に比較できるように、これらのすべてのフォーマット方法を最短から最長の順に示しました。

f_string = f'{key:<10} = {value:.2f}'


c_tuple  = '%-10s = %.2f' % (key, value)


str_args = '{:<10} = {:.2f}'.format(key, value)


str_kw   = '{key:<10} = {value:.2f}'.format(key=key,
                                          value=value)


c_dict   = '%(key)-10s = %(value).2f' % {'key': key,
                                       'value': value}


print(f'f_string:{f_string}')
print(f'c_tuple:{c_tuple}')
print(f'str_args:{str_args}')
print(f'str_kw:{str_kw}')
print(f'c_dict:{c_dict}')

出力は次のとおりです。

f_string:my_var     = 1.23
c_tuple:my_var     = 1.23
str_args:my_var     = 1.23
str_kw:my_var     = 1.23
c_dict:my_var     = 1.23

f-stringは、完全なPython式をプレースホルダーブラケットに入れることもできます。簡潔な構文でフォーマットされた値に小さな変更を加えることで、問題2を根本的に解決できます。これで、Cスタイルのフォーマットとstr.formatメソッドを使用して複数行のコンテンツを費やすことが、1行に簡単に追加できるようになりました。

for i, (item, count) in enumerate(pantry):
    old_style = '#%d: %-10s = %d' % (
        i + 1,
        item.title(),
        round(count))


    new_style = '#{}: {:<10s} = {}'.format(
        i + 1,
        item.title(),
        round(count))


   f_string = f'#{i+1}: {item.title():<10s} = {round(count)}'


   assert old_style == new_style == f_string

もちろん、コードをより明確にしたい場合は、f文字列を複数の行に分割できます。単一行バージョンよりもさらに長く、他の複数行メソッドよりもはるかにクリーンです。

for i, (item, count) in enumerate(pantry):
    print(f'#{i+1}: '


          f'{item.title():<10s} = '
          f'{round(count)}')

出力は次のとおりです。

#1: Avocados   = 1
#2: Bananas    = 2
#3: Cherries   = 15

Python式は、フォーマット指定子オプションでも使用できます。たとえば、ここでは、出力する浮動小数点数を、フォーマット文字列としてハードコーディングする代わりに変数を使用して指定します。

places = 3
number = 1.23456
print(f'My number is {number:.{places}f}')

f-stringは表現力、単純さ、明快さを組み合わせることができ、Pythonプログラマーにとって最高の組み込みオプションになります。値を文字列としてフォーマットする必要がある場合は、代わりにf-stringを選択できます。

総括する:

1.%演算子を使用したCスタイルのフォーマット文字列は、さまざまなトラップや長い問題に遭遇します。

2. str.formatメソッドは、フォーマット指定子のミニ言語にいくつかの有用な概念を導入していますが、他の側面でCスタイルのフォーマット文字列のエラーを繰り返すため、回避する必要があります。

3. f-stringは、値を文字列にフォーマットするための新しい文法であり、Cスタイルのフォーマット文字列の最大の問題を解決します。

4. f文字列は簡潔で強力です。これは、任意のPython式を書式指定子に直接埋め込むことができるためです。

この記事に興味がある場合は、Li Ning先生のWeChatパブリックアカウント(unitymarvel)を追加できます。

おすすめ

転載: blog.csdn.net/nokiaguy/article/details/108722775