Pythonの文字列フォーマットに新風 PEP 750テンプレート文字列がついに登場
引用元:https://news.ycombinator.com/item?id=43647716
言語によって文字列フォーマットのアプローチが全然違うの、マジで面白いよね。
Goにはそういうのいっぱいあるよねー。
文字列補完に関する議論を見ると、考えなしに捨てたってのはヒドすぎだって。mini-proposalsも書いてるし[1]。間違ってると思うけど、ちゃんと考えてるよ。Goのgenericsとかerror handlingと同じで、完璧主義なんだよね。Javaと違って、Goチームは自信がないと実験的な機能すらリリースしないし。
>String interpolationとfmt.Sprintfには大きな違いがあるって?
”ローカライズに関しても違いがあるよね。fmt.Sprintfはローカライズできるけど、String interpolationはできない。PEP 750でローカライズの議論がないのも気になるなー。”
プロの言語設計者ならもっとできるはず。Goに文字列補完が欲しいって人がいるなら、他の言語がどう実装してるか調べるのが当然じゃん?fmt.Sprintで似たことができるって言うけど、それ言う必要ある?genericsと同じで、ergonomicsが大事なんだよ。fmt.Sprintが良いならもっと使われてるはずだよね?
>Proposing syntax as noisy as […] is just another sign the designers don’t get it.
”\(…)``の代わりに
{…}`を使うのが嫌なの?文字数が多いから?それとも括弧をネストする必要があるから?”
Value typesはどうなった?難しいのはわかるけど、Javaが出てから新しい言語をいくつか学んで捨ててるのに、まだリリースされてないって。
Go(lang)の拒否は理解できる。フォーマット関数が文字列内で任意のコードを実行するのは悪夢だよね。Log4jが良い例。拒否の例は、文字列内のコードを文字列外の関数で修正できることを示してる。安全で、コンパイラやプログラマにも優しい。文字列にevalが欲しいなら別だけど。(evalはscripted言語でしか見たことないなー。Goはバイナリだし。)
いや、フォーマット関数は“arbitrarily execute code”しないよ。f/t stringはsyntaxであってruntimeじゃない。”Hello ” + subject + ”!”じゃなくてf”Hello {subject}!って書くだけ。subjectは普通のコードの式だよ。query([”SELECT * FROM account WHERE id = ”, ” AND active”], [id])じゃなくてquery(t”SELECT * FROM account WHERE id = {id} AND active”)って書くんだ。むしろinjectionしにくくなるよ。
返信のcontextをもう一度読んでください。俺が返信してるのは拒否されたGolangの提案だよ。PythonのPEPとか、他のmagic variablesを解決する文字列じゃないよ!
提案内容を見る限り、評価は含まれてないみたいね。Pythonと同じで、構文糖として文字列リテラルを解析して、明示的なフォーマットコードに変換するっぽい。GitHubでの議論は誤解が多いみたいだね。
それなら、例の意図を誤解してたかも。でも、理解しにくいのは、メンテナンスの負担が増えて、言語が複雑になるってことだよね。
機能がない場合の回避策が、今回のケースみたいにメンテナンスの負担が大きい場合、学習で回避できないよね。
Goなら、事前にエイリアス操作を計算して、最終的な値を参照するみたいな感じかな。例えば、>https://github.com/golang/go/issues/34174”を参考にすると、変数が多い場合に{variable}形式だと分かりにくいかも。%{variable}の方がparserで扱いやすい気がする。
提案では、Swift、Kotlin、C#みたいな構文糖が引用されてるよ。提案はそれと同じことを意図してたみたい。
この例のおかげで、sqlc/typed sqlみたいなもの(Pythonでもよく使う)のメカニズムになるってことが分かったよ。transpilationなしで、SQLに対するAPIラッパーも不要になるかも。lintersは必要だけど、typeddb.SelectActiveAccount(I’d)みたいなクエリのコストをDBで確認できるのはいいね。
PEPによると、新しいTemplate
型を返すから、型チェックとかduck typingで、Template以外の入力を拒否できるはず。
型は違うよ。static typecheckingかruntimeで確認できるはず。
多くの言語で、f-strings(またはf-stringみたいなもの)は、文字列リテラルでのみサポートされてて、ユーザーが指定した文字列ではサポートされてない。コンパイル時に、文字列連結に変換できる。
混乱があるといけないから言うけど、Pythonのf-stringは文字列リテラル限定だよ。fプレフィックスは新しいデータ型を作るわけじゃなくて、コンパイル時に解析されて、文字列連結コードに書き換えられるんだ。
いやいや、あるって。PythonのソースコードはJavaとかC#みたいにVM用のバイトコードに翻訳されて、デフォルトで.pycファイルにキャッシュされるんだよ。違うのは、ソースコードファイルの実行を指示すると、バイトコード解釈の前にコンパイルが自動的に行われるってこと。 あるよ。ソースコードをバイトコードにコンパイルするときにね。 僕の返信は、親投稿のGolangの拒否された機能リクエストの具体的な例に対するものだよ。その提案を読んで。 その提案は、ユーザーが提供した文字列でコードを実行することについては何も言ってないよ。コンパイラによって処理される文字列リテラルについてのみ述べている(その時点で、ユーザーが提供した文字列は利用できない)。 ユーザーが提供した文字列のリスクって何?サイズはわかるでしょ?他に何が心配なの? >フォーマット文字列内からコードを任意に実行するフォーマット関数 いや、逆だよ。f-stringsは、大まかに言ってeval(つまり、どんな些細な使用においてもエラーとなる可能性のある、非衛生的な文字列連結)であって、t-stringsは単なる代替の式構文であり、引数を逆参照さえしない。 f-stringsはevalじゃないよ。動的じゃない。他のすべての式と同じように実行される式にすぎない。 そうそう、そしてttyにprint()する以外のことをすると、エスケープ/インジェクション攻撃になる。 これって any_func(attacker_provided) と何が違うのさ? >Go の開発者は、この問題を5分も検討せずに、考えなしに捨て去ったみたい。 Ruby には変なデコレータなしで、めっちゃ美しい文字列フォーマットがあるじゃん。 D言語で文字列のインターポレーションが大炎上したことがあったんだよ。Walter はシンプルなのを求めてたけど、コミュニティはPythonのテンプレートみたいなのを求めてた(少なくともPEPの冒頭を読んだ感じだと)。結局 Walter はコミュニティの要望を受け入れたんだ。 マジで荒らしじゃないんだけどさ、Python の f-string が printf 形式のフォーマット文字列よりも優れてる点がマジで分かんないんだよね。ググってみたけど、イマイチ納得できるものがなかったんだ。 Go/C 形式のフォーマットオプションはこんな感じだよ: _log(f”My variable is {x + y}”) の方が、_log(“My variable is {}”.format(x+y)) や _log(“My variable is {z}”.format(z=x+y)) より、ずっと自然に読める気がするんだよね。大したことじゃないけど。 Python に全然詳しくないんだけど、最初の例をローカライズ(翻訳)するにはどうすればいいの? 文字列のインターポレーションって1970年代から実装があるのに、なんでこんなに苦労して戦わないといけないんだろ。PEP 498 (fstrings) でさえ戦いだったし。 Java は STR. “Hello {this.user.firstname()}, how are you? Nick Humrichだよ。PEP 501の書き換えを手伝って、t-stringsの基礎を作ったんだ。今回のPEPの著者じゃないけど、このPEPのことはよく知ってるから、質問があったら遠慮なくどうぞ!ついに承認されてマジ嬉しい!PEP 501に取り組み始めてから4年も経つんだぜ。 文字列の新しいタイプが増えて、言語がどんどん複雑になってるって心配する声があるよね。PEPsを書いて議論する人たちは、Pythonのエキスパートだから、初心者や入門者の視点が抜け落ちてるんじゃないかって。このバイアスを防ぐ仕組みってあるのかな? PEPの議論は全部公開フォーラムで行われてるから、誰でも意見を言えるんだ。もちろん、エキスパートが参加しやすいのは確かだけどね。プロセスは、初心者よりもエキスパートのためになってる気がする。最近は、難易度を下げるための取り組みもされてるよ。新しいPEPには「初心者にどう教えるか」ってセクションが必要になったんだ。https://peps.python.org/pep-0750/#how-to-teach-this 「初心者にどう教えるか」って質問は、ユーザータイプについて考える良いきっかけになるよね。言語全体の状況を調査するテストとか、プロダクト調査みたいなものがあれば良いけど、ユーザーエクスペリエンスの話だから難しい問題だよね。 Python開発者の平均レベルだとPEPを知らない人が多いと思うよ。議論がオープンなのは良いことだけど、平均的な開発者が何を求めてるかなんて誰も知らないんじゃない?PythonだろうとJavaだろうと、こだわりがないって人もいるし。 t-stringsとf-stringsの両方がある理由がわからないなー。この2つの違いが、新しいプログラマーにとっては混乱の元になると思うんだよね。理想のPythonは、どっちか片方だけで良いのに。 f-stringsはすぐに文字列になるけど、t-stringsはオブジェクトを導入して、ライブラリがテンプレート文字列に対してカスタムロジックやフォーマットをできるようにするんだ。例えば、SQLに挿入するときにユーザー入力を適切にエスケープするとかね。f-stringsじゃ強制できないから。 >ensure user input is properly escaped when inserting into sql あなたの心配はわかるよ。PEPにも書いてある通り、t”foo” は文字列じゃないんだ。f”foo” は文字列だけどね。typecheckerを使えば型エラーになるし、無視したらランタイムエラーになるよ。t”foo” には str() メソッドすらないから。 > ”t-strings are not strings” シンプソンの個別ストリンゲット! 全部わかる気がするけど、プログラミング入門の学生には絶対無理だろなー。もう構文で頭いっぱいなのに。 まず、 型はそんな重要じゃない。__call__や参照はstring型を返すし、fとtは使う側からしたら同じように扱える。例えば、全部のfをtに置き換えて、ちょっと修正すれば移行できると思う。時間はかかるけどね。 f-stringの使用頻度が減って、削除しても多くのプロジェクトに影響が出なくなるまで数年待つしかないね。 それはない。 人気が続くならそれでいい。大事なのは、誰にとっても複雑じゃないこと。 これで文字列のフォーマット方法が4つになるね。古いのは消えないから。 こういう時にこそ、意見が強いlinterが役立つんだよね。後方互換性を維持しつつ、徐々に“良い”バージョンに移行させる。 printf形式のフォーマット(”foo %s” % ”bar”)は、そろそろ引退してもいい気がする(ショートカットとして便利だから、絶対なくならないだろうけど)。 printf形式のフォーマット(”foo %s” % “bar”)は、もうそろそろ引退してもいいんじゃないかなーって思うけど、便利なショートカットだから、たぶん絶対なくならないよね。あと、ユーザーが提供するデータに対して、ある程度安全なのはこれだけなんだよね。 Pythonで%を使ったフォーマットって使ったことないんだけど、 後方互換性がないなんてマジかよ?! ES6のテンプレート文字列の方がf-stringより先だったと思うんだけど。もしそうなら、なんでPythonはf-stringなんて明らかに劣ってるデザインを採用しちゃったの?今や文字列補完システムが5つもあるじゃん(%、.format、string.Template、f-string、t-string)。 ES6のテンプレート文字列は知らないんだけど、なんでf-stringより優れてるの?f-stringは普通に使えるし、使いやすいと思うんだけど。特に普段C++を使ってるから、文字列操作にはあんまり期待してないのかも(笑)。 ありがとう!遅延評価の選択肢はどんなのが検討されて、どんな理由で却下されたのか知りたいな。遅延評価のメリットは、遅延評価クラスを定義するコードを少し節約できることじゃなくて、APIを標準化して、誰でもコードを読めるようにすることだと思うんだよね。あと、LLMのプロンプトテンプレート(LangChainとか)は設計に影響を与えた? ロギングのパフォーマンスが少し向上するかもしれないって聞いたけど、まだよくわかってないんだよね。 T-stringsはf-stringsと同じで、すぐに評価されるから、メリットはないよ。 PEP 501、お疲れ様!ちょっと的外れな質問かもしれないけど、PEP 750にPEP 292が全然言及されてないのはなんで? Python 3.14のリリースが近づいてるから、文字列フォーマットの仕組み(たくさんあるよね)についてドキュメントを書きたいと思ってるんだ。それぞれが少しずつ重複してるけど、それぞれ独自の使い道があるんだよね。PEP 292は廃止されることはないし、 Pythonってまた新しいstring literalが必要なの?Templateを標準ライブラリに追加するのは良いと思うけど、syntax supportまではいらないんじゃない?t”blah blah”ってTemplate(“blah blah”, context)のaliasみたいなもんでしょ? syntax supportはマジで必要だよ。staticな部分とdynamicな部分(user inputっぽいとこ)を区別しないといけないから。syntax levelでしか無理じゃん?%みたいにplaceholder使う手もあるけど、f-stringがある今、そっちの方が良くない?f-stringのsyntax highlightingとか使いやすさ最高だし。一番安全で使いやすいのが理想だよね。 だからcontext argumentを指定したんだって。Template(”{name} {address}”, dict(name = …, address = …))って、t”{name} {address}”と全く同じじゃん?変数はlocal scopeから取ってくるとして。 それだとinterpolated valueの名前を3回も書くことになるじゃん(template stringで1回、dictのkeyで1回、actual valueで1回)。できるけど、t-stringの方が使いやすいし、readableだと思うよ。 そりゃsyntax supportがあれば使いやすいよ。でもt-stringってnicheなuse case向けだし、f-stringの代わりにはなれないじゃん。stringじゃないし。input sanitizingにも不十分そう。pre-compiled statements/templates作れないし。 SQL queriesとかHTMLとかでinjection attacksを防ぐのってnicheなuse caseなの? そうだよ。しかもpre-compilationが必要だけど、t-stringはsupportしてないし。 pre-compilationはいらないよ。値を後で渡すtemplateをcompileするって意味ならね。t-stringはlibraryが値にtransformationをかけられるようにするもの。escapingとか、parameterized queryにseparate valuesとして渡すとか。HTML escapingとかSQL queriesのparameterizingが最初のuse caseだよ。それってnicheじゃないと思う。user inputのsanitizeはめっちゃcommonだし、言語とlibraryが簡単にcorrectにできるようにするのは良いことだよ。[1]: Templateって名前が良くない。interpolated valueのintermediate representationなんだから。 webがnicheってこと?web以外ではuntrusted inputがexecutable codeになる可能性はないからsanitationはいらないってことね。web developmentにはsafeでfastなtext-generation solutionを提供するtemplating libraryが2ダースもあるじゃん。t-stringより comprehensiveだよ。Pre-compilationっていうのは、templateをfirst compileして、then renderingするときにtemplateにvaluesをsupplyするってこと。t-stringがcreatedされた時にvaluesはboundされるから無理だね。 たとえwebがnicheだとしても(そうは思わないけど)、inputがmaliciousじゃないって信じても(webじゃないapplicationでは必ずしもtrueじゃない)、special charactersがbugを引き起こす心配はあるよ。Web appsだけがdatabase使ったり、user inputを含むsyntaxでstring生成するわけじゃないし。 マジ最高じゃん!f-strings大好きで、コード内の他の文字列補完全部f-stringsに置き換えたんだけど、評価を遅らせられないって重大な問題があったんだよね。もっとコメントを表示(1)
SyntaxError
はコンパイル中に発生する可能性があるから、他の例外とは根本的に違うんだ。exec
/eval
とかモジュールのインポートみたいに、明示的に発生させるか、別のコードコンパイルを明示的に呼び出す場合にのみランタイムで発生する。
文字列内の既存の/将来の(遅延/遅延評価)文字列リテラルを参照する可能性についてではなく、文字列内の任意の関数を文字通り評価するフォーマット文字列についてなんだ。
一方、Goが提供する現在の解決策(fmt.Sprintf)は、ユーザーが提供したformat Stringをサポートするもの。
この機能が拒否された理由は、あなたが述べたこととは関係ないよ。Ian Lance Taylorは単に「fmt.Sprintfを呼び出すよりも大きな利点はないようだ」と言っただけ。彼は、文字列補間にパフォーマンス上の利点があることは認めたけど、fmt.Sprintf/fmt.Sprintよりもユーザビリティの向上はないと考えている。Goでは他の言語に比べて、コンパイラに新しい機能を追加することを嫌うんだ。
つまり、テンプレートってこと? mustacheのサポートのためにgoを使うことはないな。
any_func(f”{attacker_provided}”) <=> eval(attacker_provided)は、セキュリティ/正確性の観点から。
リンク先の issue は2019年にオープンして、2023年にコメントなしでクローズされたけど、2022年までは活発に議論されてたんだね。
誰か pros と cons を簡単に教えてくんない?
fmt.Sprintf(”This house is %s tall”, measurements(2.5))
fmt.Sprint(”This house is ”, measurements(2.5), ” tall”)
Python の f-string だと:
f”This house is {measurements(2.5)} tall”
Sprintf は引数の対応を見るのが面倒。Sprint は読みやすいけど、書くのが大変。f-string は人間工学的に優れてるから、よく使う処理では差が出るよ。
It’s {tempC}°C today!” を推奨してるみたいだけど、マジ? scala の s”Hello ${this.user.firstname()}, how are you?
It’s ${tempC}°C today!” と比べて STR. “” って何なの?もっとコメントを表示(2)
例えば、dict[k] += 1をする前にdictを初期化しないといけない理由がわからない人もいる。k in dictを確認してdict[k] = 0にする必要があるって知ってても、+= が dict[k] = dict[k] + 1 に展開されるって理解してないんだよね。
昔はそれを願ってたし、JSのテンプレート文字列とそれに関するライブラリで実現したんだ。価値があるかどうかは別として(あなたはPEPを完成させたんだから、私より信頼性があるけど)、考えが変わったんだよね。それは間違いだと思う。
構文的にはナイスだけど、SQLクエリとパラメータの分離っていう現実を曖昧にするし、SQLの上に抽象化を構築するけど、それは見た目も抽象化っぽくない。そして何より、間違ったやり方と見た目が似すぎてる。安全なSQLのやり方と安全じゃないやり方の違いが、たった1文字とPythonの文字列フォーマットの理解度だけで決まるなら…ヤバいことが起きるよ。1人プロジェクトならなんとかなるけど、経験や役職が違う人がいる大きなプロジェクトだと、きっとうまくいかない。可愛いけどね。SQLクエリには向いてないと思う。
PEPの要約の冒頭に「t-strings は文字列ではない」って書くべきだね。
t-stringって、文字列じゃないものに対しては残念なネーミングだよね。
https://www。youtube。com/watch?v=7qNj-QFZbewf”something”
はstr
型だけど、t”something”
はstring。templatelib。Template
型なんだよね。t-stringを使うと、コード内で文字列のどの部分が動的に置換されたか判別できる。
”foo %s” % ”bar”
”foo {}”。format(”bar”)
bar = ”bar”; f”foo {bar}”
bar = ”bar”; t”foo {bar}” # 特別な機能付き!
初心者は全部知っておく必要があるから大変だけど。
他のやつは、少なくとも同じフォーマット文字列の構文に基づいているし。
”foo {}”。format(”bar”)は、明らかに”f-stringを使えばいい”ケース。でも、フォーマットが遠く離れた場所で行われる場合は別。そんな時はt-stringを使えば”いい”?ファイルからフォーマット文字列を読み込む場合はどうする?t-stringとf-stringは構文要素だから、動的な場合は使えない!
だから、以下のユースケースがある。
- printf形式:C言語っぽい文字列フォーマットが必要
- 。format:フォーマット対象のデータが近くにないからf-stringが使えない。t-stringは動的な場合に使えない。
- f-string:テンプレートとデータが同じ場所にあるから、文字列連結したい(よくある!)
- t-string:テンプレートとデータが同じ場所にあるけど、特別なロジックで結果を組み立てたい(文字列じゃなくてもいい!)
この2つは構文だから、最初の2つのユースケースを全部カバーするのは難しい。
でも、特定のユースケースなら、4つの中で最適なものが存在するはず。もっとコメントを表示(3)
format
よりも安全な理由ってなに?
今、ロギングには2つの方法があるよね。
1. logger.debug(f'Processing {x}')
- 見た目はいいけど、logging level > logging.DEBUG
でも評価されちゃう。
2. logger.debug('Processing %s', x)
- 必要になるまで評価されない。
t-stringsの場合はどうなるのかな?何かメリットはある?flufl.i18n
みたいな強力なライブラリで使われてるよ。
Compilingに関しては、t-stringは基本的にそうやって動くけど、python interpreterがcompilationするんだよ。t-stringをparseするとき、(byte)codeにcompileして、evaluatedされたときにscope内のexpressionsからTemplate objectを生成する。functionでt-stringをwrapして、parametersをargumentsとして取ればtemplateになるよ。>two dozen templating libraries that offer much more comprehensive safe and fast text-generation solutions than what t-strings do”
t-stringがあればlibraryはsaferになるし(f-stringで誤ってinterpolateしなくなる)、fasterになるかも(python interpreterがstringをsplitする作業をするから)。t-stringはlibraryをreplaceするわけじゃなくて、betterにするんだよ。
たとえば、>template = ’Hello、{name}’
>template.format(name=’Bob’)
>’Hello、Bob’
みたいに書けるけど、今まではf-stringsのフォーマットをその場で評価せずに使う方法がなかったんだよね。
>template = f’Hello、{name}’
>Traceback (most recent call last):
> File ”
> template = f’Hello、{name}’
> ^^
>NameError: name ’name’ is not defined
f-stringsがほとんどどこでも使えるのに、str.formatも使わざるを得ない状況が地味にウザかったんだよね。