Python timeit命令行错误:"SyntaxError:EOL扫描字符串文字时"

imp*_*his 2 python windows command-line cmd timeit

我一直在使用Python timeit模块,但它只是通过交互式Python会话或Unix shell.现在,我正在尝试在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
Run Code Online (Sandbox Code Playgroud)

这是相当令人困惑的,因为我没有在字符串中插入任何换行符 - 相反,我实际上直接从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
Run Code Online (Sandbox Code Playgroud)

我确信我正确地调用了模块,因为我在Unix shell上粘贴了相同的行,并且它们按预期工作.

由于我在Python 2.7和3.3中获得了完全相同的结果(此外,该模块是用纯Python编写的,并且它已经存在了很长时间)我确信这与Python无关,但Windows命令提示符,代替.

那么,为什么这种奇怪的行为恰好发生了,我该如何解决呢?

imp*_*his 11

TL;博士

对传递给timeit模块的语句使用双引号.
例:

C:\Users\Me>python -m timeit "'-'.join(map(str, range(100)))"
10 loops, best of 3: 28.9 usec per loop
Run Code Online (Sandbox Code Playgroud)

详细解释

与Unix shell(如bashtcsh)相比,单引号在Windows命令行上的处理方式不同.

这是一个很小的python程序来演示这个:

import sys
print(sys.argv[1:])
Run Code Online (Sandbox Code Playgroud)

运行它(让我们调用文件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'"]
Run Code Online (Sandbox Code Playgroud)

因此,单引号按字面处理(即不作为特殊字符).在SO中搜索了一下,我发现cmd对参数标记化的这个很好的描述:

从命令窗口调用命令时,命令行参数的标记化不是由cmd.exe(也称为"shell")完成的.大多数情况下,标记化是由新形成的进程的C/C++运行时完成的,但这不一定是这样 - 例如,如果新进程不是用C/C++编写的,或者新进程选择忽略argv和处理自己的原始命令行(例如[GetCommandLine()] [1]).在操作系统级别,Windows将未命名的命令行作为单个字符串传递给新进程.这与大多数*nix shell形成对比,其中shell在将参数传递给新形成的进程之前以一致,可预测的方式对参数进行标记.所有这些意味着您可能会在Windows上的不同程序中遇到极为不同的参数标记化行为,因为单个程序通常会将参数标记化放在自己手中.

如果它听起来像无政府状态,那就是它.但是,由于大量Windows程序确实使用了Microsoft C/C++运行时 argv,因此了解MSVCRT如何标记参数通常很有用.这是一段摘录:

  • 参数由空格分隔,空格可以是空格或制表符.
  • 由双引号括起的字符串被解释为单个参数,而不管其中包含的空格.带引号的字符串可以嵌入参数中.请注意,插入符号(^)不会被识别为转义字符或分隔符.

错误#2

考虑到上述情况,让我们首先解释第二个奇怪的行为(作为一个pass声明的行为),因为它有点简单.由于单引号按字面解释,因此在调用时:

C:\Users\Me>python -m timeit 'map(str,range(100))'
Run Code Online (Sandbox Code Playgroud)

确切的字符串文字'map(str,range(100))'(包含引号)作为语句传递给时间.
所以,Python会看到

"'map(str,range(100))'"
Run Code Online (Sandbox Code Playgroud)

代替

'map(str,range(100))'
Run Code Online (Sandbox Code Playgroud)

作为一个字符串,它并没有真正做任何事情,并给出了一个非常接近pass声明的测量.


错误#1

现在是第一个错误:
正如python timeit模块记录的那样:

可以通过将每一行指定为单独的语句参数来给出多行语句;

所以,在致电时:

C:\Users\Me>python -m timeit '"-".join(map(str, range(100)))'
Run Code Online (Sandbox Code Playgroud)

Python认为["'-.join(map(str,", "range(100)))'"]将语句作为语句传递给timeit,模块将其解释为多行语句:

'"-".join(map(str,
range(100)))'
Run Code Online (Sandbox Code Playgroud)

这个第一行是一个用单引号打开的字符串,但从不关闭,因此,(最后)解释了奇怪的EOL错误.


使用双引号语句来解决问题.

我也尝试过Windows PowerShell,它比cmd.exe更高级,并且表现出与Unix shell相似的行为,但对于我测试的所有语句都没有完全解决问题.
例如,这有效(注意语句中的空格):

PS C:\Users\Me> python -m timeit 'map(str, range(100))'
1000000 loops, best of 3: 0.688 usec per loop
Run Code Online (Sandbox Code Playgroud)

而最初的例子不是:

PS C:\Users\Me\Desktop> python -m timeit '"-".join(map(str, range(100)))'
option -. not recognized
use -h/--help for command line help
Run Code Online (Sandbox Code Playgroud)

(但我还不是很满意.我宁愿做的是让cmdPowerShell作为Unix shell工作,这样我就可以简单地粘贴和编写代码片段.如果有人知道这样做的快捷方式(如果它是可能的话),为了完成答案,那将是非常棒的.)