PEP 3126 – 移除隐式字符串连接
- 作者:
- Jim J. Jewett <JimJJewett at gmail.com>,Raymond Hettinger <python at rcn.com>
- 状态:
- 已拒绝
- 类型:
- 标准轨迹
- 创建:
- 2007年4月29日
- 发布历史:
- 2007年4月29日,2007年4月30日,2007年5月7日
拒绝通知
此 PEP 已被拒绝。没有足够的支持,要移除的功能并没有那么有害,并且有一些用例会变得更加困难。
摘要
Python 继承了 C 中的许多解析规则。虽然这通常很有用,但有些规则对 Python 来说不太有用,应该消除。
此 PEP 提出仅基于字面量的邻接来消除隐式字符串连接。
而不是
"abc" "def" == "abcdef"
作者将需要明确,并且要么添加字符串
"abc" + "def" == "abcdef"
要么连接它们
"".join(["abc", "def"]) == "abcdef"
动机
Python 3000 的一个目标应该是通过移除不必要的特性来简化语言。隐式字符串连接应该被放弃,转而使用现有的技术。这将简化语法并简化用户对 Python 的心理认知。后者对于让语言“融入你的脑海”非常重要。一大批当前用户甚至不知道隐式连接。在那些知道它的人中,很大一部分从未使用过它或习惯性地避免它。在那些既知道又使用它的人中,很少有人能够自信地说明隐式运算符的优先级以及在编译定义时和运行时计算它的情况。
历史或未来
许多 Python 解析规则有意与 C 兼容。这是一个有用的默认设置,但特殊情况需要根据它们在 Python 中的实用性来证明。我们不应该再假设 Python 程序员也熟悉 C,因此语言之间的兼容性应该被视为一个决定因素,而不是一个理由。
在 C 中,隐式连接是连接字符串的唯一方法,无需使用(运行时)函数调用来存储到变量中。在 Python 中,可以使用更标准的 Python 习惯用法(例如 +
或 "".join
)来连接字符串(并且仍然被识别为不可变)。
问题
隐式字符串连接会导致元组和列表比它们看起来更短;这反过来可能导致令人困惑,甚至静默的错误。例如,给定一个接受多个参数但为其中一些参数提供默认值的函数
def f(fmt, *args):
print fmt % args
这看起来像一个有效的调用,但事实并非如此
>>> f("User %s got a message %s",
"Bob"
"Time for dinner")
Traceback (most recent call last):
File "<pyshell#8>", line 2, in <module>
"Bob"
File "<pyshell#3>", line 2, in f
print fmt % args
TypeError: not enough arguments for format string
对该函数的调用可能会静默地执行错误的操作
def g(arg1, arg2=None):
...
# silently transformed into the possibly very different
# g("arg1 on this linearg2 on this line", None)
g("arg1 on this line"
"arg2 on this line")
引用 Jason Orendorff [#Orendorff]
哦。我刚刚意识到这种情况在这里发生很多。在我工作的地方,我们使用 scons,每个 SConscript 都有一个很长的文件名列表sourceFiles = [ 'foo.c' 'bar.c', #...many lines omitted... 'q1000x.c']这是一个常见的错误,遗漏了一个逗号,然后 scons 抱怨它找不到“foo.cbar.c”。即使你是 Python 程序员,这也很令人费解,而且并不是每个人都是。
解决方案
在 Python 中,字符串是对象,它们支持 __add__ 运算符,因此可以编写
"abc" + "def"
因为这些是字面量,所以编译器仍然可以优化掉此加法;CPython 编译器已经这样做了。[2]
其他现有的替代方案包括多行(三引号)字符串和 join 方法
"""This string
extends across
multiple lines, but you may want to use something like
Textwrap.dedent
to clear out the leading spaces
and/or reformat.
"""
>>> "".join(["empty", "string", "joiner"]) == "emptystringjoiner"
True
>>> " ".join(["space", "string", "joiner"]) == "space string joiner"
>>> "\n".join(["multiple", "lines"]) == "multiple\nlines" == (
"""multiple
lines""")
True
关注点
运算符优先级
Guido 指出 [2] 此更改应由 PEP 处理,因为其他字符串运算符(例如 %)有一些边缘情况。(假设 str % 保留 - 它可能会被 PEP 3101 – 高级字符串格式化取代。[3])
解决方案是使用括号来强制优先级 - 这与今天可以使用的解决方案相同
# Clearest, works today, continues to work, optimization is
# already possible.
("abc %s def" + "ghi") % var
# Already works today; precedence makes the optimization more
# difficult to recognize, but does not change the semantics.
"abc" + "def %s ghi" % var
而不是
# Already fails because modulus (%) is higher precedence than
# addition (+)
("abc %s def" + "ghi" % var)
# Works today only because adjacency is higher precedence than
# modulus. This will no longer be available.
"abc %s" "def" % var
# So the 2-to-3 translator can automatically replace it with the
# (already valid):
("abc %s" + "def") % var
长命令
…构建(我认为是)可读的 SQL 查询[4]rows = self.executesql("select cities.city, state, country" " from cities, venues, events, addresses" " where cities.city like %s" " and events.active = 1" " and venues.address = addresses.id" " and addresses.city = cities.id" " and events.venue = venues.id", (city,))
替代方案再次包括三引号字符串、+
和 .join
query="""select cities.city, state, country
from cities, venues, events, addresses
where cities.city like %s
and events.active = 1"
and venues.address = addresses.id
and addresses.city = cities.id
and events.venue = venues.id"""
query=( "select cities.city, state, country"
+ " from cities, venues, events, addresses"
+ " where cities.city like %s"
+ " and events.active = 1"
+ " and venues.address = addresses.id"
+ " and addresses.city = cities.id"
+ " and events.venue = venues.id"
)
query="\n".join(["select cities.city, state, country",
" from cities, venues, events, addresses",
" where cities.city like %s",
" and events.active = 1",
" and venues.address = addresses.id",
" and addresses.city = cities.id",
" and events.venue = venues.id"])
# And yes, you *could* inline any of the above querystrings
# the same way the original was inlined.
rows = self.executesql(query, (city,))
正则表达式
复杂的正则表达式有时用几个隐式连接的字符串来表示,每个正则表达式组件都在不同的行上,后面跟着注释。可以在这里插入加号运算符,但这会使正则表达式更难阅读。一种替代方法是使用 re.VERBOSE 选项。另一种替代方法是使用一系列 += 行构建正则表达式
# Existing idiom which relies on implicit concatenation
r = ('a{20}' # Twenty A's
'b{5}' # Followed by Five B's
)
# Mechanical replacement
r = ('a{20}' +# Twenty A's
'b{5}' # Followed by Five B's
)
# already works today
r = '''a{20} # Twenty A's
b{5} # Followed by Five B's
''' # Compiled with the re.VERBOSE flag
# already works today
r = 'a{20}' # Twenty A's
r += 'b{5}' # Followed by Five B's
国际化
一些国际化工具 - 尤其是 xgettext - 已经针对隐式连接进行了特殊处理,但没有针对 Python 的显式连接。[5]
这些工具将无法提取(已经合法的)
_("some string" +
" and more of it")
但通常有一个针对以下情况的特殊情况
_("some string"
" and more of it")
也可以使用过长的行(xgettext 将消息限制为 2048 个字符[7],小于 Python 强制执行的限制)或三引号字符串,但这些解决方案牺牲了代码的一些可读性
# Lines over a certain length are unpleasant.
_("some string and more of it")
# Changing whitespace is not ideal.
_("""Some string
and more of it""")
_("""Some string
and more of it""")
_("Some string \
and more of it")
我看不到一个好的短期解决方案。
过渡
提议的新结构在当前的 Python 中已经是合法的,可以立即使用。
2 到 3 的转换器可以机械地更改
"str1" "str2"
("line1" #comment
"line2")
进入
("str1" + "str2")
("line1" +#comments
"line2")
如果用户想要使用其他习惯用法,他们可以;由于这些习惯用法在 python 2 中都是合法的,因此可以对原始源进行编辑,而不是修补转换器。
未解决的问题
是否有更好的方法来支持外部文本提取工具,或者至少是 xgettext
[6]?
参考文献
版权
本文档已进入公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-3126.rst