web-dev-qa-db-ja.com

f-stringsの評価を延期/延期できますか?

テンプレート文字列を使用していくつかのファイルを生成していますが、以前のテンプレートコードを次のようなものから減らすために、この目的のための新しい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-stringとして解釈するようにインタープリターに伝えるために、文字列に何かが発生する必要がありますが、そのようなことがあるかどうかはわかりません。

.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}
51
JDAnders

完全な「理想的な2」です。

F-stringではなく、f-stringも使用していません。しかし、それは要求どおりです。指定されたとおりの構文。 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
12
Paul Panzer

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呼び出しで通常の文字列を使用し続けるだけです。

14
TigerhawkT3

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

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
7
kadee

または、f-stringsを使用せずに、単にフォーマットします。

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))
3
msztolcman

.formatを使用することは、この質問に対する正しい答えではありません。 Python f-stringsは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()}") ....のようなことができるという利点があります。

私見:これはf-stringsのデフォルト操作であるべきでしたが、今では手遅れですです。 F文字列の評価は、大規模で意図しない副作用を引き起こす可能性があり、それが遅延して発生すると、プログラムの実行が変更されます。

F文字列を適切に遅延させるには、python動作を明示的に切り替える何らかの方法が必要になります。文字 'g'を使用しますか?)

2
Erik Aronesty

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

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

...

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

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

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

2
user3204459

あなたが望むものはPython enhancement

一方-リンクされた議論から-以下は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
1
martineau

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  
0
Ron Lawhorn