f- 문자열 평가를 연기 / 연기하는 방법은 무엇입니까?


103

일부 파일을 생성하기 위해 템플릿 문자열을 사용하고 있으며 다음과 같이 이전 템플릿 코드를 줄이기 위해 이러한 목적으로 새 f- 문자열의 간결함을 좋아합니다.

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- 문자열로 해석하도록 지시하기 위해 문자열에 어떤 일이 발생해야하지만 그런 것이 있는지 모르겠습니다.

문자열을 가져와 .format(**locals())호출을 사용하지 않도록 f- 문자열로 해석하는 방법이 있습니까?

이상적으로는 다음과 같이 코딩 할 수 있기를 원합니다 ... ( 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
참고로, PEP 501 은 첫 번째 이상에 가까운 기능을 제안했지만 현재는 "[f-strings]에 대한 추가 경험이 지연 될 때까지 연기되었습니다."
jwodder

템플릿은 정적 문자열이지만 @kindall이 말했듯이 f- 문자열은 문자열이 아니라 코드 객체입니다. 나는 f- 문자열이 인스턴스화 될 때 (Python 3.6,7에서) 즉시 변수에 바인딩된다고 생각합니다. 따라서 f-string은 .format(**locals())외관상 더 좋지만 못생긴 낡은 것보다 덜 유용 할 수 있습니다 . PEP-501이 구현 될 때까지.
smci 2010 년

Guido는 우리를 구했지만 PEP 498은 실제로 실패했습니다 . PEP 501에 설명 된 지연된 평가는 절대적으로 핵심 f-string 구현에 포함되어야합니다. 이제 우리는 str.format()한편으로는 지연된 평가를 지원 하는 덜 기능적이고 매우 느린 방법과 다른 한편으로는 지연된 평가를 지원 하지 않는 더 기능적이고 매우 빠른 f- 문자열 구문 사이에서 흥정을하고 있습니다. 그래서 우리는 여전히 둘 다 필요하고 파이썬에는 여전히 표준 문자열 포맷터가 없습니다. xkcd 표준 meme을 삽입합니다.
Cecil Curry

답변:


26

여기에 완전한 "이상적인 2"가 있습니다.

f- 문자열이 아닙니다. f- 문자열도 사용하지 않지만 요청한대로 수행합니다. 지정된 구문과 정확히 일치합니다. 을 사용하지 않기 때문에 보안 문제가 없습니다 eval().

작은 클래스를 사용하고 __str__print에 의해 자동으로 호출되는 구현 합니다. 제한된 클래스 범위를 벗어나기 위해 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

14
나는 이것을 답으로 받아 들일 것이다. 비록 극도의 영리함 때문에 실제로 코드에서 그것을 사용하지는 않을 것이라고 생각한다. 글쎄 절대 :). 아마도 파이썬 사람들은 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- 문자열이 같은 것에 대한 통사론 적 설탕이라고 생각 str.format(**locals(), **globals())했지만 분명히 내가 틀렸다.
Paul Panzer

4
프로덕션에서 사용하지 마십시오. inspect붉은 깃발입니다.
alexandernst

1
두 가지 질문이 있습니다. 생산을 위해 "위험 신호"를 검사하는 이유는 예외일까요? 아니면 더 실행 가능한 해결 방법이 있을까요? 그리고 __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

그러나 잘못된 느낌이 들며 대체 항목에서 전역 네임 스페이스 만 엿볼 수 있다는 사실로 제한됩니다. 로컬 이름이 필요한 상황에서 사용하려고 시도하면 문자열에 인수로 전달되지 않는 한 비참하게 실패합니다 (이는 완전히 요점을 능가합니다).

문자열을 가져와 .format(**locals())호출을 사용하지 않도록 f- 문자열로 해석하는 방법이 있습니까?

기능 (제한 사항 포함) 외에는 .format.


재미있게도 나는 정확히 동일한 스 니펫을 게시했습니다. 하지만 범위 제한 때문에 철회했습니다. (함수에서 루프의 포장하십시오.)
폴 기갑에게

@PaulPanzer 질문을 편집하고 다시 포함 하시겠습니까? 대답을 삭제해도 괜찮습니다. 이것은 OP의 경우에 대한 실행 가능한 대안입니다. 모든 경우에 실행 가능한 대안은 아닙니다 .
Dimitris Fasarakis Hilliard

1
아뇨, 괜찮아요. 새로운 솔루션에 훨씬 더 만족합니다. 그러나 나는 당신이 한계를 알고 있다면 이것이 실행 가능하다는 당신의 요점을 볼 수 있습니다. 아무도 잘못 사용하여 발을 쏠 수 없도록 게시물에 약간의 경고를 추가 할 수 있습니까?
Paul Panzer

17

문자열을 f- 문자열 (전체 기능 포함)로 평가하는 간결한 방법은 다음 함수를 사용하는 것입니다.

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- 문자열을 도입했을 때이 간단한 구현을 내장 기능으로 어떻게 포함시키지 않았습니까?
user3204459

1
아니요, 범위를 잃습니다. 작동하는 유일한 이유 name는 글로벌 이기 때문 입니다. f- 문자열 평가에서 연기 되어야 하지만 FString 클래스는 호출자 지역 및 전역을 살펴봄으로써 범위 인수에 대한 참조 목록을 생성 한 다음 사용시 문자열을 평가해야합니다.
Erik Aronesty 19

2
@ user3204459 : 임의의 문자열을 실행할 수 있다는 것은 본질적으로 보안 위험이 있기 때문에 eval()일반적으로를 사용하지 않는 것이 좋습니다.
martineau 19.11.12

2
@martineau eval을 사용할 필요가 없도록 파이썬의 기능이어야합니다. 게다가 f-string은 악성 코드를 포함하여 중괄호 안에 무엇이든 넣을 수 있기 때문에 eval ()과 동일한 위험이 있습니다. 우려는 F-문자열을 사용하지 않는
user3204459

2
이것이 바로 제가 찾던 것입니다. 'fstr 연기'에 대해 더킹합니다. Eval은 일반적으로 fstring을 사용하는 것보다 나쁘지 않은 것 같습니다. 둘 다 동일한 힘을 가지고 있다고 생각합니다. f "{eval ( 'print (42) ')} "
user2692263

12

f- 문자열은 형식이 지정된 문자열을 만드는보다 간결한 방법 .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.formateval확실히 그럴 가치가 없어이다. format호출시 일반 문자열을 계속 사용 하십시오.


3
나는 당신의 코드 스 니펫에서 이점이 없다고 생각합니다. 내 말은, 당신은 항상 파일 The current name is {name}내부에 쓴 template.txt다음 print(template_a.format(name=name))(또는 .format(**locals())) 을 사용할 수 있습니다 . 코드는 약 10 자 더 길지만 .NET Framework로 인해 가능한 보안 문제는 발생하지 않습니다 eval.
Bakuriu

@Bakuriu-예; 내가 말했듯이, eval우리가 원할 때까지 f'{name}'평가 를 작성 하고 지연시킬 name수는 format있지만 OP가 이미 수행 했듯이 단순히 일반 템플릿 문자열을 만든 다음 호출 하는 것 보다 열등합니다 .
TigerhawkT3 2017

4
"f-string은 .format (** names)을 f로 대체하여 형식화 된 문자열을 생성하는 더 간결한 방법입니다." 정답은 아닙니다. 다른 구문을 사용합니다. 확인할 수있는 최근 python3은 없지만 예를 들어 f '{a + b}'가 작동한다고 믿고 '{a + b}'. format (a = a, b = b)는 KeyError를 발생시킵니다. . .format ()은 아마도 많은 상황에서 괜찮지 만 드롭 인 대체물은 아닙니다.
philh

2
@philh 방금 .formatf- 문자열과 동일하지 않은 예제를 만난 것 같습니다 DNA = "TATTCGCGGAAAATATTTTGA"; fragment = f"{DNA[2:8]}"; failed_fragment = "{DNA[2:8]}".format(**locals()). 생성 시도 failed_fragmentTypeError: string indices must be integers.
bli

12

.format을 사용하는 것은이 질문에 대한 정답이 아닙니다. 파이썬 f- 문자열은 str.format () 템플릿과 매우 다릅니다 ... 그들은 코드 나 다른 값 비싼 작업을 포함 할 수 있습니다. 따라서 지연이 필요합니다.

다음은 지연된 로거의 예입니다. 이것은 logging.getLogger의 일반적인 프리앰블을 사용하지만 로그 수준이 올바른 경우에만 f- 문자열을 해석하는 새 함수를 추가합니다.

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- 문자열 평가는 방대하고 의도하지 않은 부작용을 가질 수 있으며 지연된 방식으로 발생하면 프로그램 실행이 변경됩니다.

f- 문자열을 적절하게 지연 시키려면 파이썬이 명시 적으로 동작을 전환하는 방법이 필요합니다. 문자 'g'를 사용할까요? ;)

문자열 변환기에 버그가 있으면 지연된 로깅이 충돌하지 않아야한다는 지적이 있습니다. 위의 솔루션은 물론 이렇게하려면 변경할 수 있습니다 finally:except: , 그리고 스틱 log.exception거기에.


1
이 답변에 전적으로 동의하십시오. 이 사용 사례는이 질문을 검색 할 때 제가 생각했던 것입니다.
justhalf aug

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

당신이 원하는 것은 파이썬 향상 으로 간주되는 것 같습니다 .

한편-연결된 토론에서-다음은 사용할 필요가없는 합리적인 해결 방법 인 것 같습니다 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답변에서 영감을 얻은 다음은 deferred-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- 문자열을 사용하지 말고 다음 형식 만 지정하십시오.

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

2

어때 :

s = 'Hi, {foo}!'

s
> 'Hi, {foo}!'

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

0

f- 문자열을 사용하는 제안. 템플릿이 발생하는 논리적 수준에서 평가를 수행하고 생성기로 전달합니다. f- 문자열을 사용하여 원하는 지점에서 풀 수 있습니다.

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.