テンプレート文字列を使用していくつかのファイルを生成していますが、以前のテンプレートコードを次のようなものから減らすために、この目的のための新しい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}
完全な「理想的な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
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.format
でeval
を使用します。 format
呼び出しで通常の文字列を使用し続けるだけです。
文字列を(完全な機能を備えた)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
または、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))
.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'を使用しますか?)
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)
それはまさに質問が求めたものです
あなたが望むものは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
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