f文字列の評価を延期/延期するにはどうすればよいですか?


99

私はテンプレート文字列を使用していくつかのファイルを生成していますが、以前のテンプレートコードを次のようなものから減らすために、この目的のための新しいf文字列の簡潔さが気に入っています。

template_a = "The current name is {name}"
names = ["foo", "bar"]
for name in names:
    print (template_a.format(**locals()))

これで、変数を直接置き換えて、これを行うことができます。

names = ["foo", "bar"]
for name in names:
    print (f"The current name is {name}")

ただし、テンプレートを他の場所(コードの上位)で定義したり、ファイルなどからインポートしたりすることが理にかなっている場合があります。これは、テンプレートがフォーマットタグを含む静的な文字列であることを意味します。文字列を新しいf文字列として解釈するようにインタプリタに指示するには、文字列に何かが発生する必要がありますが、そのようなことがあるかどうかはわかりません。

文字列を取り込んで、.format(**locals())呼び出しの使用を回避するためにf文字列として解釈する方法はありますか?

理想的にmagic_fstring_functionは、このようにコーディングできるようにしたいです...(私が理解していない部分がどこにあるのか):

template_a = f"The current name is {name}"
# OR [Ideal2] template_a = magic_fstring_function(open('template.txt').read())
names = ["foo", "bar"]
for name in names:
    print (template_a)

...この望ましい出力で(ファイルを2回読み取らずに):

The current name is foo
The current name is bar

...しかし、実際に得られる出力は次のとおりです。

The current name is {name}
The current name is {name}

5
f文字列ではそれを行うことはできません。f文字列は、データではない、それは確かに文字列ではありません。それはコードです。(disモジュールで確認してください。)後でコードを評価したい場合は、関数を使用します。
kindall 2017

12
参考までに、PEP 501は最初の理想に近い機能を提案しましたが、現在は「[f-strings]のさらなる経験が出るまで延期されています」。
jwodder 2017

テンプレートは静的な文字列ですが、f文字列は文字列ではなく、@ kindallが言ったように、コードオブジェクトです。f-stringは、最終的に使用されるときではなく、インスタンス化されると(Python 3.6,7で)すぐに変数にバインドされると思います。したがって、f-stringは.format(**locals())、見た目は優れていますが、醜い古いものよりも役に立たない場合があります。PEP-501が実装されるまで。
SMCI

グイドは、私たちを救うが、PEP 498は本当にそれをしくじりましたPEP 501で説明されている延期された評価は、コアのf-string実装に組み込まれている必要があります。現在str.format()、一方では遅延評価をサポートする機能性が低く、非常に遅い方法と、他方では遅延評価をサポートしない、より機能的で非常に高速なf文字列構文との間で問題が発生してます。したがって、まだ両方が必要であり、Pythonにはまだ標準の文字列フォーマッタがありません。xkcd標準ミームを挿入します。
セシルカレー

回答:


26

これが完全な「理想2」です。

これはf文字列ではなく、f文字列も使用しませんが、要求どおりに使用します。指定されたとおりの構文。を使用していないため、セキュリティ上の問題はありませんeval()

小さなクラスを使用し、__str__printによって自動的に呼び出される実装を行います。クラスの限られたスコープを回避するために、inspectモジュールを使用して1フレーム上にホップし、呼び出し元がアクセスできる変数を確認します。

import inspect

class magic_fstring_function:
    def __init__(self, payload):
        self.payload = payload
    def __str__(self):
        vars = inspect.currentframe().f_back.f_globals.copy()
        vars.update(inspect.currentframe().f_back.f_locals)
        return self.payload.format(**vars)

template = "The current name is {name}"

template_a = magic_fstring_function(template)

# use it inside a function to demonstrate it gets the scoping right
def new_scope():
    names = ["foo", "bar"]
    for name in names:
        print(template_a)

new_scope()
# The current name is foo
# The current name is bar

13
私はこれを答えとして受け入れるつもりですが、極端な巧妙さのために実際にコードで使用することはないと思います。まあ多分決して:)。たぶん、Pythonの人々はPEP501の実装にそれを使用することができます。私の質問が「このシナリオをどのように処理する必要があるか」である場合、答えは「.format()関数を使い続け、PEP501が解決するのを待つだけです」です。すべきでないことを行う方法を理解してくれてありがとう、@ PaulPanzer
JDAnders

6
テンプレートに単純な変数名よりも複雑なものが含まれている場合、これは機能しません。例:template = "The beginning of the name is {name[:4]}"(-> TypeError: string indices must be integers
bli 2018

6
@bli興味深い、の制限のようstr.formatです。以前は、f文字列は次のような糖衣構文だと思ってstr.format(**locals(), **globals())いましたが、明らかに間違っていました。
ポールパンツァー

4
本番環境では使用しないでください。inspectは赤い旗です。
alexandernst

1
2つの質問があります。本番環境の「危険信号」を検査するのはなぜ例外であるのか、それともより実行可能な回避策があるのでしょうか。そして__slots__、メモリ使用量を減らすためにここを使用することに反対するものはありますか?
ジャブ

21

これは、テンプレートがフォーマットタグを含む静的な文字列であることを意味します

はい、そういうわけで、置換フィールドとを含むリテラルがある.formatので、いつでも呼び出すことformatでフィールドを置き換えることができます。

文字列を新しいf文字列として解釈するようにインタプリタに指示するには、文字列に何かが発生する必要があります。

それが接頭辞f/Fです。これを関数でラップして、呼び出し時に評価を延期することもできますが、もちろん、余分なオーバーヘッドが発生します。

template_a = lambda: f"The current name is {name}"
names = ["foo", "bar"]
for name in names:
    print (template_a())

印刷するもの:

The current name is foo
The current name is bar

しかし、気分が悪く、置換ではグローバル名前空間しか覗くことができないという事実によって制限されます。ローカル名が必要な状況でそれを使用しようとすると、引数として文字列に渡されない限り、惨めに失敗します(これは完全にポイントを打ち負かします)。

文字列を取り込んで、.format(**locals())呼び出しの使用を回避するためにf文字列として解釈する方法はありますか?

関数(制限が含まれています)以外は、違いますので、に固執することもでき.formatます。


おかしい、私はまったく同じスニペットを投稿しました。しかし、スコープの制限のために撤回しました。(forループを関数でラップしてみてください。)
Paul Panzer

@PaulPanzer質問を編集して、再度含めることをお勧めしますか?答えを削除してもかまいません。これはOPの場合の実行可能な代替手段です。すべての場合の実行可能な代替手段ではなく、卑劣です。
ディミトリスファサラキスヒリアード2017

1
いいえ、大丈夫です、それを維持します。私は新しいソリューションにとても満足しています。しかし、あなたがその限界を知っていれば、これは実行可能であるというあなたの指摘を私は見ることができます。投稿に少し警告を追加して、間違った使い方をして誰も足を撃たないようにすることはできますか?
ポールパンツァー

17

文字列を(その全機能を備えた)f文字列として評価する簡潔な方法は、次の関数を使用することです。

def fstr(template):
    return eval(f"f'{template}'")

次に、次のことができます。

template_a = "The current name is {name}"
names = ["foo", "bar"]
for name in names:
    print(fstr(template_a))
# The current name is foo
# The current name is bar

また、他の多くの提案されたソリューションとは対照的に、次のこともできます。

template_b = "The current name is {name.upper() * 2}"
for name in names:
    print(fstr(template_b))
# The current name is FOOFOO
# The current name is BARBAR

4
断然ベストアンサー!彼らがf-stringを導入したとき、どうしてこの単純な実装を組み込み機能として含めなかったのでしょうか。
user32044 5919年

1
いいえ、それはスコープを失います。動作する唯一の理由nameは、グローバルだからです。f-string評価で延期する必要がありますが、クラスFStringは、呼び出し元のローカルとグローバルを調べてスコープ付き引数への参照のリストを作成し、使用時に文字列を評価する必要があります。
エリックアロネスティ

2
@ user3204459:任意の文字列を実行できることは本質的にセキュリティ上の問題であるため、eval()一般的に使用をお勧めしません。
マーティ

2
@martineau evalを使用する必要がないようにPythonの機能である必要があります...さらに、悪意のあるコードを含む中括弧で何でも入れることができるため、f-stringにはeval()と同じリスクがあります。懸念がある場合は、f文字列を使用しないでください
user32044 5919年

2
これはまさに私が探していたものであり、「fstr延期」をダッキングしています。評価は、一般的にfstringを使用するよりも悪くはないようです。どちらも同じ力を持っていると思います:f "{eval( 'print(42) 「)}」
user2692263

12

f-stringは、フォーマットされた文字列を作成するためのより簡潔な方法であり.format(**names)f。に置き換えられます。このような方法で文字列をすぐに評価したくない場合は、f文字列にしないでください。通常の文字列リテラルとして保存し、format後で補間を実行するときに、これまでと同じように呼び出します。

もちろん、代替手段があります eval。をます。

template.txt

f '現在の名前は{name}'

コード:

>>> template_a = open('template.txt').read()
>>> names = 'foo', 'bar'
>>> for name in names:
...     print(eval(template_a))
...
The current name is foo
The current name is bar

しかし、その後、すべてのあなたが行うことができたが置き換えるあるstr.formateval確かにそれの価値がされていません、。format呼び出しでは通常の文字列を使い続けてください。


3
あなたのコードスニペットには何の利点もありません。The current name is {name}つまり、いつでもtemplate.txtファイル内に書き込んでからprint(template_a.format(name=name))(または.format(**locals()))を使用できます。コードは約10文字長くなっていますが、が原因で発生する可能性のあるセキュリティの問題は発生しませんeval
バクリウ2017

@ Bakuriu-はい; 私が言ったように、必要になるまでの評価evalを記述f'{name}'して遅らせるnameことはできますformatが、OPがすでに行っていたように、通常のテンプレート文字列を作成してそれを呼び出すよりも劣ります。
TigerhawkT3 2017

4
「f-stringは、フォーマットされた文字列を作成するためのより簡潔な方法であり、.format(** names)をfに置き換えます。」完全ではありません-それらは異なる構文を使用します。最近確認するのに十分なpython3がありませんが、たとえば、f '{a + b}'は機能すると思いますが、 '{a + b}'。format(a = a、b = b)はKeyErrorを発生させます。.format()はおそらく多くのコンテキストで問題ありませんが、ドロップインの代替ではありません。
philh 2017

2
@philh.formatコメントをサポートできるf文字列と同等ではない例に遭遇したと思いますDNA = "TATTCGCGGAAAATATTTTGA"; fragment = f"{DNA[2:8]}"; failed_fragment = "{DNA[2:8]}".format(**locals())failed_fragment結果を作成しようとするとTypeError: string indices must be integers
bli 2018

12

.formatを使用することは、この質問に対する正しい答えではありません。Pythonのf文字列はstr.format()テンプレートとは大きく異なります...コードやその他の高価な操作を含めることができるため、延期する必要があります。

これは、遅延ロガーの例です。これは、logging.getLoggerの通常のプリアンブルを使用しますが、ログレベルが正しい場合にのみf文字列を解釈する新しい関数を追加します。

log = logging.getLogger(__name__)

def __deferred_flog(log, fstr, level, *args):
    if log.isEnabledFor(level):
        import inspect
        frame = inspect.currentframe().f_back.f_back
        try:
            fstr = 'f"' + fstr + '"'
            log.log(level, eval(fstr, frame.f_globals, frame.f_locals))
        finally:
            del frame
log.fdebug = lambda fstr, *args: __deferred_flog(log, fstr, logging.DEBUG, *args)
log.finfo = lambda fstr, *args: __deferred_flog(log, fstr, logging.INFO, *args)

これには、次のようなことができるという利点が log.fdebug("{obj.dump()}")あります。デバッグが有効になっていない限り、オブジェクトをダンプせずに。

IMHO:これはf-stringのデフォルトの操作であるはずですが、今では手遅れです。F文字列の評価には、大規模で意図しない副作用が発生する可能性があり、それが延期された方法で発生すると、プログラムの実行が変更されます。

f-stringを適切に延期するために、Pythonは動作を明示的に切り替える何らかの方法を必要とします。たぶん文字「g」を使用しますか?;)


この答えに心から同意します。このユースケースは、この質問を検索するときに私が考えていたものです。
ちょうど半分

これが正解です。いくつかのタイミング: %timeit log.finfo(f"{bar=}") 91.9 µs ± 7.45 µs per loop %timeit log.info(f"{bar=}") 56.2 µs ± 630 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each) log.setLevel(logging.CRITICAL) %timeit log.finfo("{bar=}") 575 ns ± 2.9 ns per loop %timeit log.info(f"{bar=}") 480 ns ± 9.37 ns per loop %timeit log.finfo("") 571 ns ± 2.66 ns per loop %timeit log.info(f"") 380 ns ± 0.92 ns per loop %timeit log.info("") 367 ns ± 1.65 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Jaleks

8

あなたが望むものはPythonの拡張機能と見なされているようです。

一方、リンクされたディスカッションから、以下は、使用する必要のない合理的な回避策のようです。 eval()

class FL:
    def __init__(self, func):
        self.func = func
    def __str__(self):
        return self.func()


template_a = FL(lambda: f"The current name, number is {name!r}, {number+1}")
names = "foo", "bar"
numbers = 40, 41
for name, number in zip(names, numbers):
    print(template_a)

出力:

The current name, number is 'foo', 41
The current name, number is 'bar', 42

7

kadee回答に触発されて、以下を使用してdeferred-f-stringクラスを定義できます。

class FStr:
    def __init__(self, s):
        self._s = s
    def __repr__(self):
        return eval(f"f'{self._s}'")

...

template_a = FStr('The current name is {name}')

names = ["foo", "bar"]
for name in names:
    print (template_a)

これはまさに質問が求めたものです


4

または、f文字列を使用せず、フォーマットするだけです。

fun = "The curent name is {name}".format
names = ["foo", "bar"]
for name in names:
    print(fun(name=name))

名前のないバージョン:

fun = "The curent name is {}".format
names = ["foo", "bar"]
for name in names:
    print(fun(name))

これはすべての場合に機能するわけではありません。例:fun = "{DNA[2:8]}".format; DNA = "TATTCGCGGAAAATATTTTGA"; fun(DNA=DNA)。->TypeError: string indices must be integers
bli 2018

ただし、通常の使用では機能しません。回答stackoverflow.com/questions/14072810/

2

どうですか:

s = 'Hi, {foo}!'

s
> 'Hi, {foo}!'

s.format(foo='Bar')
> 'Hi, Bar!'

0

f文字列を使用する提案。テンプレートが発生している論理レベルで評価を行い、それをジェネレーターとして渡します。f-stringsを使用して、選択した任意の時点で巻き戻すことができます

In [46]: names = (i for i in ('The CIO, Reed', 'The homeless guy, Arnot', 'The security guard Spencer'))

In [47]: po = (f'Strangely, {next(names)} has a nice {i}' for i in (" nice house", " fast car", " big boat"))

In [48]: while True:  
...:     try:  
...:         print(next(po))  
...:     except StopIteration:  
...:         break  
...:       
Strangely, The CIO, Reed has a nice  nice house  
Strangely, The homeless guy, Arnot has a nice  fast car  
Strangely, The security guard Spencer has a nice  big boat  
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.