2

我已经使用 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

这相当令人困惑,因为我没有在字符串中插入任何换行符 - 相反,我实际上直接从 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 shell 上粘贴了相同的行并且它们按预期工作。

由于我使用 Python 2.7 和 3.3 得到完全相同的结果(另外,该模块是用纯 Python 编写的,并且已经存在了很长时间)我确信这与 Python 无关,但是 Windows 命令提示符,反而。

那么,为什么这种奇怪的行为会准确发生,我该如何解决呢?

4

1 回答 1

13

tl;博士

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

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

详细解释

bashtcsh等 Unix shell 相比,单引号在 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'"]

因此,单引号按字面意思处理(即不作为特殊字符)。在 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))'

确切的字符串文字'map(str,range(100))'(包括引号)作为语句传递给时间。
所以,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 错误。


解决方案

对语句使用双引号可以解决问题。

我也尝试过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

而最初的例子没有:

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

(不过,我还不是很满意。我宁愿让cmdPowerShell作为 Unix shell 工作,这样我就可以简单地粘贴和计时代码片段。如果有人知道一种快速而简单的方法来做到这一点(如果可能的话),为了完成答案,那就太棒了。)

于 2014-06-02T04:07:10.007 回答