2

私は長い間 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 コマンド プロンプトは、代わりは。

では、なぜこの奇妙な動作が正確に発生し、どのように修正すればよいのでしょうか?

4

1 に答える 1

13

tl;dr

timeit モジュールに渡されるステートメントには二重引用符を使用します。
例:

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

詳細な説明

bashtcshなどの Unix シェルとは対照的に、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(別名「シェル」) によって行われません。ほとんどの場合、トークン化は新しく形成されたプロセスの C/C++ ランタイムによって行われますが、必ずしもそうであるとは限りません。たとえば、新しいプロセスが C/C++ で記述されていない場合や、新しいプロセスが無視することを選択した場合などです。argvそれ自体の生のコマンドラインを処理します ([GetCommandLine()][1] などを使用)。OS レベルでは、Windows はトークン化されていないコマンド ラインを単一の文字列として新しいプロセスに渡します。これは、シェルが引数を新しく形成されたプロセスに渡す前に、一貫性のある予測可能な方法でトークン化するほとんどの *nix シェルとは対照的です。これはすべて、個々のプログラムが引数のトークン化を独自の手に委ねることが多いため、Windows 上のさまざまなプログラム間で引数のトークン化の動作が大幅に異なる可能性があることを意味します。

無政府状態のように聞こえる場合は、そのようなものです。ただし、多数の Windows プログラムMicrosoft C/C++ ランタイムの argvを利用しているため、一般に、MSVCRT が引数をトークン化する方法を理解しておくと役立ちます。ここに抜粋があります:

  • 引数は空白 (スペースまたはタブ) で区切られます。
  • 二重引用符で囲まれた文字列は、含まれる空白に関係なく、1 つの引数として解釈されます。引用符で囲まれた文字列は、引数に埋め込むことができます。キャレット (^) はエスケープ文字または区切り文字として認識されないことに注意してください。

エラー #2

上記を念頭に置いて、最初に 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 エラーが説明されます。


解決

ステートメントに二重引用符を使用すると、問題が解決します。

また、 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 シェルとして機能させることです。これにより、コード スニペットを簡単に貼り付けて時間を計ることができます。誰かがこれを行うための簡単な方法を知っている場合(可能であれば)答えを完成させるために、それは素晴らしいことです。)

于 2014-06-02T04:07:10.007 に答える