วิธีการเลื่อน / เลื่อนการประเมิน f-strings?


102

ฉันใช้สตริงเทมเพลตเพื่อสร้างไฟล์บางไฟล์และฉันชอบความกระชับของ f-strings ใหม่เพื่อจุดประสงค์นี้เพื่อลดรหัสเทมเพลตก่อนหน้าของฉันจากสิ่งนี้:

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 ใหม่ แต่ฉันไม่รู้ว่ามีสิ่งนั้นหรือไม่

มีวิธีใดบ้างในการนำสตริงเข้ามาและตีความว่าเป็น f-string เพื่อหลีกเลี่ยงการใช้การ.format(**locals())โทร?

ตามหลักการแล้วฉันต้องการโค้ดแบบนี้ ... ( 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)

... ด้วยผลลัพธ์ที่ต้องการนี้ (โดยไม่ต้องอ่านไฟล์สองครั้ง):

The current name is foo
The current name is bar

... แต่ผลลัพธ์จริงที่ฉันได้รับคือ:

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

5
คุณไม่สามารถทำได้ด้วยfสตริง fสตริงไม่ได้เป็นข้อมูลและก็ไม่แน่นอนสตริง; มันคือรหัส (ตรวจสอบด้วยdisโมดูล) หากคุณต้องการให้มีการประเมินโค้ดในภายหลังให้ใช้ฟังก์ชัน
kindall

12
FYI, PEP 501เสนอคุณลักษณะที่ใกล้เคียงกับอุดมคติแรกของคุณ แต่ขณะนี้ "รอการตัดบัญชีรอประสบการณ์เพิ่มเติมกับ [f-strings]"
jwodder

เทมเพลตเป็นสตริงแบบคงที่ แต่ f-string ไม่ใช่สตริงเป็นอ็อบเจ็กต์โค้ดดังที่ @kindall กล่าว ฉันคิดว่า f-string ถูกผูกไว้กับตัวแปรทันทีเมื่อมีการสร้างอินสแตนซ์ (ใน Python 3.6,7) ไม่ใช่เมื่อมันถูกใช้ในที่สุด ดังนั้น f-string อาจมีประโยชน์น้อยกว่าของเก่าที่น่าเกลียดของคุณ.format(**locals())แม้ว่าจะสวยกว่าก็ตาม จนกว่าจะใช้ PEP-501
smci

กุยช่วยเรา แต่PEP 498 จริงๆเรียบร้อยมัน การประเมินผลรอการตัดบัญชีที่อธิบายโดยPEP 501ควรได้รับการรวมเข้ากับการใช้งานหลัก f-string ตอนนี้เรากำลังทะเลาะกันอยู่ระหว่างวิธีการที่มีคุณลักษณะน้อยและช้ามากstr.format()ซึ่งสนับสนุนการประเมินผลรอการตัดบัญชีในมือข้างหนึ่งและไวยากรณ์ f-string ที่มีคุณลักษณะและรวดเร็วมากขึ้นซึ่งไม่สนับสนุนการประเมินผลที่รอการตัดบัญชีในอีกด้านหนึ่ง ดังนั้นเรายังคงต้องการทั้งสองอย่างและ Python ยังไม่มีตัวจัดรูปแบบสตริงมาตรฐาน แทรกมส์มาตรฐาน xkcd
Cecil Curry

คำตอบ:


26

นี่คือ "อุดมคติ 2" ฉบับสมบูรณ์

ไม่ใช่ f-string - ไม่ใช้ f-strings ด้วยซ้ำ แต่เป็นตามที่ร้องขอ ไวยากรณ์ตรงตามที่ระบุ eval()ไม่มีการรักษาความปลอดภัยปวดหัวเนื่องจากเราไม่ได้ใช้

ใช้คลาสเล็ก ๆ น้อย ๆ และใช้__str__ซึ่งเรียกโดยอัตโนมัติโดยการพิมพ์ เพื่อหลีกเลี่ยงขอบเขตที่ จำกัด ของคลาสเราใช้inspectโมดูลเพื่อกระโดดขึ้นหนึ่งเฟรมและดูตัวแปรที่ผู้โทรเข้าถึงได้

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
ฉันจะยอมรับสิ่งนี้เป็นคำตอบแม้ว่าฉันจะไม่คิดว่าฉันจะเคยใช้มันในโค้ดเพราะความฉลาดสุด ๆ อาจจะไม่เคย :) บางทีคนหลามสามารถใช้สำหรับการดำเนินงานของPEP 501 หากคำถามของฉันคือ "ฉันจะจัดการสถานการณ์นี้อย่างไร" คำตอบคือ "เพียงแค่ใช้ฟังก์ชัน. format () ต่อไปและรอให้ PEP 501 แก้ไข" ขอบคุณที่หาวิธีทำในสิ่งที่ไม่ควรทำ @PaulPanzer
JDAnders

6
สิ่งนี้ใช้ไม่ได้เมื่อเทมเพลตมีสิ่งที่ซับซ้อนกว่าชื่อตัวแปรธรรมดา ตัวอย่างเช่น: template = "The beginning of the name is {name[:4]}"(-> TypeError: string indices must be integers)
bli

6
@bli น่าสนใจดูเหมือนจะเป็นข้อ จำกัด ของstr.format. ฉันเคยคิดว่า f-strings เป็นเพียงแค่น้ำตาลในการสังเคราะห์str.format(**locals(), **globals())เท่านั้น แต่เห็นได้ชัดว่าฉันคิดผิด
Paul Panzer

4
โปรดอย่าใช้สิ่งนั้นในการผลิต inspectเป็นธงสีแดง
อเล็กซานเดอร์

1
ฉันมีคำถาม 2 ข้อทำไมการตรวจสอบ "ธงสีแดง" สำหรับการใช้งานจริงกรณีเช่นนี้จะเป็นข้อยกเว้นหรือจะมีวิธีแก้ปัญหาที่เป็นไปได้มากกว่านี้ และมีบางอย่างต่อต้านการใช้__slots__ที่นี่สำหรับการใช้หน่วยความจำที่ลดลงหรือไม่?
Jab

21

ซึ่งหมายความว่าเทมเพลตเป็นสตริงแบบคงที่ที่มีแท็กการจัดรูปแบบอยู่ในนั้น

ใช่นั่นเป็นเหตุผลที่เรามีตัวอักษรพร้อมฟิลด์ทดแทน.formatดังนั้นเราจึงสามารถแทนที่ฟิลด์เมื่อใดก็ได้ที่เราต้องการโดยเรียกformatมัน

จะต้องมีบางอย่างเกิดขึ้นกับสตริงเพื่อบอกให้ล่ามแปลความหมายของสตริงเป็นสตริง f ใหม่

f/Fนั่นเป็นคำนำหน้า คุณสามารถรวมไว้ในฟังก์ชั่นและเลื่อนการประเมินผลในช่วงเวลาโทรออกได้ แต่แน่นอนว่าจะต้องเสียค่าใช้จ่ายเพิ่มเติม:

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

ซึ่งพิมพ์ออกมา:

The current name is foo
The current name is bar

แต่รู้สึกผิดและถูก จำกัด ด้วยความจริงที่ว่าคุณสามารถมองไปที่เนมสเปซส่วนกลางในการแทนที่ของคุณเท่านั้น การพยายามใช้ในสถานการณ์ที่ต้องใช้ชื่อท้องถิ่นจะล้มเหลวอย่างน่าสังเวชเว้นแต่จะส่งผ่านไปยังสตริงเป็นอาร์กิวเมนต์ (ซึ่งตรงประเด็นโดยสิ้นเชิง)

มีวิธีใดบ้างในการนำสตริงเข้ามาและตีความว่าเป็น f-string เพื่อหลีกเลี่ยงการใช้การ.format(**locals())โทร?

นอกเหนือจากฟังก์ชั่น (รวมข้อ จำกัด ) ไม่ดังนั้นอาจติดด้วย.formatเช่นกัน


ตลกดีฉันเคยโพสต์ตัวอย่างข้อมูลเดียวกัน แต่ฉันถอยกลับเนื่องจากข้อ จำกัด ของขอบเขต (ลองตัด for loop ในฟังก์ชัน)
Paul Panzer

@PaulPanzer คุณอาจต้องการแก้ไขคำถามและรวมเข้าไปใหม่หรือไม่? ฉันไม่รังเกียจที่จะลบคำตอบ นี่เป็นทางเลือกที่เป็นไปได้สำหรับกรณีของ OP ไม่ใช่ทางเลือกที่เป็นไปได้สำหรับทุกกรณีมันเป็นการส่อเสียด
Dimitris Fasarakis Hilliard

1
ไม่เป็นไรเก็บไว้ ฉันมีความสุขมากขึ้นกับโซลูชันใหม่ของฉัน แต่ฉันเห็นประเด็นของคุณว่าสิ่งนี้ใช้ได้ถ้าคุณตระหนักถึงข้อ จำกัด ของมัน บางทีคุณอาจเพิ่มคำเตือนเล็กน้อยในโพสต์ของคุณเพื่อให้ไม่มีใครสามารถยิงเท้าของพวกเขาโดยใช้มันผิด?
Paul Panzer

17

วิธีที่กระชับในการประเมินสตริงเป็น 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

4
คำตอบที่ดีที่สุด! พวกเขาไม่รวมการใช้งานแบบธรรมดานี้เป็นคุณสมบัติในตัวได้อย่างไรเมื่อพวกเขาแนะนำ f-strings?
user3204459

1
ไม่นั่นเสียขอบเขต เหตุผลเดียวที่ใช้งานได้เป็นเพราะnameทั่วโลก f-strings ควรเลื่อนออกไปในการประเมินผล แต่คลาส FString จำเป็นต้องสร้างรายการการอ้างอิงไปยังอาร์กิวเมนต์ที่กำหนดขอบเขตโดยดูที่ผู้โทรในพื้นที่และคนทั่วโลก ... จากนั้นประเมินสตริงเมื่อใช้
Erik Aronesty

2
@ user3204459: เพราะความสามารถในการดำเนินการโดยพลสตริงเป็นอย่างโดยเนื้อแท้เป็นอันตรายต่อความปลอดภัย - ซึ่งเป็นเหตุผลที่ใช้eval()เป็นกำลังใจโดยทั่วไป
martineau

2
@martineau ควรเป็นคุณสมบัติของ python ดังนั้นคุณจึงไม่จำเป็นต้องใช้ eval ... บวกกับ f-string มีความเสี่ยงเช่นเดียวกับ eval () เนื่องจากคุณสามารถใส่อะไรก็ได้ในวงเล็บปีกการวมถึงโค้ดที่เป็นอันตรายดังนั้นหากเป็นเช่นนั้น กังวลแล้วอย่าใช้ f-strings
user3204459

2
นี่คือสิ่งที่ฉันกำลังมองหาอย่างแท้จริงโดยใช้คำว่า 'fstr เลื่อนออกไป' Eval ดูเหมือนจะไม่แย่ไปกว่าการใช้ fstrings โดยทั่วไปเท่าที่จะเป็นไปได้ฉันเดาว่าทั้งคู่มีพลังเท่ากัน: f "{eval ('print (42) ')} "
user2692263

12

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โทร


3
ฉันไม่เห็นข้อได้เปรียบในข้อมูลโค้ดของคุณเลย ฉันหมายความว่าคุณสามารถเขียนเพียงแค่The current name is {name}ภายในtemplate.txtไฟล์จากนั้นใช้print(template_a.format(name=name))(หรือ.format(**locals())) รหัสเป็นเรื่องเกี่ยวกับ 10 ตัวอักษรอีกต่อไป แต่ก็ไม่ได้แนะนำปัญหาด้านความปลอดภัยที่เป็นไปได้ใด ๆ evalอันเนื่องจาก
Bakuriu

@ บาคุริว - ครับ; อย่างที่ฉันพูดแม้ว่าevalจะช่วยให้เราสามารถเขียนf'{name}'และชะลอการประเมินผลnameจนกว่าจะต้องการ แต่ก็ด้อยกว่าเพียงแค่สร้างสตริงเทมเพลตปกติแล้วเรียกformatใช้ตามที่ OP กำลังทำอยู่
TigerhawkT3

4
"f-string เป็นเพียงวิธีการสร้างสตริงที่จัดรูปแบบให้กระชับยิ่งขึ้นโดยแทนที่. format (** names) ด้วย f" ไม่มาก - พวกเขาใช้ไวยากรณ์ที่แตกต่างกัน ฉันไม่มี python3 ล่าสุดเพียงพอที่จะตรวจสอบด้วย แต่ตัวอย่างเช่นฉันเชื่อว่า f '{a + b}' ใช้งานได้ขณะที่ '{a + b}'. format (a = a, b = b) เพิ่ม KeyError . .format () อาจใช้ได้ดีในหลาย ๆ บริบท แต่ไม่ใช่การแทนที่แบบดรอปอิน
philh

2
@philh ฉันคิดว่าฉันเพิ่งพบตัวอย่างเช่นในกรณีที่.formatจะไม่เทียบเท่ากับ DNA = "TATTCGCGGAAAATATTTTGA"; fragment = f"{DNA[2:8]}"; failed_fragment = "{DNA[2:8]}".format(**locals())F-สตริงที่สามารถรองรับคุณแสดงความคิดเห็น: ความพยายามในการสร้างfailed_fragmentผลลัพธ์ในTypeError: string indices must be integers.
bli

12

การใช้. format ไม่ใช่คำตอบที่ถูกต้องสำหรับคำถามนี้ Python f-strings แตกต่างจากเทมเพลต str.format () อย่างมาก ... สามารถมีโค้ดหรือการดำเนินการอื่น ๆ ที่มีราคาแพงได้ดังนั้นจึงจำเป็นต้องมีการเลื่อนออกไป

นี่คือตัวอย่างของคนตัดไม้รอการตัดบัญชี สิ่งนี้ใช้คำนำหน้าปกติของ logging.getLogger แต่จะเพิ่มฟังก์ชันใหม่ที่แปลความหมายของ f-string ก็ต่อเมื่อระดับการบันทึกถูกต้อง

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()}").... โดยไม่ทิ้งวัตถุเว้นแต่จะเปิดใช้งานการดีบัก

IMHO: นี่ควรเป็นค่าเริ่มต้นการดำเนินงานของ F-สตริง แต่ตอนนี้มันสายเกินไป การประเมิน F-string อาจมีผลข้างเคียงจำนวนมากและไม่ได้ตั้งใจและการที่สิ่งนั้นเกิดขึ้นในลักษณะที่รอการตัดบัญชีจะเปลี่ยนการทำงานของโปรแกรม

ในการสร้าง f-strings ให้ถูกเลื่อนออกไปอย่างถูกต้อง python จำเป็นต้องมีวิธีการเปลี่ยนพฤติกรรมอย่างชัดเจน อาจจะใช้ตัวอักษร 'g'? ;)

มีการชี้ให้เห็นว่าการบันทึกที่รอการตัดบัญชีไม่ควรผิดพลาดหากมีข้อบกพร่องในตัวแปลงสตริง การแก้ปัญหาดังกล่าวข้างต้นสามารถทำเช่นนี้ได้เป็นอย่างดีเปลี่ยนfinally:ไปexcept:และติดlog.exceptionอยู่ในนั้น


1
เห็นด้วยกับคำตอบนี้ด้วยใจจริง กรณีการใช้งานนี้เป็นสิ่งที่ฉันคิดเมื่อค้นหาคำถามนี้
justhalf

1
นี่คือคำตอบที่ถูกต้อง บางช่วงเวลา: %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

สิ่งที่คุณต้องการดูเหมือนจะถือว่าเป็นการเพิ่มประสิทธิภาพของ Pythonการเพิ่มประสิทธิภาพ

ในขณะเดียวกัน - จากการสนทนาที่เชื่อมโยง - สิ่งต่อไปนี้ดูเหมือนว่าจะเป็นวิธีแก้ปัญหาที่สมเหตุสมผลที่ไม่จำเป็นต้องใช้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

7

แรงบันดาลใจจากคำตอบโดย kadeeสิ่งต่อไปนี้สามารถใช้เพื่อกำหนดคลาส f-string รอการตัดบัญชี

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)

ซึ่งตรงกับคำถามที่ถาม


4

หรืออาจจะไม่ใช้ 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))

วิธีนี้ใช้ไม่ได้ในทุกกรณี ตัวอย่าง: fun = "{DNA[2:8]}".format; DNA = "TATTCGCGGAAAATATTTTGA"; fun(DNA=DNA). ->TypeError: string indices must be integers
bli

แต่มันก็ใช้ไม่ได้เช่นกันในการใช้งานปกติโปรดดูที่คำตอบstackoverflow.com/questions/14072810/…
msztolcman


0

คำแนะนำที่ใช้ f-strings ทำการประเมินผลของคุณในระดับตรรกะที่เกิดเทมเพลตและส่งต่อเป็นตัวสร้าง คุณสามารถผ่อนคลายได้ทุกเมื่อโดยใช้ 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  
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.