Làm thế nào để hoãn / trì hoãn việc đánh giá chuỗi f?


102

Tôi đang sử dụng chuỗi mẫu để tạo một số tệp và tôi thích sự ngắn gọn của chuỗi f mới cho mục đích này, để giảm mã mẫu trước đó của tôi khỏi một cái gì đó như thế này:

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

Bây giờ tôi có thể làm điều này, trực tiếp thay thế các biến:

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

Tuy nhiên, đôi khi có ý nghĩa nếu mẫu được xác định ở nơi khác - cao hơn trong mã hoặc được nhập từ tệp hoặc thứ gì đó. Điều này có nghĩa là mẫu là một chuỗi tĩnh có các thẻ định dạng trong đó. Điều gì đó sẽ xảy ra với chuỗi để yêu cầu trình thông dịch giải thích chuỗi đó là một chuỗi f mới, nhưng tôi không biết liệu có điều đó xảy ra hay không.

Có cách nào để đưa vào một chuỗi và nó được hiểu là chuỗi f để tránh sử dụng .format(**locals())cuộc gọi không?

Lý tưởng nhất là tôi muốn có thể viết mã như thế này ... ( magic_fstring_functionphần tôi không hiểu nằm ở đâu):

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)

... với đầu ra mong muốn này (mà không cần đọc tệp hai lần):

The current name is foo
The current name is bar

... nhưng sản lượng thực tế tôi nhận được là:

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

5
Bạn không thể làm điều đó với một fchuỗi. Một fchuỗi không phải là dữ liệu, và nó chắc chắn không phải là một chuỗi; nó là mã. (Kiểm tra nó với dismô-đun.) Nếu bạn muốn mã được đánh giá sau này, bạn sử dụng một hàm.
kindall

12
FYI, PEP 501 đã đề xuất một tính năng gần với lý tưởng đầu tiên của bạn, nhưng nó hiện "được hoãn lại trong khi chờ trải nghiệm thêm với [f-string]."
jwodder

Mẫu là một chuỗi tĩnh, nhưng chuỗi f không phải là một chuỗi, nó là một đối tượng mã, như @kindall đã nói. Tôi nghĩ rằng một chuỗi f bị ràng buộc với các biến ngay lập tức khi nó được khởi tạo (trong Python 3.6,7), chứ không phải khi nó được sử dụng cuối cùng. Vì vậy, f-string có thể ít hữu ích hơn cái cũ xấu xí của bạn .format(**locals()), mặc dù đẹp hơn về mặt thẩm mỹ. Cho đến khi PEP-501 được triển khai.
smci

Guido cứu chúng tôi, nhưng PEP 498 thực sự đã làm hỏng nó . Đánh giá trì hoãn được mô tả bởi PEP 501 hoàn toàn nên được đưa vào triển khai chuỗi f cốt lõi. Bây giờ chúng ta còn mặc cả giữa một mặt là str.format()phương pháp ít tính năng hơn, cực kỳ chậm hỗ trợ đánh giá trì hoãn và mặt khác là cú pháp chuỗi f cực kỳ nhanh, tính năng hơn, không hỗ trợ đánh giá chậm lại. Vì vậy, chúng ta vẫn cần cả hai và Python vẫn không có trình định dạng chuỗi tiêu chuẩn. Chèn meme tiêu chuẩn xkcd.
Cecil Curry vào

Câu trả lời:


26

Đây là một "Lý tưởng 2" hoàn chỉnh.

Nó không phải là chuỗi f — thậm chí không sử dụng chuỗi f — nhưng nó thực hiện theo yêu cầu. Cú pháp chính xác như được chỉ định. Không phải đau đầu về bảo mật vì chúng tôi không sử dụng eval().

Nó sử dụng một lớp nhỏ và thực hiện __str__được tự động gọi bằng print. Để thoát khỏi phạm vi giới hạn của lớp, chúng tôi sử dụng inspectmô-đun để nhảy một khung lên và xem các biến mà người gọi có quyền truy cập.

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
Tôi sẽ chấp nhận đây là câu trả lời, mặc dù tôi không nghĩ rằng tôi sẽ thực sự sử dụng nó trong mã vì sự thông minh cực kỳ. Chà có lẽ không bao giờ :). Có lẽ mọi người có thể sử dụng python để triển khai PEP 501 . Nếu câu hỏi của tôi là "tôi nên xử lý tình huống này như thế nào" thì câu trả lời sẽ là "chỉ cần tiếp tục sử dụng hàm .format () và đợi PEP 501 giải quyết." Cảm ơn bạn đã tìm ra cách làm những gì không nên làm, @PaulPanzer
JDAnders

6
Điều này không hoạt động khi mẫu bao gồm một cái gì đó phức tạp hơn tên biến đơn giản. Ví dụ: template = "The beginning of the name is {name[:4]}"(-> TypeError: string indices must be integers)
Bli

6
@bli Thú vị, có vẻ là một hạn chế của str.format. Tôi từng nghĩ chuỗi f chỉ là đường cú pháp cho một cái gì đó như thế str.format(**locals(), **globals())nhưng rõ ràng là tôi đã sai.
Paul Panzer

4
Vui lòng không sử dụng nó trong sản xuất. inspectlà một lá cờ đỏ.
alexandernst

1
Tôi có 2 câu hỏi, tại sao việc kiểm tra "cờ đỏ" đối với sản xuất sẽ là một trường hợp như trường hợp này là ngoại lệ hoặc sẽ có cách giải quyết khả thi hơn? Và có điều gì đó chống lại việc sử dụng __slots__ở đây để giảm mức sử dụng bộ nhớ không?
Jab

21

Điều này có nghĩa là mẫu là một chuỗi tĩnh có các thẻ định dạng trong đó

Vâng, đó chính xác là lý do tại sao chúng ta có các ký tự với các trường thay thế và .format, vì vậy chúng tôi có thể thay thế các trường bất cứ khi nào chúng tôi muốn bằng cách gọi formatnó.

Điều gì đó sẽ xảy ra với chuỗi để yêu cầu trình thông dịch giải thích chuỗi đó là một chuỗi f mới

Đó là tiền tố f/F. Bạn có thể gói nó trong một chức năng và hoãn đánh giá trong thời gian gọi nhưng tất nhiên điều đó sẽ phát sinh thêm chi phí:

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

Cái nào in ra:

The current name is foo
The current name is bar

nhưng cảm thấy sai và bị hạn chế bởi thực tế là bạn chỉ có thể xem qua không gian tên chung trong phần thay thế của mình. Cố gắng sử dụng nó trong tình huống yêu cầu tên cục bộ sẽ thất bại thảm hại trừ khi được chuyển đến chuỗi dưới dạng đối số (hoàn toàn đánh bại điểm).

Có cách nào để đưa vào một chuỗi và nó được hiểu là chuỗi f để tránh sử dụng .format(**locals())cuộc gọi không?

Ngoài một chức năng (bao gồm các giới hạn), nope, do đó cũng có thể gắn bó với .format.


Thật buồn cười, chính xác là tôi đã đăng cùng một đoạn mã. Nhưng tôi đã rút lại vì giới hạn phạm vi. (Hãy thử gói các vòng lặp for trong một hàm.)
Paul Panzer

@PaulPanzer, bạn có thể muốn chỉnh sửa câu hỏi và bao gồm lại nó không? Tôi sẽ không phiền khi xóa câu trả lời. Đây là một sự thay thế khả thi cho trường hợp của OP, Nó không phải là một sự thay thế khả thi cho mọi trường hợp, nó đang lén lút.
Dimitris Fasarakis Hilliard

1
Không, không sao đâu, giữ nó đi. Tôi hạnh phúc hơn nhiều với giải pháp mới của mình. Nhưng tôi có thể thấy quan điểm của bạn rằng điều này là khả thi nếu bạn nhận thức được những hạn chế của nó. Có lẽ bạn có thể thêm một cảnh báo nhỏ vào bài đăng của mình để không ai có thể bắn vào chân họ do sử dụng sai?
Paul Panzer

17

Một cách ngắn gọn để có một chuỗi được đánh giá là một chuỗi f (với đầy đủ các tính năng của nó) là sử dụng hàm sau:

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

Sau đó, bạn có thể làm:

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

Và, ngược lại với nhiều giải pháp được đề xuất khác, bạn cũng có thể làm:

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
cho đến nay câu trả lời tốt nhất! Làm thế nào mà họ không bao gồm triển khai đơn giản này như là một tính năng tích hợp khi họ giới thiệu chuỗi f?
user3204459

1
nope, điều đó làm mất phạm vi. lý do duy nhất hoạt động là vì namelà toàn cầu. f-string nên được hoãn lại trong quá trình đánh giá, nhưng lớp FString cần tạo một danh sách các tham chiếu đến các đối số trong phạm vi bằng cách xem xét các địa phương và hình cầu ... và sau đó đánh giá chuỗi khi được sử dụng.
Erik Aronesty

2
@ user3204459: Bởi vì khả năng thực thi các chuỗi tùy ý vốn dĩ là một mối nguy hiểm về bảo mật - đó là lý do tại sao việc sử dụng eval()thường không được khuyến khích.
martineau

2
@martineau lẽ ra nó phải là một tính năng của python để bạn không cần sử dụng eval ... ngoài ra, f-string có cùng rủi ro như eval () vì bạn có thể đặt bất kỳ thứ gì trong dấu ngoặc nhọn bao gồm cả mã độc hại, vì vậy nếu đó là một mối quan tâm sau đó không sử dụng e-strings
user3204459

2
Đây chính xác là những gì tôi đang tìm kiếm, bỏ qua "fstr hoãn". Eval có vẻ không tệ hơn việc sử dụng các chuỗi nói chung, vì chúng, tôi đoán, cả hai đều sở hữu sức mạnh như nhau: f "{eval ('print (42) ')} "
user2692263

12

Chuỗi f chỉ đơn giản là một cách ngắn gọn hơn để tạo một chuỗi được định dạng, thay thế .format(**names)bằng f. Nếu bạn không muốn một chuỗi được đánh giá ngay lập tức theo cách như vậy, đừng đặt nó thành chuỗi f. Lưu nó dưới dạng một chuỗi ký tự thông thường, và sau đó gọi formatnó sau khi bạn muốn thực hiện nội suy, như bạn đã làm.

Tất nhiên, có một thay thế với eval.

template.txt:

f'Tên hiện tại là {name} '

Mã:

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

Nhưng sau đó tất cả những gì bạn phải làm là thay thế str.formatbằng eval, điều này chắc chắn không đáng. Chỉ cần tiếp tục sử dụng các chuỗi thông thường với một formatcuộc gọi.


3
Tôi thực sự không thấy lợi thế nào trong đoạn mã của bạn. Ý tôi là, bạn luôn có thể ghi ngay The current name is {name}bên trong template.txttệp và sau đó sử dụng print(template_a.format(name=name))(hoặc .format(**locals())). Mã dài hơn khoảng 10 ký tự, nhưng nó không gây ra bất kỳ vấn đề bảo mật nào có thể xảy ra eval.
Bakuriu

@Bakuriu - Có; như tôi đã nói, mặc dù evalcho phép chúng tôi viết f'{name}'và trì hoãn việc đánh giá namecho đến khi mong muốn, nhưng nó kém hơn so với việc chỉ cần tạo một chuỗi mẫu thông thường và sau đó gọi formatnó, như OP đã làm.
TigerhawkT3

4
"Chuỗi f chỉ đơn giản là một cách ngắn gọn hơn để tạo một chuỗi được định dạng, thay thế .format (** names) bằng f." Không hoàn toàn - chúng sử dụng các cú pháp khác nhau. Tôi không có python3 đủ gần đây để kiểm tra, nhưng ví dụ: tôi tin rằng f '{a + b}' hoạt động, trong khi '{a + b}'. Format (a = a, b = b) làm tăng KeyError . .format () có thể tốt trong nhiều ngữ cảnh, nhưng nó không phải là một sự thay thế thả xuống.
philh

2
@philh Tôi nghĩ rằng tôi chỉ gặp một ví dụ nơi .formatkhông phải là tương đương với một e-chuỗi, có thể hỗ trợ bạn nhận xét: DNA = "TATTCGCGGAAAATATTTTGA"; fragment = f"{DNA[2:8]}"; failed_fragment = "{DNA[2:8]}".format(**locals()). Nỗ lực tạo ra failed_fragmentkết quả trong TypeError: string indices must be integers.
Bli

12

Sử dụng .format không phải là một câu trả lời chính xác cho câu hỏi này. Chuỗi f trong Python rất khác với các mẫu str.format () ... chúng có thể chứa mã hoặc các hoạt động đắt tiền khác - do đó cần phải hoãn lại.

Đây là một ví dụ về trình ghi nhật ký trả chậm. Điều này sử dụng phần mở đầu bình thường của logging.getLogger, nhưng sau đó thêm các hàm mới chỉ diễn giải chuỗi f nếu mức nhật ký là chính xác.

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)

Điều này có lợi thế là có thể làm những việc như: log.fdebug("{obj.dump()}").... mà không cần kết xuất đối tượng trừ khi gỡ lỗi được bật.

IMHO: Đây lẽ ra là hoạt động mặc định của f-string, tuy nhiên bây giờ đã quá muộn . Đánh giá chuỗi F có thể có các tác dụng phụ lớn và không mong muốn, và nếu điều đó xảy ra một cách trì hoãn sẽ thay đổi việc thực thi chương trình.

Để làm cho chuỗi f bị trì hoãn đúng cách, python sẽ cần một số cách chuyển đổi hành vi rõ ràng. Có thể sử dụng chữ cái 'g'? ;)

Nó đã được chỉ ra rằng ghi nhật ký hoãn lại sẽ không bị lỗi nếu có lỗi trong trình chuyển đổi chuỗi. Giải pháp trên cũng có thể làm điều này, thay đổi finally:thành except:, và gắn log.exceptionvào đó.


1
Đồng ý với câu trả lời này hết lòng. Trường hợp sử dụng này là những gì tôi đã nghĩ đến khi tìm kiếm câu hỏi này.
justhalf

1
Đây là câu trả lời chính xác. Một số thời gian: %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

Những gì bạn muốn dường như được coi là một cải tiến của Python .

Trong khi đó - từ cuộc thảo luận được liên kết - thì điều sau có vẻ như đó sẽ là một cách giải quyết hợp lý mà không yêu cầu sử dụng 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)

Đầu ra:

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

7

lấy cảm hứng từ câu trả lời của kadee , phần sau có thể được sử dụng để xác định một lớp f-string hoãn lại.

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)

đó chính xác là những gì câu hỏi yêu cầu


4

Hoặc có thể không sử dụng f-string, chỉ định dạng:

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

Trong phiên bản không có tên:

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

Điều này không hoạt động trong mọi trường hợp. Ví dụ: fun = "{DNA[2:8]}".format; DNA = "TATTCGCGGAAAATATTTTGA"; fun(DNA=DNA). ->TypeError: string indices must be integers
Bli

Nhưng nó không hoạt động cũng trong sử dụng bình thường, hãy nhìn vào câu trả lời stackoverflow.com/questions/14072810/...
msztolcman

2

Làm thế nào về:

s = 'Hi, {foo}!'

s
> 'Hi, {foo}!'

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

0

Một gợi ý sử dụng chuỗi f. Thực hiện đánh giá của bạn ở cấp độ logic nơi diễn ra quá trình tạo khuôn mẫu và chuyển nó như một bộ tạo. Bạn có thể thư giãn nó bất cứ lúc nào bạn chọn, sử dụng 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  
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.