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 [1]
哦。我刚意识到这里经常发生这种情况。在我工作的地方,我们使用 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"
True
>>> "\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