はじめに
Pythonを使い、WindowsのExplorerでディレクトリを開くには、以下で十分に機能します。
import subprocess
path = r".\Cat"
cmd = ["explorer", path]
subprocess.run(cmd)
print("ok")
しかし、このようにしてExplorerを呼ぶと、呼んだ分だけのExplorerプロセスが残り続けてしまいます。
Popen()で開いてterminate()を実行してもExplorerは終了しません。
そもそもrun()で呼んでいるのにPythonに実行が戻ってきてしまいます。
つまり、Explorerを呼んだプロセス自体は、正常に終了しているのだと推測できます。
import subprocess
path = r".\Cat"
cmd = ["explorer", path]
p = subprocess.Popen(cmd)
p.wait()
p.terminate()
print("ok")
プロセスが残ってしまうのは問題なので、プロセスが残らない方法を調べてみました。
Windows APIでExplorerを呼ぶ関数は、少なくとも2つあります。
使い分けとしては以下になります。
- ディレクトリを開くだけで良い場合、
ShellExecuteExW()を使う - ディレクトリを開いてファイルを選択状態にする場合、
SHOpenFolderAndSelectItems()を使う
環境
Python 3.14.2 (tags/v3.14.2:df79316, Dec 5 2025, 17:18:21) [MSC v.1944 64 bit (AMD64)]
コード
import ctypes as ct
from ctypes import wintypes as wt
Shell32 = ct.windll.Shell32
Ole32 = ct.windll.Ole32
SEE_MASK_DEFAULT = 0x00000000
SEE_MASK_CLASSNAME = 0x00000001
SEE_MASK_CLASSKEY = 0x00000003
SEE_MASK_IDLIST = 0x00000004
SEE_MASK_INVOKEIDLIST = 0x0000000C
SEE_MASK_ICON = 0x00000010
SEE_MASK_HOTKEY = 0x00000020
SEE_MASK_NOCLOSEPROCESS = 0x00000040
SEE_MASK_CONNECTNETDRV = 0x00000080
SEE_MASK_NOASYNC = 0x00000100
SEE_MASK_FLAG_DDEWAIT = 0x00000100
SEE_MASK_DOENVSUBST = 0x00000200
SEE_MASK_FLAG_NO_UI = 0x00000400
SEE_MASK_UNICODE = 0x00004000
SEE_MASK_NO_CONSOLE = 0x00008000
SEE_MASK_ASYNCOK = 0x00100000
SEE_MASK_NOQUERYCLASSSTORE = 0x01000000
SEE_MASK_HMONITOR = 0x00200000
SEE_MASK_NOZONECHECKS = 0x00800000
SEE_MASK_WAITFORINPUTIDLE = 0x02000000
SEE_MASK_FLAG_LOG_USAGE = 0x04000000
SEE_MASK_FLAG_HINST_IS_SITE = 0x08000000
SW_HIDE = 0
SW_SHOWNORMAL = SW_NORMAL = 1
SW_SHOWMINIMIZED = 2
SW_SHOWMAXIMIZED = SW_MAXIMIZE = 3
SW_SHOWNOACTIVATE = 4
SW_SHOW = 5
SW_MINIMIZE = 6
SW_SHOWMINNOACTIVE = 7
SW_SHOWNA = 8
SW_RESTORE = 9
SW_SHOWDEFAULT = 10
SW_FORCEMINIMIZE = 11
SE_ERR_FNF = 2
SE_ERR_PNF = 3
SE_ERR_ACCESSDENIED = 5
SE_ERR_OOM = 8
SE_ERR_DLLNOTFOUND = 32
SE_ERR_SHARE = 26
SE_ERR_ASSOCINCOMPLETE = 27
SE_ERR_DDETIMEOUT = 28
SE_ERR_DDEFAIL = 29
SE_ERR_DDEBUSY = 30
SE_ERR_NOASSOC = 31
# typedef struct _SHELLEXECUTEINFOW {
# DWORD cbSize;
# ULONG fMask;
# HWND hwnd;
# LPCWSTR lpVerb;
# LPCWSTR lpFile;
# LPCWSTR lpParameters;
# LPCWSTR lpDirectory;
# int nShow;
# HINSTANCE hInstApp;
# void *lpIDList;
# LPCWSTR lpClass;
# HKEY hkeyClass;
# DWORD dwHotKey;
# union {
# HANDLE hIcon;
# HANDLE hMonitor;
# } DUMMYUNIONNAME;
# HANDLE hProcess;
# } SHELLEXECUTEINFOW, *LPSHELLEXECUTEINFOW;
class DUMMYUNIONNAME(ct.Union):
_fields_ = (
("hIcon", wt.HANDLE),
("hMonitor", wt.HANDLE),
)
class SHELLEXECUTEINFOW(ct.Structure):
_fields_ = (
("cbSize", wt.DWORD),
("fMask", wt.ULONG),
("hwnd", wt.HWND),
("lpVerb", wt.LPCWSTR),
("lpFile", wt.LPCWSTR),
("lpParameters", wt.LPCWSTR),
("lpDirectory", wt.LPCWSTR),
("nShow", ct.c_int),
("hInstApp", wt.HINSTANCE),
("lpIDlist", ct.c_void_p),
("lpClass", wt.LPCWSTR),
("hkeyClass", wt.HKEY),
("dwHotkey", wt.DWORD),
("dummyUnionName", DUMMYUNIONNAME),
("hProcess", wt.HANDLE),
)
def __init__(
self,
fMask=0,
hwnd=None,
lpVerb=None,
lpFile=None,
lpParameters=None,
lpDirectory=None,
nShow=SW_NORMAL,
hInstApp=None,
lpIDlist=None,
lpClass=None,
hkeyClass=None,
dwHotkey=0,
dummyUnionName=None,
hProcess=None,
):
super().__init__()
self.cbSize = ct.sizeof(SHELLEXECUTEINFOW)
self.fMask = fMask
self.hwnd = hwnd
self.lpVerb = lpVerb
self.lpFile = lpFile
self.lpParameters = lpParameters
self.lpDirectory = lpDirectory
self.nShow = nShow
self.hInstApp = hInstApp
self.lpIDlist = lpIDlist
self.lpClass = lpClass
self.hkeyClass = hkeyClass
self.dwHotkey = dwHotkey
self.dummyUnionName = DUMMYUNIONNAME(dummyUnionName)
self.hProcess = hProcess
def __repr__(self):
return (
f"{self.cbSize}, {self.fMask}, {self.hwnd}, {self.lpVerb}, {self.lpFile}, {self.lpParameters}, "
f"{self.lpDirectory}, {self.nShow}, {self.hInstApp}, {self.lpIDlist}, {self.lpClass}, {self.hkeyClass}, "
f"{self.dwHotkey}, {self.dummyUnionName}, {self.hProcess}"
)
# typedef struct _SHITEMID {
# USHORT cb;
# BYTE abID[1];
# } SHITEMID;
class SHITEMID(ct.Structure):
_fields_ = (
("cb", wt.USHORT),
("abID", wt.BYTE * 1), # 長さ1の配列
)
# typedef struct _ITEMIDLIST {
# SHITEMID mkid;
# } ITEMIDLIST;
class ITEMIDLIST(ct.Structure):
_fields_ = (("mkid", SHITEMID),)
COINIT_APARTMENTTHREADED = 0x2
COINIT_MULTITHREADED = 0x0
COINIT_DISABLE_OLE1DDE = 0x4
COINIT_SPEED_OVER_MEMORY = 0x8
# typedef LONG HRESULT;
HRESULT = wt.LONG
# BOOL ShellExecuteExW(
# [in, out] SHELLEXECUTEINFOW *pExecInfo
# );
def callShellExecuteExW(lpFile, lpDirectory=None, lpVerb="open"):
hr = CoInitializeEx(None, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)
if hr not in (0, 1):
msg = f"CoInitialize failed: HRESULT 0x{hr & 0xFFFFFFFF:08X}"
raise OSError(msg)
ShExecInfo = SHELLEXECUTEINFOW(lpVerb=lpVerb, lpFile=lpFile, lpDirectory=lpDirectory)
res = ShellExecuteExW(ShExecInfo)
CoUninitialize()
if not res:
raise ct.WinError()
return ShExecInfo.hInstApp > SE_ERR_DLLNOTFOUND
# SHSTDAPI SHOpenFolderAndSelectItems(
# [in] PCIDLIST_ABSOLUTE pidlFolder,
# UINT cidl,
# [in, optional] PCUITEMID_CHILD_ARRAY apidl,
# DWORD dwFlags
# );
SHSTDAPI = HRESULT
SHOpenFolderAndSelectItems = Shell32.SHOpenFolderAndSelectItems
SHOpenFolderAndSelectItems.restype = SHSTDAPI
SHOpenFolderAndSelectItems.argtypes = (
ct.POINTER(ITEMIDLIST),
wt.UINT,
ct.POINTER(ct.POINTER(ITEMIDLIST)),
wt.DWORD,
)
OFASI_EDIT = 0x0001
OFASI_OPENDESKTOP = 0x0002
# HRESULT
S_OK = 0x00000000 # Operation successful
E_NOTIMPL = 0x80004001 # Not implemented
E_NOINTERFACE = 0x80004002 # No such interface supported
E_POINTER = 0x80004003 # Pointer that is not valid
E_ABORT = 0x80004004 # Operation aborted
E_FAIL = 0x80004005 # Unspecified failure
E_UNEXPECTED = 0x8000FFFF # Unexpected failure
E_ACCESSDENIED = 0x80070005 # General access denied error
E_HANDLE = 0x80070006 # Handle that is not valid
E_OUTOFMEMORY = 0x8007000E # Failed to allocate necessary memory
E_INVALIDARG = 0x80070057 # One or more arguments are not valid
# #ifdef UNICODE
# typedef LPCWSTR PCTSTR;
# #else
# typedef LPCSTR PCTSTR;
# #endif
# PIDLIST_ABSOLUTE ILCreateFromPath(
# [in] PCTSTR pszPath
# );
ILCreateFromPath = Shell32.ILCreateFromPath
ILCreateFromPath.restype = ct.POINTER(ITEMIDLIST)
ILCreateFromPath.argtypes = (wt.LPCWSTR,)
# void ILFree(
# [in] PIDLIST_RELATIVE pidl
# );
ILFree = Shell32.ILFree
ILFree.restype = None
ILFree.argtypes = (ct.POINTER(ITEMIDLIST),)
# void CoTaskMemFree(
# [in, optional] _Frees_ptr_opt_ LPVOID pv
# );
CoTaskMemFree = Ole32.CoTaskMemFree
CoTaskMemFree.restype = None
CoTaskMemFree.argtypes = (wt.LPVOID,)
# SHSTDAPI SHParseDisplayName(
# [in] PCWSTR pszName,
# [in, optional] IBindCtx *pbc,
# [out] PIDLIST_ABSOLUTE *ppidl,
# [in] SFGAOF sfgaoIn,
# [out, optional] SFGAOF *psfgaoOut
# );
SFGAOF = wt.ULONG
SFGAO_CANCOPY = 0x00000001
SFGAO_CANMOVE = 0x00000002
SFGAO_CANLINK = 0x00000004
SFGAO_STORAGE = 0x00000008
SFGAO_CANRENAME = 0x00000010
SFGAO_CANDELETE = 0x00000020
SFGAO_HASPROPSHEET = 0x00000040
SFGAO_DROPTARGET = 0x00000100
SFGAO_CAPABILITYMASK = 0x00000177
SFGAO_SYSTEM = 0x00001000
SFGAO_ENCRYPTED = 0x00002000
SFGAO_ISSLOW = 0x00004000
SFGAO_GHOSTED = 0x00008000
SFGAO_LINK = 0x00010000
SFGAO_SHARE = 0x00020000
SFGAO_READONLY = 0x00040000
SFGAO_HIDDEN = 0x00080000
SFGAO_DISPLAYATTRMASK = 0x000FC000 # Do not use.
SFGAO_NONENUMERATED = 0x00100000
SFGAO_NEWCONTENT = 0x00200000
# SFGAO_CANMONIKER # Not supported.
# SFGAO_HASSTORAGE # Not supported.
SFGAO_STREAM = 0x00400000
SFGAO_STORAGEANCESTOR = 0x00800000
SFGAO_VALIDATE = 0x01000000
SFGAO_REMOVABLE = 0x02000000
SFGAO_COMPRESSED = 0x04000000
SFGAO_BROWSABLE = 0x08000000
SFGAO_FILESYSANCESTOR = 0x10000000
SFGAO_FOLDER = 0x20000000
SFGAO_FILESYSTEM = 0x40000000
SFGAO_STORAGECAPMASK = 0x70C50008
SFGAO_HASSUBFOLDER = 0x80000000
SFGAO_CONTENTSMASK = 0x80000000
SFGAO_PKEYSFGAOMASK = 0x81044000
SHParseDisplayName = Shell32.SHParseDisplayName
SHParseDisplayName.restype = SHSTDAPI
SHParseDisplayName.argtypes = (
wt.LPCWSTR,
ct.c_void_p, # IBindCtx,
ct.POINTER(ct.POINTER(ITEMIDLIST)),
SFGAOF,
ct.POINTER(SFGAOF),
)
# void CoUninitialize();
CoUninitialize = Ole32.CoUninitialize
CoUninitialize.restype = None
CoUninitialize.argtypes = ()
def getPidList(path):
path = pathlib.Path(path) if isinstance(path, str) else path
if not path.exists():
msg = f"{path} not Found"
raise FileNotFoundError(msg)
pidl = ct.pointer(ITEMIDLIST())
sfgao = SFGAOF(0)
hr = SHParseDisplayName(str(path), None, ct.byref(pidl), 0, ct.byref(sfgao))
if hr != S_OK:
msg = f"SHParseDisplayName failed for {path!r}: HRESULT 0x{hr & 0xFFFFFFFF:08X}"
raise OSError(msg)
return pidl
def callSHOpenFolderAndSelectItems(pathDir, pathFiles):
hr = CoInitializeEx(None, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)
if hr not in (0, 1):
msg = f"CoInitialize failed: HRESULT 0x{hr & 0xFFFFFFFF:08X}"
raise OSError(msg)
dwFlags = 0
if pathDir is None:
pidlDir = None
dwFlags = OFASI_OPENDESKTOP
else:
pidlDir = getPidList(pathDir)
try:
pidlFiles = [getPidList(file) for file in pathFiles]
except (OSError, FileNotFoundError):
CoTaskMemFree(pidlDir)
CoUninitialize()
raise
count = len(pidlFiles)
Array_ITEMIDLIST = ct.POINTER(ITEMIDLIST) * count
arr = Array_ITEMIDLIST(*pidlFiles)
hr = SHOpenFolderAndSelectItems(pidlDir, count, arr, dwFlags)
if hr != S_OK:
msg = f"SHOpenFolderAndSelectItems failed: HRESULT 0x{hr & 0xFFFFFFFF:08X}"
raise OSError(msg)
CoTaskMemFree(pidlDir)
for pidl in pidlFiles:
CoTaskMemFree(pidl)
CoUninitialize()
解説
コードには使っていませんが、関連する定数定義も記述したので非常に長くなりました。
そのため、要点だけを解説します。
ShellExecuteExW
def callShellExecuteExW(lpFile, lpDirectory=None, lpVerb="open"):
hr = CoInitializeEx(None, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)
if hr not in (0, 1):
msg = f"CoInitialize failed: HRESULT 0x{hr & 0xFFFFFFFF:08X}"
raise OSError(msg)
ShExecInfo = SHELLEXECUTEINFOW(lpVerb=lpVerb, lpFile=lpFile, lpDirectory=lpDirectory)
res = ShellExecuteExW(ShExecInfo)
CoUninitialize()
if not res:
raise ct.WinError()
return ShExecInfo.hInstApp > SE_ERR_DLLNOTFOUND
ShellExecuteExWを呼ぶには、コンポーネント オブジェクト モデル(COM)の初期化が必要です。
それを、CoInitializeEx()で行います。
次に、ディレクトリを開くために、SHELLEXECUTEINFOW構造体に値を設定します。
正直なところ良く分からないパラメータも多いですが、今回は、ディレクトリ(ファイル)を開く事だけを考えます。
まず、lpVerbには、"open"を指定します。
lpFileには、開きたいディレクトリ(ファイル)パスを指定します。
作業ディレクトリを明示的に指定したい場合には、lpDirectoryを指定します。
設定したSHELLEXECUTEINFOW構造体を渡してShellExecuteExW()を呼べばディレクトリ(ファイル)が開きます。
CoInitializeEx()を呼んだら、CoUninitialize()で後始末をする必要があるので呼びます。
ShellExecuteExWの使い方
ディレクトリを開く場合は、以下のようにします。
path = r".\someDirectory"
callShellExecuteExW(path)
# callShellExecuteExW(path, path)
# callShellExecuteExW(None, path)
内部的な違いが分かりませんが、
lpFile=Path, lpDirecoty=NonelpFile=None, lpDirecoty=PathlpFile=Path, lpDirecoty=Path
は全て同じ動作になります。
記事の目的とは異なりますが、以下のようにlpFileにファイルへのパスを指定すると、拡張子に対応した既定のアプリがファイルを開きます。
path = r".\someDirectory\someFile.txt"
callShellExecuteExW(path)
SHOpenFolderAndSelectItems
def getPidList(path):
path = pathlib.Path(path) if isinstance(path, str) else path
if not path.exists():
msg = f"{path} not Found"
raise FileNotFoundError(msg)
pidl = ct.pointer(ITEMIDLIST())
sfgao = SFGAOF(0)
hr = SHParseDisplayName(str(path), None, ct.byref(pidl), 0, ct.byref(sfgao))
if hr != S_OK:
msg = f"SHParseDisplayName failed for {path!r}: HRESULT 0x{hr & 0xFFFFFFFF:08X}"
raise OSError(msg)
return pidl
def callSHOpenFolderAndSelectItems(pathDir, pathFiles):
hr = CoInitializeEx(None, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)
if hr not in (0, 1):
msg = f"CoInitialize failed: HRESULT 0x{hr & 0xFFFFFFFF:08X}"
raise OSError(msg)
dwFlags = 0
if pathDir is None:
pidlDir = None
dwFlags = OFASI_OPENDESKTOP
else:
pidlDir = getPidList(pathDir)
try:
pidlFiles = [getPidList(file) for file in pathFiles]
except (OSError, FileNotFoundError):
CoTaskMemFree(pidlDir)
CoUninitialize()
raise
count = len(pidlFiles)
Array_ITEMIDLIST = ct.POINTER(ITEMIDLIST) * count
arr = Array_ITEMIDLIST(*pidlFiles)
hr = SHOpenFolderAndSelectItems(pidlDir, count, arr, dwFlags)
if hr != S_OK:
msg = f"SHOpenFolderAndSelectItems failed: HRESULT 0x{hr & 0xFFFFFFFF:08X}"
raise OSError(msg)
CoTaskMemFree(pidlDir)
for pidl in pidlFiles:
CoTaskMemFree(pidl)
CoUninitialize()
SHOpenFolderAndSelectItems()を呼ぶにも、COMの初期化が必要なのでCoInitializeEx()を実行します。
ディレクトリを開くには、そのディレクトリを表すITEMIDLISTが必要です。
これは、ILCreateFromPath()かSHParseDisplayName()で取得できます。
ILCreateFromPath()の方が単純で分かりやすいのですが、Microsoftは、SHParseDisplayName()を使用するように推奨しているようです。
SHILCreateFromPath()を使わずにSHParseDisplayName()を使えとの記述は見つけましたが、ILCreateFromPath()が非推奨という記述は見つけられませんでした。
いずれにせよ、SHParseDisplayName()の方が対応範囲が広いのでこちらを使って損はありません。
SHOpenFolderAndSelectItemsの引数のpidlFolderには、開きたいディレクトリのITEMIDLISTのポインターを渡します。
apidlには、そのディレクトリに存在するファイルのITEMIDLISTの配列のポインターを渡します。
ITEMIDLISTは、使い終わったら解放しなければいけません。
それには、CoTaskMemFree()を使います。
古いWindowsでは、ILFree()を使っていたようですが、現状は使わないようです。
最後に、CoInitializeEx()の後始末として、CoUninitialize()を実行します。
SHOpenFolderAndSelectItemsの注意点
このコードを実行するとエラーも出ずにディレクトリが開くのですが、稀に、最小化・最大化・閉じるボタンが表示されない事があります。
ネットで検索すると出てくる他のコードも同じように表示されない事があるので自分のコードに起因するバグとも言い切れません。
これの対処法が分かれば良いのですが。
SHOpenFolderAndSelectItemsの使い方
以下のようにすると、someFile.txtを選択状態にしてsomeDirectoryが開きます。
また、複数のファイルを渡すと、それらすべてが選択状態で開きます。
dir = r".\someDirectory"
files = [r".\someDirectory\someFile.txt"]
callSHOpenFolderAndSelectItems(dir, files)
以下のようにすると、someDirectoryを選択状態にしてsomeDirectoryの親ディレクトリが開きます。
dir = r".\someDirectory"
files = []
callSHOpenFolderAndSelectItems(dir, files)
最後に、これも記事の目的とは違うのですが、pathにデスクトップへのパスを指定して、filesにデスクトップ上のファイルへのパスを指定すると、デスクトップのファイルが選択状態になります。
path = r"...\デスクトップ"
files = [r"...\デスクトップ\someFile.txt"]
callSHOpenFolderAndSelectItems(path, files)
pathをNoneにしても同じように、デスクトップのファイルが選択状態になります。
files = [r"...\デスクトップ\someFile.txt"]
callSHOpenFolderAndSelectItems(None, files)
参考