25

検討:

>>> timeit.timeit('from win32com.client import Dispatch', number=100000)
0.18883283882571789
>>> timeit.timeit('import win32com.client', number=100000)
0.1275979248277963

モジュール全体ではなく Dispatch 関数だけをインポートすると、かなり時間がかかります。単一の関数を取得するためのオーバーヘッドが非常に悪い理由を誰かが説明できますか? ありがとう!

4

3 に答える 3

33

その理由は次のとおりです。

from win32com.client import Dispatch

次と同等です。

import win32com.client              #import the whole module first
Dispatch = win32com.client.Dispatch #assign the required attributes to global variables
del win32com                        #remove the reference to module object

ただしfrom win32com.client import Dispatch、独自の利点があります。たとえばwin32com.client.Dispatch、コードで複数回使用している場合は、変数に割り当てることをお勧めします。これにより、ルックアップの数を減らすことができます。それ以外の場合、 を呼び出すたびに、win32com.client.Dispatch()最初に search を検索しwin32com、次にclientinsideを検索しwin32com、最後にDispatchinsideを検索しwin32com.clientます。


バイトコード比較:

from os.path import splitext バイトコードから、 に必要なステップ数が単純な よりも多いことは明らかですimport

>>> def func1():
    from os.path import splitext
...     
>>> def func2():
    import os.path
...     
>>> import dis
>>> dis.dis(func1)
  2           0 LOAD_CONST               1 (-1)
              3 LOAD_CONST               2 (('splitext',))
              6 IMPORT_NAME              0 (os.path)
              9 IMPORT_FROM              1 (splitext)
             12 STORE_FAST               0 (splitext)
             15 POP_TOP             
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE        
>>> dis.dis(func2)
  2           0 LOAD_CONST               1 (-1)
              3 LOAD_CONST               0 (None)
              6 IMPORT_NAME              0 (os.path)
              9 STORE_FAST               0 (os)
             12 LOAD_CONST               0 (None)
             15 RETURN_VALUE     

モジュールのキャッシュ:

Python はインポートされたモジュールをキャッシュするため、引き続きモジュールfrom os.path import splitextにアクセスできることに注意してください。ossys.modules

ドキュメントから:

注 効率上の理由から、各モジュールはインタープリター セッションごとに 1 回だけインポートされます。したがって、モジュールを変更した場合は、インタープリターを再起動する必要があります。または、対話的にテストしたいモジュールが 1 つだけの場合はreload()reload(modulename).

デモ:

import sys
from os.path import splitext
try:
    print os
except NameError:
    print "os not found"
try:
    print os.path
except NameError:
    print "os.path is not found"

print sys.modules['os']

出力:

os not found
os.path is not found
<module 'os' from '/usr/lib/python2.7/os.pyc'>

タイミング比較:

$ python -m timeit -n 1 'from os.path import splitext'
1 loops, best of 3: 5.01 usec per loop
$ python -m timeit -n 1 'import os.path'
1 loops, best of 3: 4.05 usec per loop
$ python -m timeit -n 1 'from os import path'
1 loops, best of 3: 5.01 usec per loop
$ python -m timeit -n 1 'import os'
1 loops, best of 3: 2.86 usec per loop
于 2013-07-12T00:21:49.037 に答える
11

必要な名前を取得するには、モジュール全体をインポートする必要があります...また、OSがモジュールをキャッシュしているため、その後の.pycファイルへのアクセスが高速になります。

于 2013-07-12T00:19:48.627 に答える
2

ここでの主な問題は、コードがタイミングだと思っているタイミングを合わせていないことです。 timieit.timeit()ステートメントをループで 100000 回実行importしますが、多くても最初の反復で実際にインポートが実行されます。他のすべての反復では、 でモジュールを検索し、モジュールのグローバルで名前をsys.modules検索して、Dispatchこの名前をインポート モジュールのグローバルに追加するだけです。したがって、基本的には辞書操作のみであり、非常に安価な辞書操作と比較して相対的な影響が大きいため、バイト コードの小さなバリエーションが目に見えるようになります。

一方、実際にモジュールをインポートするのにかかる時間を測定すると、2 つのアプローチの間に違いは見られません。どちらの場合も、この時間は実際のインポートによって完全に支配されており、違いはいじっています。名前辞書を使用すると、無視できるようになります。sys.modules各反復でモジュールを削除することにより、再インポートを強制できます。

In [1]: import sys

In [2]: %timeit from os import path; del sys.modules["os"]
1000 loops, best of 3: 248 us per loop

In [3]: %timeit import os.path; del sys.modules["os"]
1000 loops, best of 3: 248 us per loop

In [4]: %timeit from os import path
1000000 loops, best of 3: 706 ns per loop

In [5]: %timeit import os.path
1000000 loops, best of 3: 444 ns per loop
于 2013-07-12T19:18:41.493 に答える