F-dizgilerinin değerlendirilmesi nasıl ertelenir / ertelenir?


102

Bazı dosyalar oluşturmak için şablon dizeleri kullanıyorum ve bu amaç için yeni f-dizelerinin kısa olmasını seviyorum, önceki şablon kodumu şunun gibi bir şeyden azaltmak için:

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

Şimdi bunu doğrudan değişkenleri değiştirerek yapabilirim:

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

Bununla birlikte, bazen şablonun başka bir yerde tanımlanması mantıklıdır - kodda daha yukarıda veya bir dosyadan veya başka bir şeyden içe aktarılmış. Bu, şablonun, içinde biçimlendirme etiketleri bulunan statik bir dize olduğu anlamına gelir. Yorumlayıcıya dizeyi yeni bir f-dizesi olarak yorumlamasını söylemek için dizeye bir şey olması gerekir, ancak böyle bir şey olup olmadığını bilmiyorum.

.format(**locals())Çağrıyı kullanmaktan kaçınmak için bir dizge getirmenin ve onu bir f-dizesi olarak yorumlamanın bir yolu var mı ?

İdeal olarak, böyle kodlayabilmeyi istiyorum ... ( magic_fstring_functionanlamadığım kısım nereden geliyor):

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)

... bu istenen çıktıyla (dosyayı iki kez okumadan):

The current name is foo
The current name is bar

... ancak aldığım gerçek çıktı:

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

5
Bunu bir fip ile yapamazsın . Bir fdizge veri değildir ve kesinlikle bir dizge değildir; bu kod. ( disModülle kontrol edin .) Kodun daha sonra değerlendirilmesini istiyorsanız, bir işlev kullanırsınız.
kindall

12
Bilginize, PEP 501 ilk idealinize yakın bir özellik önerdi, ancak şu anda "[f-dizeleri] ile daha fazla deneyim beklemede ertelendi."
jwodder

Bir şablon statik bir dizedir, ancak bir f-dizesi bir dizge değildir, @kindall'ın dediği gibi bir kod nesnesidir. Bence bir f-string, en sonunda kullanıldığında değil, somutlaştırıldığında (Python 3.6,7'de) değişkenlere anında bağlanır. Yani f-string, .format(**locals())kozmetik olarak daha güzel olsa da , çirkin yaşlılığınızdan daha az kullanışlı olabilir . PEP-501 uygulanana kadar.
smci

Guido bizi kurtardı, ancak PEP 498 gerçekten başarısız oldu . PEP 501 tarafından açıklanan ertelenmiş değerlendirme kesinlikle çekirdek f-string uygulamasında pişirilmiş olmalıydı. Şimdi daha az özellikli, son derece yavaş arasındaki pazarlık kalacaksın str.format()son derece hızlı f-string sözdizimi, bir taraftan ertelenmiş değerlendirmeyi destekleyen yöntem ve bir featureful değil diğer yanda ertelenmiş değerlendirmeyi destekleyen. Öyleyse hala ikisine de ihtiyacımız var ve Python'un hala standart bir dize biçimlendiricisi yok. Xkcd standartlarını ekleyin.
Cecil Curry

Yanıtlar:


26

İşte tam bir "İdeal 2".

Bu bir f-stringi değil - f-stringlerini bile kullanmıyor - ama istendiği gibi yapıyor. Sözdizimi tam olarak belirtildiği gibi. Kullanmadığımız için güvenlik sorunu yok eval().

Küçük bir sınıf kullanır ve __str__otomatik olarak baskı tarafından çağrılan uygular . Sınıfın sınırlı kapsamından kaçmak için inspectmodülü bir çerçeve yukarı atlamak ve çağıranın erişebildiği değişkenleri görmek için kullanırız.

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
Bunu cevap olarak kabul edeceğim, ancak aşırı akıllılık nedeniyle onu kodda asla kullanacağımı sanmıyorum. Belki asla :). Belki python insanları onu PEP 501'in uygulanması için kullanabilir . Sorularım "bu senaryoyu nasıl ele almalıyım" ise cevap ".format () işlevini kullanmaya devam et ve PEP 501'in çözülmesini bekle" olacaktır. Neyin yapılmaması gerektiğini anladığınız için teşekkürler, @PaulPanzer
JDAnders

6
Bu, şablon basit değişken adlarından daha karmaşık bir şey içerdiğinde çalışmaz. Örneğin: template = "The beginning of the name is {name[:4]}"(-> TypeError: string indices must be integers)
bli

6
@bli İlginç, bir sınırlama gibi görünüyor str.format. Eskiden f dizelerinin sadece sözdizimsel bir şeker olduğunu düşünürdüm str.format(**locals(), **globals())ama açıkçası yanılmışım.
Paul Panzer

4
Lütfen bunu üretimde kullanmayın. inspectkırmızı bayraktır.
alexandernst

1
2 sorum var, neden üretim için bir "kırmızı bayrağı" incelemek böyle bir durum bir istisna olur mu yoksa daha uygulanabilir geçici çözümler olur mu? __slots__Azaltılmış bellek kullanımı için buranın kullanımına karşı bir şey var mı?
Jab

21

Bu, şablonun içinde biçimlendirme etiketleri bulunan statik bir dize olduğu anlamına gelir

Evet, tam da bu yüzden değiştirme alanlarına sahip değişmez değerlere sahibiz ve .formatböylece alanları istediğimiz zaman çağırarak değiştirebiliriz format.

Yorumlayıcıya dizeyi yeni bir f-string olarak yorumlamasını söylemek için dizeye bir şey olması gerekir.

Bu önek f/F. Bunu bir işleve sarabilir ve görüşme sırasında değerlendirmeyi erteleyebilirsiniz, ancak elbette bu fazladan ek yüke neden olur:

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

Hangi çıktı:

The current name is foo
The current name is bar

ancak yanlış hissediyor ve değiştirmelerinizde yalnızca genel ad alanına göz atabileceğiniz gerçeğiyle sınırlı. Yerel isimler gerektiren bir durumda onu kullanmaya çalışmak, dizgeye argümanlar olarak geçmedikçe sefil bir şekilde başarısız olacaktır (noktayı tamamen yener).

.format(**locals())Çağrıyı kullanmaktan kaçınmak için bir dizge getirmenin ve onu bir f-dizesi olarak yorumlamanın bir yolu var mı ?

Bir işlev dışında (sınırlamalar dahil), hayır, bu yüzden de bağlı kalabilir .format.


Komik, tam olarak aynı pasajı göndermiştim. Ancak kapsam sınırlamaları nedeniyle geri çektim. (For döngüsünü bir işlevle sarmayı deneyin.)
Paul Panzer

@PaulPanzer soruyu düzenlemek ve yeniden eklemek isteyebilir misiniz? Cevabı silmekten çekinmem. Bu, OP'nin durumu için geçerli bir alternatif, tüm durumlar için geçerli bir alternatif değil , sinsi.
Dimitris Fasarakis Hilliard

1
Hayır, sorun değil, kalsın. Yeni çözümümden çok daha mutluyum. Ancak, sınırlamalarının farkındaysanız, bunun uygulanabilir olduğunu anlıyorum. Kimsenin ayağını yanlış kullanarak vurmaması için gönderinize küçük bir uyarı da ekleyebilirsiniz.
Paul Panzer

17

Bir dizenin bir f-dizesi olarak değerlendirilmesini sağlamanın özlü bir yolu (tüm yetenekleriyle) aşağıdaki işlevi kullanmaktır:

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

O zaman şunları yapabilirsiniz:

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

Ve diğer birçok önerilen çözümün aksine, şunları da yapabilirsiniz:

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
açık ara en iyi cevap! f-dizelerini tanıttıklarında bu basit uygulamayı yerleşik bir özellik olarak nasıl dahil etmediler?
user3204459

1
hayır, bu kapsamını kaybeder. Bunun işe yaramasının tek nedeni nameküresel olmasıdır. f-dizeleri olmalıdır değerlendirmede ertelenmelidir, ancak ihtiyaçlarının FString sınıf arayanlar yerli ve globaller bakarak kapsamlı tartışmalara referans listesi oluşturmak için ... sonra kullanıldığında dize değerlendirir.
Erik Aronesty

2
@ user3204459: Çünkü rasgele dizileri yürütebilmek doğası gereği bir güvenlik tehlikesidir - bu nedenle kullanımı eval()genellikle tavsiye edilmez.
martineau

2
@martineau, eval kullanmanıza gerek kalmaması için python'un bir özelliği olmalıydı ... plus, f-string, kötü amaçlı kod da dahil olmak üzere herhangi bir şeyi süslü parantez içine koyabileceğiniz için eval () ile aynı risklere sahiptir. bir endişe o zaman f dizeleri kullanmayın
user3204459

2
Tam olarak aradığım şey buydu, 'fstr erteleme' için eğiliyor. Eval, genel olarak fstring kullanımından daha kötü görünmüyor, çünkü her ikisi de aynı güce sahip: f "{eval ('print (42) ')} "
user2692263

12

Bir f-string basitçe yerine bir biçimlendirilmiş dize oluşturmanın daha özlü bir yoldur .format(**names)ile f. Bir dizgenin hemen bu şekilde değerlendirilmesini istemiyorsanız, onu bir f-dizesi haline getirmeyin. Sıradan bir dizge olarak kaydedin ve daha formatsonra yaptığınız gibi, enterpolasyonu gerçekleştirmek istediğinizde onu çağırın .

Tabii ki ile bir alternatif var eval.

template.txt:

f'Şu andaki ad {name} '

Kod:

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

Ama sonra bütün Değiştirmek yapmanız başardınız str.formatile evalmutlaka buna değmez, hangi. Bir formatçağrı ile normal dizeleri kullanmaya devam edin .


3
Kod pasajınızda gerçekten bir avantaj göremiyorum. Demek istediğim, her zaman dosyanın The current name is {name}içine yazabilir template.txtve sonra print(template_a.format(name=name))(veya .format(**locals())) kullanabilirsiniz . Kod yaklaşık 10 karakter uzunluğundadır, ancak nedeniyle herhangi bir olası güvenlik sorunu ortaya çıkarmaz eval.
Bakuriu

@Bakuriu - Evet; Dediğim gibi , değerlendirmeyi istenene kadar evalyazmamıza f'{name}'ve ertelememize izin nameverse de, basitçe normal bir şablon dizisi oluşturmak ve ardından formatOP'nin zaten yaptığı gibi onu çağırmak yetersizdir .
TigerhawkT3

4
"Bir f-string, .format (** adlar) 'ı f ile değiştirerek, biçimlendirilmiş bir dize oluşturmanın basit bir yoludur." Tam olarak değil - farklı sözdizimi kullanıyorlar. Kontrol edebileceğim yeni bir python3'üm yok, ancak örneğin f '{a + b}' nin çalıştığına inanıyorum, '{a + b}'. Format (a = a, b = b) KeyError'ı yükseltir . .format () pek çok bağlamda muhtemelen iyidir, ancak bu bir drop-in ikamesi değildir.
philh

2
@philh ben sadece bir örnek karşılaştı düşünüyorum .formatEğer yorum destekleyecek bir f-string, eşdeğer değildir: DNA = "TATTCGCGGAAAATATTTTGA"; fragment = f"{DNA[2:8]}"; failed_fragment = "{DNA[2:8]}".format(**locals()). Yaratma denemesi failed_fragmentsonuçlanır TypeError: string indices must be integers.
bli

12

.Format kullanmak bu sorunun doğru cevabı değildir. Python f dizeleri str.format () şablonlarından çok farklıdır ... kod veya diğer pahalı işlemleri içerebilirler - dolayısıyla erteleme ihtiyacı doğar.

Aşağıda bir ertelenmiş günlük kaydedici örneği verilmiştir. Bu, logging.getLogger'ın normal başlangıcını kullanır, ancak daha sonra f-dizesini yalnızca günlük düzeyi doğruysa yorumlayan yeni işlevler ekler.

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)

Bu, aşağıdaki gibi şeyler yapabilme avantajına sahiptir: log.fdebug("{obj.dump()}").... hata ayıklama etkinleştirilmedikçe nesneyi dökmeden.

IMHO: Bu , f dizelerinin varsayılan işlemi olmalıydı , ancak artık çok geç . F-string değerlendirmesinin büyük ve istenmeyen yan etkileri olabilir ve bunun ertelenmiş bir şekilde gerçekleşmesi programın yürütülmesini değiştirecektir.

F dizelerini düzgün bir şekilde ertelemek için, python'un açıkça davranışı değiştirmenin bir yoluna ihtiyacı olacaktır. Belki 'g' harfini kullanırsınız? ;)

Dize dönüştürücüsünde bir hata varsa, ertelenmiş günlüğün çökmemesi gerektiği belirtildi. Yukarıdaki çözüm, hem de bunu değiştirebilir finally:için except:ve bir sopa log.exceptioniçeride.


1
Bu cevaba gönülden katılıyorum. Bu kullanım örneği, bu soruyu ararken düşündüğüm şeydi.
justhalf

1
Bu doğru cevap. Bazı zamanlar: %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

İstediğiniz şey bir Python geliştirmesi olarak görülüyor .

Bu arada - bağlantılı tartışmadan - aşağıdakiler, kullanımı gerektirmeyen makul bir geçici çözüm gibi görünüyor 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)

Çıktı:

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

7

kadee'nin yanıtından esinlenerek , ertelenmiş-f-string sınıfını tanımlamak için aşağıdakiler kullanılabilir.

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)

tam olarak sorulan soru buydu


4

Ya da belki f dizeleri kullanmayın, sadece biçimlendirin:

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

İsimsiz versiyonda:

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

Bu her durumda çalışmaz. Örnek: fun = "{DNA[2:8]}".format; DNA = "TATTCGCGGAAAATATTTTGA"; fun(DNA=DNA). ->TypeError: string indices must be integers
bli

Ancak normal kullanımda da çalışmaz, lütfen cevaba bakın stackoverflow.com/questions/14072810/…
msztolcman

2

Peki ya:

s = 'Hi, {foo}!'

s
> 'Hi, {foo}!'

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

0

F dizelerini kullanan bir öneri. Değerlendirmenizi şablonlamanın gerçekleştiği mantıksal seviyede yapın ve onu bir jeneratör olarak geçirin. F dizelerini kullanarak, seçtiğiniz herhangi bir noktada gevşetebilirsiniz.

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  
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.