Является ли форматировщик строк, который тянет переменные из его вызывающей области плохой практики?

У меня есть код, который делает очень много форматирования строк. Часто я получаю код в строках:

"...".format(x=x, y=y, z=z, foo=foo, ...)

Где я пытаюсь интерполировать большое количество переменных в большую строку.

Есть ли хорошая причина не писать такую ​​функцию, которая использует модуль inspect для поиска переменных для интерполяции?

import inspect

def interpolate(s):
    return s.format(**inspect.currentframe().f_back.f_locals)

def generateTheString(x):
    y = foo(x)
    z = x + y
    # more calculations go here
    return interpolate("{x}, {y}, {z}")

Ответ 1

Более простым и безопасным подходом будет приведенный ниже код. index.currentframe не доступен для всех реализаций python, поэтому ваш код сломается, когда он не будет. В jython, ironpython или pypy это может быть недоступно, потому что это похоже на cpython. Это делает ваш код менее портативным.

print "{x}, {y}".format(**vars())

этот метод фактически описан в главе Python в разделе "Ввод и вывод"

Это также можно сделать, передав таблицу как аргументы ключевого слова с помощью обозначение '**. Это особенно полезно в сочетании с новая встроенная функция vars(), которая возвращает словарь, содержащий все локальные переменные.

также в документах python для inspect.currentframe

Подробности реализации CPython: эта функция основана на стеке Python поддержка фрейма в интерпретаторе, который, как гарантируется, не существует в все реализации Python. Если выполняется в реализации без Кадр стека Python поддерживает эту функцию, возвращает None.

Ответ 2

Обновление: Python 3.6 имеет эту функцию (более мощный вариант):

x, y, z = range(3)
print(f"{x} {y + z}")
# -> 0 3

См. PEP 0498 - Интерполяция буквенных строк


Это [ручное решение] приводит к несколько неожиданному поведению с вложенными функциями:

from callerscope import format

def outer():
    def inner():
        nonlocal a
        try:
            print(format("{a} {b}"))
        except KeyError as e:
            assert e.args[0] == 'b'
        else:
            assert 0

    def inner_read_b():
        nonlocal a
        print(b) # read `b` from outer()
        try:
            print(format("{a} {b}"))
        except KeyError as e:
            assert 0
    a, b = "ab"
    inner()
    inner_read_b()

Примечание: тот же вызов выполняется успешно или не выполняется в зависимости от того, упоминается ли какая-либо переменная где-то выше или ниже.

Где callerscope:

import inspect
from collections import ChainMap
from string import Formatter

def format(format_string, *args, _format=Formatter().vformat, **kwargs):
    caller_locals = inspect.currentframe().f_back.f_locals
    return _format(format_string, args, ChainMap(kwargs, caller_locals))

Ответ 3

У хорошего старого mailman есть функция _, которая делает именно эту вещь:

def _(s):
    if s == '':
        return s
    assert s
    # Do translation of the given string into the current language, and do
    # Ping-string interpolation into the resulting string.
    #
    # This lets you write something like:
    #
    #     now = time.ctime(time.time())
    #     print _('The current time is: %(now)s')
    #
    # and have it Just Work.  Note that the lookup order for keys in the
    # original string is 1) locals dictionary, 2) globals dictionary.
    #
    # First, get the frame of the caller
    frame = sys._getframe(1)
    # A `safe' dictionary is used so we won't get an exception if there a
    # missing key in the dictionary.
    dict = SafeDict(frame.f_globals.copy())
    dict.update(frame.f_locals)
    # Translate the string, then interpolate into it.
    return _translation.gettext(s) % dict

Итак, если Барри Варшава может это сделать, почему мы не можем?

Ответ 4

В модуле inspect currentframe определяется следующим образом:

if hasattr(sys, '_getframe'):
    currentframe = sys._getframe
else:
    currentframe = lambda _=None: None

Поэтому, если sys не имеет атрибута _getframe, функция interpolate не будет работать.

Документы для sys._getframe говорят:

Подробности реализации CPython: эту функцию следует использовать для только внутренние и специализированные цели. Не гарантируется существование во всех реализациях Python.


Запись

"{x}, {y}, {z}".format(**vars())

в теле функции не намного длиннее

interpolate("{x}, {y}, {z}")

и ваш код будет более переносимым.