Python-CookBook:34、给字符串中的变量名做插值处理

本贴最后更新于 1707 天前,其中的信息可能已经渤澥桑田

问题

我们想创建一个字符串,其中嵌入的变量名称会以变量的字符串值形式替换掉。

解决方案

Python 并不直接支持在字符串中对变量做简单的值替换。但是,这个功能可以通过字符串的 format()方法近似模拟出来。示例如下:

s='{name} has {n} messages.'
print(s.format(name='Mark',n=10))

输出

Mark has 10 messages.

另一种方式是,如果要被替换的值确实能在变量中找到,则可以将 format_map()和 vars()联合起来使用,示例如下:

s='{name} has {n} messages.'
name='mark'
n=10

print(s.format_map(vars()))
print(vars())

输出:

mark has 10 messages.
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x1048804a8>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Volumes/UP/python_cookbook/34-2.py', '__cached__': None, 's': '{name} has {n} messages.', 'name': 'mark', 'n': 10}

有关 vars()的一个微妙的特性是它也能作用于类实例上。比如:

s='{name} has {n} messages.'

class Info:
    def __init__(self,name,n):
        self.name=name
        self.n=n

a=Info('Mark',10)
print(s.format_map(vars(a)))
print(vars(a))

输出:

Mark has 10 messages.
{'name': 'Mark', 'n': 10}

而 format()和 format_map()的一个缺点则是没法优雅地处理缺少某个值的情况。例如:

>>> s.format(name='Guido')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'n'
>>>

避免出现这种情况的一种方法就是单独定义一个带有__missing__()方法的字典类,示例如下:

s='{name} has {n} messages.'
name='mark'
class safesub(dict):
    def __missing__(self, key):
        return '{'+key+'}'
print(s.format_map(safesub(vars())))

输出:

mark has {n} messages.

如果发现自己在代码中常常需要执行这些步骤,则可以将替换变量的过程隐藏在一个小型的功能函数内,这里要采用一种称之为“frame hack”的技巧。示例如下:

import sys

class safesub(dict):
    def __missing__(self, key):
        return '{'+key+'}'

def sub(text):
    return text.format_map(safesub(sys._getframe(1).f_locals))

name='mark'
n=10
print(sub('Hello {name}'))
print(sub('You have {n} messages'))
print(sub('You favorite color is {color}'))

输出:

Hello mark
You have 10 messages
You favorite color is {color}

2.15.3 讨论

多年来,由于 Python 缺乏真正的变量插值功能,由此产生了各种解决方案。作为本节中已给出的解决方案的替代,有时候我们会看到类似下面代码中的字符串格式化操作:

>>> name = 'Guido'
>>> n = 37
>>> '%(name) has %(n) messages.' % vars()
'Guido has 37 messages.'
>>>

我们可能还会看到模板字符串(template string)的使用:

>>> import string
>>> s = string.Template('$name has $n messages.')
>>> s.substitute(vars())
'Guido has 37 messages.'
>>>

但是,format()和 format_map()方法比上面这些替代方案都要更加现代化,我们应该将其作为首选。使用 format()的一个好处是可以同时得到所有关于字符串格式化方面的功能(对齐、填充、数值格式化等),而这些功能在字符串模板对象上是不可能做到的。

在本节的部分内容中还提到了一些有趣的高级特性。字典类中鲜为人知的__missing__()方法可用来处理缺少值时的行为。在 safesub 类中,我们将该方法定义为将缺失的值以占位符的形式返回,因此这里不会抛出 KeyError 异常,缺少的那个值会出现在最后生成的字符串中(可能对调试有些帮助)。

sub()函数使用了 sys._getframe(1)来返回调用方的栈帧。通过访问属性 f_locals 来得到局部变量。无需赘言,在大部分的代码中都应该避免去和栈帧打交道,但是对于类似完成字符串替换功能的函数来说,这会是有用的。插一句题外话,值得指出的是 f_locals 是一个字典,它完成对调用函数中局部变量的拷贝。尽管可以修改 f_locals 的内容,可是修改后并不会产生任何持续性的效果。因此,尽管访问不同的栈帧可能看起来是很邪恶的,但是想意外地覆盖或修改调用方的本地环境也是不可能的。

  • Python

    Python 是一种面向对象、直译式电脑编程语言,具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。

    536 引用 • 672 回帖 • 1 关注
  • 书籍

    宋真宗赵恒曾经说过:“书中自有黄金屋,书中自有颜如玉。”

    76 引用 • 390 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...