Возможна ли интерполяция вложенных строковых литералов? - PullRequest
0 голосов
/ 08 июня 2018

При использовании отформатированного строкового литерала возможно иметь в некоторой степени вложенные f-строки .

a = 3
b = 7

res = f"{f'{a*b}'}"

print(res) # '21'

Хотя это не работаетесли внутренним выражением является переменная, содержащая строку.

a = 3
b = 7

expr = 'a*b'

res = f"{f'{expr}'}"

print(res) # 'a*b'

Есть ли способ сделать эту работу и получить второй вывод, равный '21'?Если нет, то какая разница между первой и второй строкой, которая мешает этому?

Ответы [ 3 ]

0 голосов
/ 08 июня 2018

Я думаю, что будет полезно увидеть, что на самом деле происходит под капотом, когда вызывается каждое из этих выражений.

f"{f'{a*b}'}"

def om1(a, b):
    return f"{f'{a*b}'}"

dis.dis(om1)
  2           0 LOAD_FAST                0 (a)
              2 LOAD_FAST                1 (b)
              4 BINARY_MULTIPLY
              6 FORMAT_VALUE             0
              8 FORMAT_VALUE             0
             10 RETURN_VALUE

Внешняя f-строка встречает выражение, которое она оценивает, и внутренняя f-строка также находитвыражение, которое оно вычисляет, что приводит к вызову BINARY_MULTIPLY

f"{f'{expr}'}"

def om2(a, b):
    expr = 'a*b'
    return f"{f'{expr}'}"

dis.dis(om2)
  2           0 LOAD_CONST               1 ('a*b')
              2 STORE_FAST               2 (expr)

  3           4 LOAD_FAST                2 (expr)
              6 FORMAT_VALUE             0
              8 FORMAT_VALUE             0
             10 RETURN_VALUE

Здесь первая f-строка встречает выражение иоценивает его, и внутренняя f-строка встречает строку, что приводит к вызову LOAD_FAST вместо оценки содержимого строки как кода Python.

Также это важноотметить во втором примере отсутствующие LOAD_FAST вызовы как a, так и b, которые присутствуют в первом примере.

0 голосов
/ 08 июня 2018

Это называется "строка литерал интерполяция".Строка должна быть литералом, т.е. во время компиляции компилятор превратит строку в правильный исполняемый код.Если у вас уже есть строка в качестве значения (не как литерала), для этого уже слишком поздно.

У меня нет доступа к Python с включенным PEP 498, поэтому мои примеры будут на Ruby,который имел этот механизм в течение длительного времени.Синтаксис Ruby для f"...{expr}..." в Python: "...#{expr}...".

. В Ruby "a#{2 * 3}b" является синтаксическим сахаром для ["a", (2 * 3), "b"].join (т. Е. Они производят точно такой же байт-код).Если у вас уже есть строка "2 * 3" в качестве значения, компилятор ничего не может с этим поделать;единственный способ превратить строковое значение в результат - это оценить его.

В первом примере у вас есть строковый литерал внутри строкового литерала;оба обрабатываются компилятором во время компиляции: когда компилятор видит внешний литерал, он компилирует его, находит там другой строковый литерал, а также компилирует его, генерирует код.Фактически, "a#{"#{2 * 3}"}b" производит точно такой же байт-код, опять же.

Тот факт, что это делается во время компиляции, также является причиной того, что интерполяция строкового литерала вызовет синтаксическую ошибку, если выражение внутриискажен, даже если рассматриваемая строка никогда не выполняется: if false; "#{1+}"; end будет выдавать SyntaxError.

Тот факт, что это делается во время компиляции, означает, что строки, уже находящиеся в переменных, не подходят для этого механизма.В вашем коде к моменту оценки res, expr могло бы быть чем угодно;единственный выход - evil (или другой, более безопасный, оценщик).

0 голосов
/ 08 июня 2018

Есть несколько библиотек, которые разработали функции для вычисления числовых и логических выражений безопасно (ключом является "сейф").

Во-первых, настройка -

a = 3
b = 7
op = '*'

numexpr.evaluate

>>> import numexpr as ne
>>> ne.evaluate(f'{a} {op} {b}')
array(21, dtype=int32)

numexpr достаточно умен, чтобы оптимизировать ваши выражения, и в некоторых случаях даже быстрее, чем просто.Установка с использованием pip.


pandas.eval

A safe eval из Pandas API, аналогичного ne.evaluate.

>>> import pandas as pd
>>> pd.eval(f'{a} {op} {c}')
12
...