📜  使用Python模板类格式化字符串

📅  最后修改于: 2020-08-26 04:51:18             🧑  作者: Mango

介绍

Python模板用于将数据替换为字符串。使用模板,我们获得了一个高度可定制的界面,用于字符串替换(或字符串插值)。

Python已经提供了许多替换字符串的方法,包括最近引入的f-Strings。虽然它是用模板替换字符串,它的强大之处在于不常见的如何,我们可以自定义我们的字符串格式化规则。

在本文中,我们将使用Python的Template类格式化字符串。然后,我们看一下如何更改模板将数据替换为字符串的方式。

为了更好地理解这些主题,您将需要一些有关如何使用正则表达式的基础知识。

了解Python模板类

从Python 2.4开始,Python Template类已添加到string模块中。此类旨在用作内置替换选项(主要是%)的替代方法,以创建基于字符串的复杂模板并以用户友好的方式处理它们。

该类的实现使用正则表达式来匹配有效模板字符串的一般模式。有效的模板字符串或占位符由两部分组成:

  • $符号
  • 有效的Python标识符。标识符是大小写字母A到Z,下划线(_)和数字0到9的任意序列。标识符不能以数字开头,也不能是Python关键字。

在模板字符串,$name并且$age将被视为有效的占位符。

Template在我们的代码中使用Python 类,我们需要:

  1. Templatestring模块导入
  2. 创建一个有效的模板字符串
  3. Template使用模板字符串作为参数实例化
  4. 使用替代方法执行替代

这是一个如何Template在代码中使用Python 类的基本示例:

>>> from string import Template
>>> temp_str = 'Hi $name, welcome to $site'
>>> temp_obj = Template(temp_str)
>>> temp_obj.substitute(name='John Doe', site='StackAbuse.com')
'Hi John Doe, welcome to StackAbuse.com'

我们注意到在构建模板字符串时temp_str,我们使用两个占位符:$name$site。该$符号执行实际替换,并且使用标识符(namesite)将占位符映射到我们需要插入模板字符串中的具体对象。

当我们使用replace()方法执行替换并构建所需的字符串时,魔术就完成了。想想substitute()好象我们告诉Python中,通过这个字符串,如果你发现$name,然后更换它John Doe。继续搜索字符串,如果找到标识符$site,则将其变成StackAbuse.com

我们传递的参数名称.substitute()必须与模板字符串占位符中使用的标识符匹配。

TemplatePython中可用的字符串替换工具与其他字符串替换工具之间最重要的区别在于,未考虑参数的类型。我们可以传入可以转换为有效Python字符串的任何类型的对象。该Template课程将这些对象自动转换为字符串,然后把它们插入到最终的字符串。

既然我们了解了有关如何使用Python Template类的基础知识,那么让我们深入研究其实现的细节,以更好地了解该类在内部的工作方式。掌握了这些知识之后,我们将能够在代码中有效地使用该类。

模板字符串

模板字符串是包含特殊占位符的常规Python字符串。正如我们之前所见,这些占位符是使用$符号以及有效的Python标识符创建的。一旦有了有效的模板字符串,就可以将占位符替换为我们自己的值以创建更详细的字符串。

根据PEP 292-更简单的字符串替换,以下规则适用$于占位符的使用:

  1. $$是逃跑;它被替换为一个$
  2. $identifier命名与映射键“ identifier”匹配的替换占位符。默认情况下,“标识符”必须拼写http://docs.python.org/reference/lexical_analysis.html#identifiers-and-keywords中定义的Python标识符。$字符终止该占位符规范后的第一个非标识符字符。
  3. ${identifier}等价于$identifier。当有效的标识符字符位于占位符之后但不属于占位符时,例如,则是必需的"${noun}ification"。(来源)

让我们编写一些示例,以更好地理解这些规则的工作方式。

我们将从一个如何逃避$标志的示例开始。假设我们正在处理货币,并且需要在结果字符串中使用美元符号。我们可以将$符号加倍以使其自身在模板字符串中逸出,如下所示: 

>>> budget = Template('The $time budget for investment is $$$amount')
>>> budget.substitute(time='monthly', amount='1,000.00')
'The monthly budget for investment is $1,000.00'

请注意,就像在中所做的那样,无需在转义符号和下一个占位符之间添加额外的空间$$$amount。模板足够聪明,可以$正确地逃避标志。

第二条规则说明了在模板字符串中构建有效占位符的基础。每个占位符都需要使用$字符和有效的Python标识符来构建。看下面的例子:

>>> template = Template('$what, $who!')
>>> template.substitute(what='Hello', who='World')
'Hello, World!'

在这里,两个占位符都是使用有效的Python标识符(whatwho)形成的。还要注意,如第二条规则所述,第一个非标识符字符会终止占位符,如您所见$who!,该字符!不是占位符的一部分,而是最终字符串的一部分。

在某些情况下,我们需要部分替换字符串中的单词。这就是我们还有第二种选择来构建占位符的原因。第三个规则指出,当有效的标识符字符位于占位符之后但不属于占位符本身时,该规则${identifier}等效于$identifier并应使用。

假设我们需要自动创建包含有关我们公司产品的商业信息的文件。这些文件是根据包含产品代码,名称和生产批次的模式命名的,所有模式都用下划线(_)字符分隔。考虑以下示例:

>>> filename_temp = Template('$code_$product_$batch.xlsx')
>>> filename_temp.substitute(code='001', product='Apple_Juice', batch='zx.001.2020')
Traceback (most recent call last):
  ...
KeyError: 'code_'

由于_是有效的Python标识符字符,因此我们的模板字符串无法正常工作,并Template引发KeyError。要解决此问题,我们可以使用大括号(${identifier})并按以下方式构建占位符:

>>> filename_temp = Template('${code}_${product}_$batch.xlsx')
>>> filename_temp.substitute(code='001', product='Apple_Juice', batch='zx.001.2020')
'001_Apple_Juice_zx.001.2020.xlsx'

现在,模板可以正常工作!这是因为括号正确地将我们的标识符与_字符分开。值得一提的是,我们只需要使用支撑记法codeproduct而不是batch因为.后面的字符batch不是在Python有效标识符字符。

最后,模板字符串存储在template实例的属性中。让我们重新看一下Hello, World!示例,但是这次我们将做一些修改template

>>> template = Template('$what, $who!')  # Original template
>>> template.template = 'My $what, $who template'  # Modified template
>>> template.template
'My $what, $who template'
>>> template.substitute(what='Hello', who='World')
'My Hello, World template'

由于Python并不限制对实例属性的访问,因此我们可以随时修改模板字符串以满足需求。但是,在使用Python Template类时,这不是常见的做法。

最好为Template我们在代码中使用的每个不同的模板字符串创建新的实例。这样,我们将避免一些与不确定模板字符串的使用有关的细微而难以发现的错误。

replace()方法

到目前为止,我们一直substitute()Template实例上使用该方法来执行字符串替换。此方法使用关键字参数或包含标识符-值对的映射替换模板字符串中的占位符。

映射中的关键字参数或标识符必须与用于定义模板字符串中占位符的标识符一致。值可以是成功转换为字符串的任何Python类型。

由于我们在前面的示例中介绍了关键字参数的使用,因此现在让我们集中讨论使用字典。这是一个例子:

>>> template = Template('Hi $name, welcome to $site')
>>> mapping = {'name': 'John Doe', 'site': 'StackAbuse.com'}
>>> template.substitute(**mapping)
'Hi John Doe, welcome to StackAbuse.com'

当我们使用字典作为参数substitute(),我们需要使用字典拆包运算符:**。该运算符会将键值对解压缩为关键字参数,这些关键字参数将用于替换模板字符串中匹配的占位符。

常见模板错误

使用Python Template类时,我们可能会无意中引入一些常见错误。

例如,KeyError只要我们提供的参数集不完整,就会引发a substitute()。考虑以下使用不完整参数集的代码:

>>> template = Template('Hi $name, welcome to $site')
>>> template.substitute(name='Jane Doe')
Traceback (most recent call last):
  ...
KeyError: 'site'

如果我们调用substitute()的参数集与模板字符串中的所有占位符都不匹配,则将得到一个KeyError

如果我们在某些占位符中使用了无效的Python标识符,则会ValueError告诉我们占位符不正确。

请以本示例为例,在此示例中,我们使用无效的标识符$0name代替,作为占位符$name

>>> template = Template('Hi $0name, welcome to $site')
>>> template.substitute(name='Jane Doe', site='StackAbuse.com')
Traceback (most recent call last):
  ...
ValueError: Invalid placeholder in string: line 1, col 4

仅当Template对象读取模板字符串以执行替换时,它才会发现无效标识符。它立即引发一个ValueError。请注意,这0name不是有效的Python标识符或名称,因为它以数字开头。

safe_substitute()方法

Python Template类有第二种方法,我们可以用来执行字符串替换。该方法称为safe_substitute()。它的工作原理与之类似,substitute()但是当我们使用不完整或不匹配的参数集时,该方法不会出现KeyError

在这种情况下,丢失或不匹配的占位符在最终字符串中保持不变。

以下是safe_substitute()使用不完整的参数集(site将丢失)的工作方式:

>>> template = Template('Hi $name, welcome to $site')
>>> template.safe_substitute(name='John Doe')
'Hi John Doe, welcome to $site'

在这里,我们首先safe_substitute()使用一组不完整的参数进行调用。结果字符串包含原始占位符$site,但不KeyError引发。

自定义Python模板类

Python Template类设计用于子类化和自定义。这使我们能够修改正则表达式模式和该类的其他属性,以满足我们的特定需求。

在本节中,我们将介绍如何自定义类的一些最重要的属性以及这如何影响Template对象的一般行为。让我们从class属性开始.delimiter

使用其他定界符

class属性delimiter保存用作占位符起始字符的字符。到目前为止,我们的默认值为$

由于Python Template类是为继承而设计的,因此我们可以通过重写来继承Template和更改默认值delimiter。看下面的示例,在该示例中我们覆盖了定界符,#而不是$

from string import Template
class MyTemplate(Template):
    delimiter = '#'

template = MyTemplate('Hi #name, welcome to #site')
print(template.substitute(name='Jane Doe', site='StackAbuse.com'))

# Output:
# 'Hi Jane Doe, welcome to StackAbuse.com'

# Escape operations also work
tag = MyTemplate('This is a Twitter hashtag: ###hashtag')
print(tag.substitute(hashtag='Python'))

# Output:
# 'This is a Twitter hashtag: #Python'

我们可以MyTemplate像使用常规Python Template类一样使用我们的类。但是,我们现在必须使用#而不是$构建占位符。当我们使用处理许多美元符号的字符串时,例如在处理货币时,这可能很方便。

注意:不要更换delimiter使用正则表达式。模板类自动转义分隔符。因此,如果我们使用正则表达式,delimiter因为我们的自定义很可能Template无法正常运行。

更改符合条件的标识符

idpattern类属性包含用于验证的占位符的第二半中的模板串的正则表达式。换句话说,idpattern验证我们在占位符中使用的标识符是有效的Python标识符。默认值idpatternr'(?-i:[_a-zA-Z][_a-zA-Z0-9]*)'

我们可以继承子类Template并将其使用自己的正则表达式模式idpattern。假设我们需要将标识符限制为既不包含下划线(_)也不包含数字([0-9])的名称。为此,我们可以idpattern按如下所示从模式中覆盖并删除这些字符:

from string import Template
class MyTemplate(Template):
    idpattern = r'(?-i:[a-zA-Z][a-zA-Z]*)'

# Underscores are not allowed
template = MyTemplate('$name_underscore not allowed')
print(template.substitute(name_underscore='Jane Doe'))

如果我们运行此代码,将会得到以下错误:

Traceback (most recent call last):
    ...
KeyError: 'name'

我们可以确认数字也是不允许的:

template = MyTemplate('$python3 digits not allowed')
print(template.substitute(python3='Python version 3.x'))

错误将是:

Traceback (most recent call last):
    ...
KeyError: 'python'

由于下划线和数字不包含在我们的自定义中idpattern,因此该Template对象将应用第二条规则,并使用后面的第一个非标识符字符来破坏占位符$。这就是为什么我们KeyError在每种情况下都得到一个。

构建高级模板子类

有可能是我们需要修改的Python行为的情况Template类,但覆盖delimiteridpattern或两者是不够的。在这些情况下,我们可以走得更远,并覆盖patternclass属性,以为自定义Template子类定义一个全新的正则表达式。

如果决定为使用全新的正则表达式pattern,则需要提供包含四个命名组的正则表达式:

  1. escaped 匹配定界符的转义序列,例如 $$
  2. named 匹配分隔符和有效的Python标识符,例如 $identifier
  3. braced 使用花括号匹配定界符和有效的Python标识符,例如 ${identifier}
  4. invalid 匹配其他格式不正确的分隔符,例如 $0site

pattern属性保存一个已编译的正则表达式对象。但是,可以通过访问pattern属性的pattern属性来检查原始正则表达式字符串。查看以下代码:

>>> template = Template('$name')
>>> print(template.pattern.pattern)
\$(?:
    (?P\$) |   # Escape sequence of two delimiters
    (?P(?-i:[_a-zA-Z][_a-zA-Z0-9]*))      |   # delimiter and a Python identifier
    {(?P(?-i:[_a-zA-Z][_a-zA-Z0-9]*))}   |   # delimiter and a braced identifier
    (?P)              # Other ill-formed delimiter exprs
  )

此代码输出用于编译patternclass属性的默认字符串。在这种情况下,我们可以清楚地看到符合默认正则表达式的四个命名组。如前所述,如果需要深度自定义的行为Template,则应提供这四个相同的命名组以及每个组的特定正则表达式。

使用eval()和exec()运行代码

注: 内置的功能eval()exec()恶意输入使用时可以具有重要的安全隐患。请谨慎使用!

这最后一部分旨在让您了解Template如果我们将Python 类与一些Python内置函数(例如eval()和)一起使用时,Python 类的强大程度exec()

eval()函数执行一个Python表达式并返回其结果。该exec()函数还执行Python表达式,但从不返回其值。通常exec()在只对表达式的副作用感兴趣的情况下使用,例如,更改后的变量值。

我们将要介绍的示例似乎有些不合常规,但是我们确信您可以找到一些有趣的用例,以了解Python工具的这种强大组合。他们深入了解了生成Python代码的工具如何工作!

对于第一个示例,我们将使用Template并eval()通过列表理解来动态创建列表:

>>> template = Template('[$exp for item in $coll]')
>>> eval(template.substitute(exp='item ** 2', coll='[1, 2, 3, 4]'))
[1, 4, 9, 16]
>>> eval(template.substitute(exp='2 ** item', coll='[3, 4, 5, 6, 7, 8]'))
[8, 16, 32, 64, 128, 256]
>>> import math
>>> eval(template.substitute(expression='math.sqrt(item)', collection='[9, 16, 25]'))
[3.0, 4.0, 5.0]

在此示例中,我们的模板对象包含列表理解的基本语法。从此模板开始,我们可以通过用有效的表达式(exp)和集合(coll)替换占位符来动态创建列表。最后,我们使用进行理解eval()

由于对模板字符串的复杂程度没有限制,因此可以创建包含任何Python代码段的模板字符串。让我们考虑以下示例,该示例如何使用Template对象创建整个类:

from string import Template

_class_template = """
class ${klass}:
    def __init__(self, name):
        self.name = name

    def ${method}(self):
        print('Hi', self.name + ',', 'welcome to', '$site')
"""

template = Template(_class_template)
exec(template.substitute(klass='MyClass',
                         method='greet',
                         site='StackAbuse.com'))

obj = MyClass("John Doe")
obj.greet()

在这里,我们创建一个模板字符串来保存功能齐全的Python类。以后我们可以使用该模板创建不同的类,但根据需要使用不同的名称。

在这种情况下,exec()创建真实的类并将其带入当前的名称空间。从这一点开始,我们可以像使用任何常规Python类一样自由使用该类。

尽管这些示例相当基础,但它们显示了Python Template类的强大功能以及我们如何利用它来解决Python中的复杂编程问题。

结论

Python Template类旨在用于字符串替换或字符串插值。该类使用正则表达式工作,并提供了用户友好且功能强大的界面。当创建复杂的基于字符串的模板时,它是替代内置字符串替换选项的可行选择。

在本文中,我们学习了Python Template类的工作方式。我们还了解了在使用时会引入的常见错误Template以及如何解决这些错误。最后,我们介绍了如何通过子类定制类以及如何使用它运行Python代码。

掌握了这些知识之后,我们处于更好的条件下,可以有效地使用Python Template类在代码中执行字符串插值或替换。