Bagaimana cara menunda / menunda evaluasi f-string?


105

Saya menggunakan string template untuk menghasilkan beberapa file dan saya menyukai keringkasan f-string baru untuk tujuan ini, untuk mengurangi kode template saya sebelumnya dari sesuatu seperti ini:

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

Sekarang saya bisa melakukan ini, langsung mengganti variabel:

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

Namun, terkadang masuk akal untuk menetapkan template di tempat lain - lebih tinggi dalam kode, atau diimpor dari file atau sesuatu. Artinya, template adalah string statis dengan tag pemformatan di dalamnya. Sesuatu harus terjadi pada string untuk memberi tahu penerjemah untuk menafsirkan string sebagai f-string baru, tetapi saya tidak tahu apakah ada hal seperti itu.

Apakah ada cara untuk memasukkan string dan menafsirkannya sebagai f-string untuk menghindari penggunaan .format(**locals())panggilan?

Idealnya saya ingin dapat membuat kode seperti ini ... (di magic_fstring_functionmana bagian yang tidak saya mengerti masuk):

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)

... dengan keluaran yang diinginkan ini (tanpa membaca file dua kali):

The current name is foo
The current name is bar

... tapi keluaran sebenarnya yang saya dapatkan adalah:

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

5
Anda tidak dapat melakukannya dengan ftali. Sebuah fstring tidak data, dan itu tentu tidak string; itu kode. (Periksa dengan dismodul.) Jika Anda ingin kode dievaluasi di lain waktu, Anda menggunakan fungsi.
kindall

12
FYI, PEP 501 mengusulkan fitur yang mendekati ideal pertama Anda, tetapi saat ini "ditangguhkan sambil menunggu pengalaman lebih lanjut dengan [f-string]."
jwodder

Template adalah string statis, tetapi f-string bukanlah string, ini adalah objek kode, seperti yang dikatakan @kindall. Saya pikir f-string terikat terhadap variabel segera ketika dibuat (dengan Python 3.6,7), bukan ketika akhirnya digunakan. Jadi f-string mungkin kurang berguna dibandingkan f-string lama Anda .format(**locals()), meskipun secara kosmetik lebih bagus. Sampai PEP-501 diimplementasikan.
smci

Guido menyelamatkan kita, tapi PEP 498 benar-benar mengacaukannya . Evaluasi yang ditangguhkan yang dijelaskan oleh PEP 501 seharusnya sudah dimasukkan ke dalam implementasi f-string inti. Sekarang kita dibiarkan tawar-menawar antara metode yang kurang fitur, sangat lambat str.format()yang mendukung evaluasi yang ditangguhkan di satu sisi dan sintaks f-string yang lebih lengkap dan sangat cepat yang tidak mendukung evaluasi yang ditangguhkan di sisi lain. Jadi kita masih membutuhkan keduanya dan Python masih belum memiliki pemformat string standar. Masukkan meme standar xkcd.
Cecil Curry

Jawaban:


26

Berikut adalah "Ideal 2" yang lengkap.

Ini bukan f-string — bahkan tidak menggunakan f-string — tapi seperti yang diminta. Sintaks persis seperti yang ditentukan. Tidak ada sakit kepala keamanan karena kami tidak menggunakan eval().

Ini menggunakan kelas kecil dan implementasi __str__yang secara otomatis dipanggil dengan print. Untuk keluar dari ruang lingkup kelas yang terbatas, kita menggunakan inspectmodul untuk melompat satu frame ke atas dan melihat variabel yang dapat diakses pemanggil.

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

14
Saya akan menerima ini sebagai jawabannya, meskipun saya rasa saya tidak akan pernah benar-benar menggunakannya dalam kode karena kepintarannya yang ekstrim. Yah mungkin tidak pernah :). Mungkin orang python bisa menggunakannya untuk implementasi PEP 501 . Jika pertanyaan saya adalah "bagaimana saya harus menangani skenario ini" jawabannya adalah "tetap gunakan fungsi .format () dan tunggu sampai PEP 501 selesai." Terima kasih telah mencari tahu bagaimana melakukan apa yang tidak boleh dilakukan, @PaulPanzer
JDAnders

7
Ini tidak berfungsi saat templat menyertakan sesuatu yang lebih kompleks daripada nama variabel sederhana. Misalnya: template = "The beginning of the name is {name[:4]}"(-> TypeError: string indices must be integers)
bli

6
@bli Menarik, tampaknya menjadi batasan str.format. Dulu saya berpikir f-string hanyalah gula sintaksis untuk sesuatu seperti str.format(**locals(), **globals())tapi jelas saya salah.
Paul Panzer

4
Tolong jangan gunakan itu dalam produksi. inspectadalah bendera merah.
alexandernst

1
Saya memiliki 2 pertanyaan, mengapa memeriksa "bendera merah" untuk produksi, apakah kasus seperti ini merupakan pengecualian atau apakah ada solusi yang lebih layak? Dan apakah ada sesuatu yang menentang penggunaan di __slots__sini untuk penggunaan memori yang berkurang?
Jab

22

Artinya, template adalah string statis dengan tag pemformatan di dalamnya

Ya, itulah mengapa kami memiliki literal dengan bidang pengganti dan .format, sehingga kami dapat mengganti bidang kapan pun kami suka dengan memanggilnya format.

Sesuatu harus terjadi pada string untuk memberi tahu penerjemah untuk menafsirkan string sebagai f-string baru

Itu awalannya f/F. Anda dapat menggabungkannya dalam sebuah fungsi dan menunda evaluasi selama waktu panggilan, tetapi tentu saja itu menimbulkan overhead tambahan:

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

Yang mencetak:

The current name is foo
The current name is bar

tetapi terasa salah dan dibatasi oleh fakta bahwa Anda hanya dapat mengintip namespace global di pengganti Anda. Mencoba menggunakannya dalam situasi yang membutuhkan nama lokal akan gagal total kecuali diteruskan ke string sebagai argumen (yang benar-benar mengalahkan intinya).

Apakah ada cara untuk memasukkan string dan menafsirkannya sebagai f-string untuk menghindari penggunaan .format(**locals())panggilan?

Selain fungsi (termasuk batasan), tidak, jadi sebaiknya tetap gunakan .format.


Lucu, saya telah memposting cuplikan yang sama persis. Tapi saya mencabutnya karena keterbatasan cakupan. (Coba gabungkan loop for dalam sebuah fungsi.)
Paul Panzer

@PaulPanzer apakah Anda mungkin ingin mengedit pertanyaan dan memasukkannya kembali? Saya tidak keberatan menghapus jawabannya. Ini adalah alternatif yang layak untuk kasus OP, Ini bukan alternatif yang layak untuk semua kasus, ini licik.
Dimitris Fasarakis Hilliard

1
Tidak, tidak apa-apa, simpanlah. Saya jauh lebih bahagia dengan solusi baru saya. Tetapi saya dapat memahami maksud Anda bahwa yang satu ini layak jika Anda menyadari keterbatasannya. Mungkin Anda bisa menambahkan sedikit peringatan ke posting Anda sehingga tidak ada yang bisa bertindak dengan salah?
Paul Panzer

18

Cara ringkas agar string dievaluasi sebagai f-string (dengan kemampuan penuhnya) adalah menggunakan fungsi berikut:

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

Kemudian Anda dapat melakukan:

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

Dan, berbeda dengan banyak solusi lain yang diusulkan, Anda juga dapat melakukan:

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
sejauh ini jawaban terbaik! bagaimana mereka tidak menyertakan penerapan sederhana ini sebagai fitur bawaan saat mereka memperkenalkan f-string?
user3204459

1
tidak, itu kehilangan ruang lingkup. satu-satunya alasan yang berhasil adalah karena namebersifat global. f-string harus ditangguhkan dalam evaluasi, tetapi kelas FString perlu membuat daftar referensi ke argumen terbatas dengan melihat pemanggil lokal dan global ... dan kemudian mengevaluasi string tersebut saat digunakan.
Erik Aronesty

2
@ user3204459: Karena dapat mengeksekusi string arbitrer pada dasarnya merupakan bahaya keamanan - itulah sebabnya penggunaan eval()umumnya tidak disarankan.
martineau

2
@martineau seharusnya fitur python sehingga Anda tidak perlu menggunakan eval ... plus, f-string memiliki risiko yang sama dengan eval () karena Anda dapat memasukkan apa pun ke dalam tanda kurung kurawal termasuk kode berbahaya jadi jika itu kekhawatiran maka jangan gunakan f-string
user3204459

2
Ini persis seperti yang saya cari, merunduk untuk 'fstr postpone ". Eval tampaknya tidak lebih buruk daripada penggunaan fstring pada umumnya, karena keduanya, menurut saya, keduanya memiliki kekuatan yang sama: f" {eval (' print (42) ')} "
user2692263

12

F-string hanyalah cara yang lebih ringkas untuk membuat string berformat, menggantikan .format(**names)dengan f. Jika Anda tidak ingin string segera dievaluasi dengan cara seperti itu, jangan menjadikannya f-string. Simpan sebagai string literal biasa, dan kemudian panggil formatnanti ketika Anda ingin melakukan interpolasi, seperti yang telah Anda lakukan.

Tentu saja, ada alternatif lain dengan eval.

template.txt:

f'Nama saat ini adalah {name} '

Kode:

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

Tetapi yang berhasil Anda lakukan hanyalah mengganti str.formatdengan eval, yang tentunya tidak sepadan. Tetap gunakan string biasa dengan formatpanggilan.


3
Saya benar-benar tidak melihat keuntungan dalam cuplikan kode Anda. Maksud saya, Anda selalu dapat menulis The current name is {name}di dalam template.txtfile dan kemudian menggunakan print(template_a.format(name=name))(atau .format(**locals())). Kode ini lebih panjang sekitar 10 karakter, tetapi tidak menimbulkan kemungkinan masalah keamanan karena eval.
Bakuriu

@ Bakuriu - Ya; seperti yang saya katakan, meskipun evalmemungkinkan kita untuk menulis f'{name}'dan menunda evaluasi namesampai diinginkan, itu inferior untuk hanya membuat string template biasa dan kemudian memanggilnya format, seperti yang sudah dilakukan OP.
TigerhawkT3

4
"F-string hanyalah cara yang lebih ringkas untuk membuat string berformat, menggantikan .format (** nama) dengan f." Kurang tepat - mereka menggunakan sintaks yang berbeda. Saya tidak memiliki python3 yang cukup baru untuk diperiksa, tetapi misalnya saya yakin f '{a + b}' berfungsi, sementara '{a + b}'. Format (a = a, b = b) memunculkan KeyError . .format () mungkin baik-baik saja dalam banyak konteks, tetapi ini bukan pengganti drop-in.
philh

2
@philh Saya pikir saya hanya ditemui contoh di mana .formattidak setara dengan f-string, yang dapat mendukung Anda komentar: DNA = "TATTCGCGGAAAATATTTTGA"; fragment = f"{DNA[2:8]}"; failed_fragment = "{DNA[2:8]}".format(**locals()). Upaya untuk menciptakan failed_fragmenthasil dalam TypeError: string indices must be integers.
bli

12

Menggunakan .format bukanlah jawaban yang benar untuk pertanyaan ini. F-string Python sangat berbeda dari template str.format () ... mereka dapat berisi kode atau operasi mahal lainnya - oleh karena itu perlu penangguhan.

Berikut ini contoh logger yang ditangguhkan. Ini menggunakan pembukaan normal logging.getLogger, tetapi kemudian menambahkan fungsi baru yang menafsirkan f-string hanya jika level log sudah benar.

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)

Ini memiliki keuntungan karena dapat melakukan hal-hal seperti: log.fdebug("{obj.dump()}").... tanpa membuang objek kecuali proses debug diaktifkan.

IMHO: Ini seharusnya menjadi default operasi f-string, namun sekarang sudah terlambat . Evaluasi F-string dapat memiliki efek samping yang sangat besar dan tidak diinginkan, dan jika hal itu terjadi secara tertunda akan mengubah eksekusi program.

Untuk membuat f-string ditangguhkan dengan benar, python memerlukan beberapa cara untuk mengubah perilaku secara eksplisit. Mungkin menggunakan huruf 'g'? ;)

Telah ditunjukkan bahwa logging yang ditangguhkan seharusnya tidak macet jika ada bug di konverter string. Solusi di atas dapat melakukan ini juga, mengubah finally:menjadi except:, dan menempelkan a log.exceptiondi sana.


1
Setuju dengan jawaban ini dengan sepenuh hati. Kasus penggunaan inilah yang saya pikirkan saat menelusuri pertanyaan ini.
setengah dari

1
Ini jawaban yang benar. Beberapa waktu: %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

Apa yang Anda inginkan tampaknya dianggap sebagai peningkatan Python .

Sementara itu - dari diskusi yang ditautkan - berikut ini sepertinya merupakan solusi yang masuk akal yang tidak memerlukan penggunaan 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)

Keluaran:

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

7

terinspirasi oleh jawaban oleh kadee , berikut ini dapat digunakan untuk mendefinisikan kelas f-string yang ditangguhkan.

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)

itulah pertanyaan yang ditanyakan


4

Atau mungkin tidak menggunakan f-string, cukup format:

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

Dalam versi tanpa nama:

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

Ini tidak bekerja di semua kasus. Contoh: fun = "{DNA[2:8]}".format; DNA = "TATTCGCGGAAAATATTTTGA"; fun(DNA=DNA). ->TypeError: string indices must be integers
bli

Tetapi itu juga tidak berfungsi dalam penggunaan normal, silakan lihat jawaban stackoverflow.com/questions/14072810/…
msztolcman

2

Bagaimana tentang:

s = 'Hi, {foo}!'

s
> 'Hi, {foo}!'

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

0

Saran yang menggunakan f-string. Lakukan evaluasi Anda pada tingkat logis di mana template terjadi dan berikan sebagai generator. Anda dapat melepasnya kapan saja, menggunakan f-string

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  
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.