Python中的链式字符串格式化
在处理一些SQL格式化的时候,我惊讶地发现我可以把字符串格式化器串联起来使用:
def get_sql(table, limit=True):
sql = "select report_date from %s"
if limit:
result = "%s limit 1" % sql % table
else:
result = sql % table
return result
这样做可以吗?有没有什么理由不这样做?
4 个回答
没错,像这样把字符串格式化连起来是可以的,尽管这是我第一次看到这么用(而且用得很糟糕,别忘了这一点)!
原因是同类型的运算符是从左到右处理的(这叫“左结合性”),不过有几个例外,比如指数运算 **
和比较运算 a<b<c
。
所以就像这样:
>>> 1 - 2 - 3 # equals to (1-2)-3
-4
>>> 16 / 4 / 2 # equals to (16 /4) /2, NOT 16 / (4 / 2)
2
同样,s1 % s2 % s3
其实等于 (s1 % s2) % s3
。
哦,对了,不管 s1、s2 和 s3 是字符串还是数字都没关系——编译器在编译的时候并不知道这一点,只有在运行时才会确定 %
是表示“除法的余数”(如果 s1 是数字)还是字符串格式化(如果 s1 是字符串)。
这完全是合理的。
字符串格式化的“单个参数”形式其实是一个特殊情况——当有多个项目时,通常会使用元组,这样就能更清楚地说明为什么这样做是可以的。
result = "%s limit 1" % (sql % (table,),)
这段话最初是为了鼓励提问者,告诉他们支持多种格式是语言的一个合法特性,但正如Nas Banov所评论的,这段话读起来像是在解释它是怎么工作的(而且代码写得也不太对)。它并不是像这段话所暗示的那样从右到左构建字符串,而是必须从左到右构建(也就是说是有结合性的)。操作符必须在左边接受一个字符串并返回一个字符串,但在右边可以接受一个非字符串(比如元组)。因为你不能对一对元组使用%运算符,所以它不可能反向工作。
>>> "%s %f %s" % ( "%d", 0.1, "%d %d" ) % (1,2,3)
'1 0.100000 2 3'
不过,这可能会导致代码变得复杂或混乱,所以我个人建议尽量少用。
你可以这样处理你的例子:
def get_sql(table, limit=True):
sql = "select report_date from"
strlimit = ""
if limit:
strlimit = "limit 1"
return "%s %s"%(sql, strlimit)
这样做是有道理的,因为像这样的语句:
'some value goes here %s' % value
实际上会返回一个字符串。把它看成这样可能更容易理解:
result = ("%s limit 1" % sql) % table
这样做没有什么明显的问题,但把多个操作连在一起可能会让你很难找出错误的来源。
比如说,这样做是没问题的:
>>> sql = 'a value of %s'
>>> x = 'some string %s with stuff'
>>> y = 'VALUE'
>>> x % sql % y
'some string a value of VALUE with stuff'
但是如果里面有格式错误(我知道这个例子有点极端,但能说明问题):
>>> sql = 'a value of %d'
>>> x = 'some string %d with stuff'
>>> y = 123
>>> x % sql % y
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: %d format: a number is required, not str
你根本不清楚是哪一个 %d
引起了错误。因此,我建议把每个格式化操作分开,尽量每行只用一个 %
格式化符,这样错误追踪时就能准确指向出问题的那一行和格式化符。
另外,采用每行一个格式化符的方式,也会让其他人阅读你的代码时轻松很多,更容易理解发生了什么。