如何使用Python查找Windows常用应用程序数据文件夹?

31

我希望我的应用程序可以存储一些数据供所有用户访问。使用Python,我该如何找到该存储数据的位置?


这是为哪个操作系统设计的? - tgray
2
@tgray,这在问题标题中已经提到了。 - Robert S.
17
有点尴尬... - tgray
6个回答

45
如果您不想添加像winpaths这样的第三方模块的依赖项,我建议使用Windows中已经可用的环境变量: 具体来说,您可能需要使用ALLUSERSPROFILE来获取公共用户配置文件文件夹的位置,该位置是应用程序数据目录所在的位置。

e.g.:

C:\> python -c "import os; print os.environ['ALLUSERSPROFILE']"
C:\Documents and Settings\All Users

编辑:看了看winpaths模块,它使用ctypes,因此如果您想仅使用代码的相关部分而不安装winpath,则可以使用此代码(显然为了简洁省略了一些错误检查)。

import ctypes
from ctypes import wintypes, windll

CSIDL_COMMON_APPDATA = 35

_SHGetFolderPath = windll.shell32.SHGetFolderPathW
_SHGetFolderPath.argtypes = [wintypes.HWND,
                            ctypes.c_int,
                            wintypes.HANDLE,
                            wintypes.DWORD, wintypes.LPCWSTR]


path_buf = wintypes.create_unicode_buffer(wintypes.MAX_PATH)
result = _SHGetFolderPath(0, CSIDL_COMMON_APPDATA, 0, 0, path_buf)
print path_buf.value

示例运行:

C:\> python get_common_appdata.py
C:\Documents and Settings\All Users\Application Data

1
如果用户使用的是非英语版本的Windows呢?ALLUSERSPROFILE仍然适用吗? - Robert S.
那么相关的位置是什么?是由 _SHGetFolderPath 提供的 C:\Documents and Settings\All Users\Application Data\MySoftware 还是由 os.environ['ALLUSERSPROFILE'] 提供的 C:\Documents and Settings\All Users\MySoftware?需要 \Application Data\\(第一个解决方案)还是不需要(第二个解决方案)? - Basj
7
使用ctypes.create_unicode_buffer,它在PY2和PY3中都可以使用。在PY2中,由于使用了from ctypes import *,它只是意外地出现在wintypes中。 - Eryk Sun
@Basj,程序应该使用SHGetFolderPathW"%ALLUSERSPROFILE%\Application Data"只应在批处理文件中使用。Vista+中首选的环境变量是ProgramDataPublic - Eryk Sun
@eryksun "%ALLUSERSPROFILE%\Application Data" 这将在 Vista/7/8 上显示为 C:\ProgramData\Application Data - Basj
显示剩余2条评论

15

来自http://snipplr.com/view.php?codeview&id=7354

homedir = os.path.expanduser('~')

# ...works on at least windows and linux. 
# In windows it points to the user's folder 
#  (the one directly under Documents and Settings, not My Documents)


# In windows, you can choose to care about local versus roaming profiles.
# You can fetch the current user's through PyWin32.
#
# For example, to ask for the roaming 'Application Data' directory:
#  (CSIDL_APPDATA asks for the roaming, CSIDL_LOCAL_APPDATA for the local one)
#  (See microsoft references for further CSIDL constants)
try:
    from win32com.shell import shellcon, shell            
    homedir = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, 0, 0)

except ImportError: # quick semi-nasty fallback for non-windows/win32com case
    homedir = os.path.expanduser("~")

要获取所有用户的应用程序数据目录,而不是当前用户,请使用 shellcon.CSIDL_COMMON_APPDATA 而不是 shellcon.CSIDL_APPDATA

OP要求所有用户都可以访问的数据。 - Jason S
1
@Jason S:我已经添加了一个关于所有用户应用数据的注释。 - Craig McQueen
常量值对应于MS网站上记录的Environment.SpecialFolder枚举:https://learn.microsoft.com/en-us/dotnet/api/system.environment.specialfolder?redirectedfrom=MSDN&view=net-5.0 - Martin Vyskočil

10

+1 这看起来非常有用,谢谢,但是Jay的答案可以满足我的需求,而且没有额外的依赖。 - David Sykes

3

自从SHGetFolderPath被弃用后,在Vista及更高版本中你也可以使用SHGetKnownFolderPath。这还能让你查找到比SHGetFolderPath更多的路径。以下是一个简化的示例(完整代码在Gist上可用):

import ctypes, sys
from ctypes import windll, wintypes
from uuid import UUID

class GUID(ctypes.Structure):   # [1]
    _fields_ = [
        ("Data1", wintypes.DWORD),
        ("Data2", wintypes.WORD),
        ("Data3", wintypes.WORD),
        ("Data4", wintypes.BYTE * 8)
    ] 

    def __init__(self, uuid_):
        ctypes.Structure.__init__(self)
        self.Data1, self.Data2, self.Data3, self.Data4[0], self.Data4[1], rest = uuid_.fields
        for i in range(2, 8):
            self.Data4[i] = rest>>(8 - i - 1)*8 & 0xff

class FOLDERID:     # [2]
    LocalAppData            = UUID('{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}')
    LocalAppDataLow         = UUID('{A520A1A4-1780-4FF6-BD18-167343C5AF16}')
    RoamingAppData          = UUID('{3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}')

class UserHandle:   # [3]
    current = wintypes.HANDLE(0)
    common  = wintypes.HANDLE(-1)

_CoTaskMemFree = windll.ole32.CoTaskMemFree     # [4]
_CoTaskMemFree.restype= None
_CoTaskMemFree.argtypes = [ctypes.c_void_p]

_SHGetKnownFolderPath = windll.shell32.SHGetKnownFolderPath     # [5] [3]
_SHGetKnownFolderPath.argtypes = [
    ctypes.POINTER(GUID), wintypes.DWORD, wintypes.HANDLE, ctypes.POINTER(ctypes.c_wchar_p)
] 

class PathNotFoundException(Exception): pass

def get_path(folderid, user_handle=UserHandle.common):
    fid = GUID(folderid) 
    pPath = ctypes.c_wchar_p()
    S_OK = 0
    if _SHGetKnownFolderPath(ctypes.byref(fid), 0, user_handle, ctypes.byref(pPath)) != S_OK:
        raise PathNotFoundException()
    path = pPath.value
    _CoTaskMemFree(pPath)
    return path

common_data_folder = get_path(FOLDERID.RoamingAppData)

# [1] http://msdn.microsoft.com/en-us/library/windows/desktop/aa373931.aspx
# [2] http://msdn.microsoft.com/en-us/library/windows/desktop/dd378457.aspx
# [3] http://msdn.microsoft.com/en-us/library/windows/desktop/bb762188.aspx
# [4] http://msdn.microsoft.com/en-us/library/windows/desktop/ms680722.aspx
# [5] http://www.themacaque.com/?p=954

3

由于与非美国版的Windows以及Vista不兼容,先前的答案已被删除。

编辑: 为了扩展Out Into Space的答案,您可以使用winpaths.get_common_appdata函数。您可以使用easy_install winpaths获取winpaths,也可以转到pypi页面http://pypi.python.org/pypi/winpaths/,并下载.exe安装程序。


不幸的是,这在非英语操作系统上无法维持。 - Jeff
请您能否更加具体明确一些? - tgray
我相信他的意思是,“Documents and Settings”在非英文版本的XP中已被本地化。此外,可以通过注册表更改此文件夹的位置。最后,Vista将此文件夹命名为“Users”。教训:不要硬编码它。 - Ben Blank
但是使用winpaths.get_common_appdata会使用注册表值,对吗?这种方法应该是跨语言的。 - tgray
我建议删除答案的第一部分,硬编码这个值对于Vista或任何非英语XP都是错误的。 - David Sykes

3
您可以使用os.environ字典在os模块中访问所有操作系统环境变量。但是,选择要使用的键可能有些棘手。特别是在使用这些路径时,您应该注意国际化(即非英语)版本的Windows。 os.environ['ALLUSERSPROFILE']应该为您提供计算机上所有用户的根目录,但是请小心不要硬编码子目录名称,例如“Application Data”,因为这些目录在非英语版本的Windows上不存在。为此,您可能需要进行一些研究,以了解您可以期望设置ALLUSERSPROFILE环境变量的Windows版本(我自己不知道-它可能是通用的)。
我的XP机器上有一个COMMONAPPDATA环境变量,它指向All Users\Application Data文件夹,但我的Win2K3系统没有这个环境变量。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接