Python timeit 命令行错误:“语法错误:扫描字符串字面量时到达行尾”
我一直在使用Python的timeit模块,已经有一段时间了,但之前都是在交互式的Python环境或者Unix命令行中使用。现在,我想在Windows的命令提示符(cmd.exe)中测量一些代码片段,但出现了这个错误:
C:\Users\Me>python -m timeit '"-".join(map(str, range(100)))'
Traceback (most recent call last):
File "C:\Python33\lib\runpy.py", line 160, in _run_module_as_main
"__main__", fname, loader, pkg_name)
File "C:\Python33\lib\runpy.py", line 73, in _run_code
exec(code, run_globals)
File "C:\Python33\lib\timeit.py", line 334, in <module>
sys.exit(main())
File "C:\Python33\lib\timeit.py", line 298, in main
t = Timer(stmt, setup, timer)
File "C:\Python33\lib\timeit.py", line 131, in __init__
code = compile(src, dummy_src_name, "exec")
File "<timeit-src>", line 6
'-.join(map(str,
^
SyntaxError: EOL while scanning string literal
这让我很困惑,因为我在字符串中并没有插入任何换行符,实际上,我是直接从timeit模块的文档中复制的示例。
在尝试这个的过程中,我试着测试没有空格的代码片段,因为错误提示的字符正好在空格前面。虽然现在没有出现异常,但模块报告的执行时间和我传入一个pass
语句时的结果是一样的,如下所示:
C:\Users\Me>python -m timeit
100000000 loops, best of 3: 0.013 usec per loop
C:\Users\Me>python -m timeit 'map(str,range(100))'
100000000 loops, best of 3: 0.013 usec per loop
C:\Users\Me>python -m timeit 'map(str,range(1000000000000000))'
100000000 loops, best of 3: 0.013 usec per loop
我确定我正确调用了这个模块,因为我在Unix命令行中粘贴的相同代码行都能正常工作。
由于我在Python 2.7和3.3中得到的结果完全相同(而且这个模块是用纯Python写的,已经存在很长时间了),我相信这和Python本身无关,而是和Windows命令提示符有关。
那么,这种奇怪的行为到底是怎么回事,我该如何解决呢?
1 个回答
简要说明
在传递给timeit模块的语句中使用双引号。
示例:
C:\Users\Me>python -m timeit "'-'.join(map(str, range(100)))"
10 loops, best of 3: 28.9 usec per loop
详细解释
与Unix系统的命令行(比如bash和tcsh)不同,Windows命令行对单引号的处理方式有所不同。
这里有一个简单的Python程序来演示这个问题:
import sys
print(sys.argv[1:])
运行这个程序(我们称这个文件为cmdtest.py),我们会观察到以下结果:
C:\Users\Me\Desktop>python cmdtest.py 1 2 3
['1', '2', '3']
C:\Users\Me\Desktop>python cmdtest.py "1 2 3"
['1 2 3']
C:\Users\Me\Desktop>python cmdtest.py '1 2 3'
["'1", '2', "3'"]
所以,单引号会被当作普通字符处理(也就是说,不会被当作特殊字符)。在StackOverflow上搜索了一下,我找到了一段关于cmd如何处理命令行参数的很好的描述:这段描述:
当从命令窗口调用命令时,
cmd.exe
(也就是“命令行”)并不会对命令行参数进行分割。通常情况下,分割是由新创建的进程的C/C++运行时来完成的,但这并不一定是这样——例如,如果新进程不是用C/C++编写的,或者如果新进程选择忽略argv
,自己处理原始命令行(比如使用[GetCommandLine()][1])。在操作系统层面,Windows将命令行作为一个整体字符串传递给新进程,而不是分割开来。这与大多数*nix系统的命令行不同,在这些系统中,命令行会在传递给新进程之前被一致且可预测地分割。所有这些意味着,在Windows上,不同程序的参数分割行为可能会有很大差异,因为各个程序往往会自行处理参数的分割。如果这听起来像是一种混乱,确实有点。不过,由于很多Windows程序确实使用了Microsoft C/C++运行时的
argv
,了解MSVCRT如何分割参数是有用的。以下是一些要点:
- 参数之间用空格分隔,空格可以是普通空格或制表符。
- 用双引号括起来的字符串会被视为一个单独的参数,不管里面有多少空格。一个带引号的字符串可以嵌入在参数中。注意,插入符号(^)不会被当作转义字符或分隔符。
错误 #2
考虑到以上内容,先解释第二个奇怪的行为(那个表现得像pass
语句的),因为它相对简单。由于单引号被当作普通字符处理,当调用:
C:\Users\Me>python -m timeit 'map(str,range(100))'
时,确切的字符串字面量'map(str,range(100))'
(包括引号)被作为语句传递给time模块。
所以,Python会看到
"'map(str,range(100))'"
而不是
'map(str,range(100))'
这样作为字符串并没有实际作用,测量结果与pass
语句非常接近。
错误 #1
现在说说第一个错误:
根据Python的timeit模块的文档:
可以通过将每一行指定为单独的语句参数来给出多行语句;
所以,当调用:
C:\Users\Me>python -m timeit '"-".join(map(str, range(100)))'
时,Python看到["'-.join(map(str,", "range(100)))'"]
被作为语句传递给timeit,模块将其解释为多行语句:
'"-".join(map(str,
range(100)))'
这第一行是一个以单引号开头但没有结尾的字符串,因此,最终解释了奇怪的EOL错误。
解决方案
使用双引号来传递给time的语句可以解决这个问题。
我还尝试了Windows PowerShell,它比cmd.exe更高级,表现得更像Unix命令行,但并没有完全解决我测试的所有语句的问题。
例如,这个可以正常工作(注意语句中的空格):
PS C:\Users\Me> python -m timeit 'map(str, range(100))'
1000000 loops, best of 3: 0.688 usec per loop
而最初的示例则不行:
PS C:\Users\Me\Desktop> python -m timeit '"-".join(map(str, range(100)))'
option -. not recognized
use -h/--help for command line help
(不过我还不是很满意。我更希望能让cmd或PowerShell像Unix命令行那样工作,这样我就可以简单地粘贴和计时代码片段。如果有人知道一个快速简单的方法来实现这一点(如果可能的话),那就太好了。)