Является ли форматировщик строк, который тянет переменные из его вызывающей области плохой практики?
У меня есть код, который делает очень много форматирования строк. Часто я получаю код в строках:
"...".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}")
и ваш код будет более переносимым.