从调用作用域中提取变量的字符串格式化器是不好的实践吗?

11

我有一些代码需要大量的字符串格式化操作,通常情况下,我会得到类似以下的代码:

"...".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
你也可以使用locals()globals() - Facundo Casco
@F.C.:确实如此,但是到处都有interpolate("...", **locals())看起来很凌乱,而且在s是局部变量的罕见情况下会失败,因为它尝试两次设置第一个参数。 - Eric
1
不过,我猜你现在是手动输入这些内容。为什么不在定义函数时将它们放入字典中呢? - Wilduck
1
我认为它看起来很不错。我没有通过使用Python。 - BeniBela
1
在 PyPy 上,这段代码将会非常慢。而这将是你自己的错(在生产环境中使用调试钩子)。 - fijal
显示剩余4条评论
4个回答

9

更新:Python 3.6 已经内置了这个功能(一个更强大的变量):

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

请参考PEP 0498 -- 字面字符串插值


手动解决方案(manual solution)在嵌套函数中会导致一些令人惊讶的行为:

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

8
一个更简单和更安全的方法是下面的代码。inspect.currentframe在Python的所有实现中都不可用,因此当它不可用时,您的代码将会出错。在Jython、IronPython或PyPy下,可能无法使用它,因为它似乎是CPython的东西。这使得您的代码不太具有可移植性。
print "{x}, {y}".format(**vars())

这种技术实际上在Python教程的输入和输出章节中有所描述。

也可以使用“**”符号将表作为关键字参数传递。这在与新的内置vars()函数结合使用时特别有用,该函数返回包含所有局部变量的字典。

inspect.currentframe也在Python文档中有所提及。

CPython实现细节:此函数依赖于解释器中对Python堆栈帧的支持,在不保证存在Python堆栈帧支持的所有Python实现中运行此函数会返回None。


3

老实的邮递员有一个函数_,可以完美地完成这个任务:

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

所以如果Barry Warsaw能做到,为什么我们不能呢?


2
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}")

这样做可以使你的代码更具可移植性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接