使用Windows复制对话框进行复制

11
我目前正在使用shutil.copy2()复制大量的图像文件和文件夹(大小在0.5到5GB之间)。Shutil可以正常工作,但速度很慢。我想知道是否有一种方法可以将这个信息传递给Windows进行复制并给我它的标准传输对话框。你知道的,那个...

http://www.top-windows-tutorials.com/images/file-copy.jpg

很多时候,我的脚本需要花费比标准Windows复制两倍的时间,这让我担心我的Python解析器在运行复制时会出现问题。我运行多次复制过程,并希望缩短时间。


1
你有没有实际计时过使用Python和Windows资源管理器传输同一个文件的时间?我很难相信Python会比Windows资源管理器更慢。 - Nathaniel Waisbrot
是的,我进行了并排测试。由于它是通过网络进行的,因此可能会受到网络速度的干扰,但我该如何使用shutil找到我的传输速度呢? - tylerART
你可以在Python中使用time.clock()来获取传输时间,但是你需要使用秒表来计时Explorer。我的假设是Python和Explorer都会调用相同的库来执行复制操作,但是Explorer感觉更快,因为它有进度条,也许还因为它给出了一些不正确的时间估计。如果你同时运行两个程序并看到巨大的差异,那就非常有趣了! - Nathaniel Waisbrot
我在网上找到了一些关于shutil速度较慢的帖子...也许我会在某个时候进行更科学的测试。无论如何,我仍然想将副本转储到Windows,这样我就不必等待复制完成才能继续脚本。也许这是一个多线程问题? - tylerART
经过本地和不同日期在我们的网络上测试后,我发现shutil速度与其一样快。 我猜这可能是网络流量问题。 谢谢你的帮助。 - tylerART
4个回答

5
如果你的目标是一个漂亮的复制对话框,Windows API函数SHFileOperation可以实现。pywin32包有它的Python绑定,ctypes也是一个选择(搜索“SHFileOperation ctypes”可以找到示例)。
以下是我使用pywin32编写的(经过轻微测试的)示例:
import os.path
from win32com.shell import shell, shellcon


def win32_shellcopy(src, dest):
    """
    Copy files and directories using Windows shell.

    :param src: Path or a list of paths to copy. Filename portion of a path
                (but not directory portion) can contain wildcards ``*`` and
                ``?``.
    :param dst: destination directory.
    :returns: ``True`` if the operation completed successfully,
              ``False`` if it was aborted by user (completed partially).
    :raises: ``WindowsError`` if anything went wrong. Typically, when source
             file was not found.

    .. seealso:
        `SHFileperation on MSDN <http://msdn.microsoft.com/en-us/library/windows/desktop/bb762164(v=vs.85).aspx>`
    """
    if isinstance(src, basestring):  # in Py3 replace basestring with str
        src = os.path.abspath(src)
    else:  # iterable
        src = '\0'.join(os.path.abspath(path) for path in src)

    result, aborted = shell.SHFileOperation((
        0,
        shellcon.FO_COPY,
        src,
        os.path.abspath(dest),
        shellcon.FOF_NOCONFIRMMKDIR,  # flags
        None,
        None))

    if not aborted and result != 0:
        # Note: raising a WindowsError with correct error code is quite
        # difficult due to SHFileOperation historical idiosyncrasies.
        # Therefore we simply pass a message.
        raise WindowsError('SHFileOperation failed: 0x%08x' % result)

    return not aborted

如果您将上面的标志设置为shellcon.FOF_SILENT | shellcon.FOF_NOCONFIRMATION | shellcon.FOF_NOERRORUI | shellcon.FOF_NOCONFIRMMKDIR,您也可以在“静默模式”(无对话框、无确认窗口、无错误弹出窗口)下执行相同的复制操作。有关详细信息,请参见SHFILEOPSTRUCT


您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Mr_and_Mrs_D
我又在想,src = os.path.abspath(src) 是否应该改为 src = os.path.abspath(src) + '\0'(下面的join也一样)? - Mr_and_Mrs_D
OSError: SHFileOperation 失败:0x000000b7 - CS QGB

2

更新:请查看

把它封装成一个库会更好……借助以上答案的帮助,我能够在Windows 7上按照以下步骤使其工作。

import pythoncom
from win32com.shell import shell,shellcon

def win_copy_files(src_files,dst_folder):           
        # @see IFileOperation
        pfo = pythoncom.CoCreateInstance(shell.CLSID_FileOperation,None,pythoncom.CLSCTX_ALL,shell.IID_IFileOperation)

        # Respond with Yes to All for any dialog
        # @see http://msdn.microsoft.com/en-us/library/bb775799(v=vs.85).aspx
        pfo.SetOperationFlags(shellcon.FOF_NOCONFIRMATION)

        # Set the destionation folder
        dst = shell.SHCreateItemFromParsingName(dst_folder,None,shell.IID_IShellItem)

        for f in src_files:
                src = shell.SHCreateItemFromParsingName(f,None,shell.IID_IShellItem)
                pfo.CopyItem(src,dst) # Schedule an operation to be performed


        # @see http://msdn.microsoft.com/en-us/library/bb775780(v=vs.85).aspx
        success = pfo.PerformOperations()

        # @see sdn.microsoft.com/en-us/library/bb775769(v=vs.85).aspx
        aborted = pfo.GetAnyOperationsAborted()
        return success and not aborted


files_to_copy = [r'C:\Users\jrm\Documents\test1.txt',r'C:\Users\jrm\Documents\test2.txt']
dest_folder = r'C:\Users\jrm\Documents\dst'
win_copy_files(files_to_copy,dest_folder)

这里的参考资料也非常有帮助:http://timgolden.me.uk/pywin32-docs/html/com/win32com/HTML/QuickStartClientCom.html


很抱歉回复晚了,但这看起来非常有前途。我运行时出现了一个错误,说 shell 模块没有 CLSID_FileOperation 属性。 - tylerART
CLSID_FileOperation不在pywin32的pip版本中。您是否有pywin32版本218.4+?请参见https://github.com/frmdstryr/pywinutils。 - frmdstryr
CLSID_FileOperation 在 pywin 220 中。 - Mr_and_Mrs_D
实际上它是在219中添加的:http://pywin32.hg.sourceforge.net/hgweb/pywin32/pywin32/raw-file/tip/CHANGES.txt。然而,显然有一些功能缺失-例如,我无法获取IFileSystemBindData接口的任何常量。 - Mr_and_Mrs_D

1

*bump* Windows 10!

在您的帮助下和Virgil Dupras' send2trash
我只使用ctypes做了一个纯Python版本:

import os
import ctypes
from ctypes import wintypes


class _SHFILEOPSTRUCTW(ctypes.Structure):
    _fields_ = [("hwnd", wintypes.HWND),
                ("wFunc", wintypes.UINT),
                ("pFrom", wintypes.LPCWSTR),
                ("pTo", wintypes.LPCWSTR),
                ("fFlags", ctypes.c_uint),
                ("fAnyOperationsAborted", wintypes.BOOL),
                ("hNameMappings", ctypes.c_uint),
                ("lpszProgressTitle", wintypes.LPCWSTR)]


def win_shell_copy(src, dst):
    """
    :param str src: Source path to copy from. Must exist!
    :param str dst: Destination path to copy to. Will be created on demand.
    :return: Success of the operation. False means is was aborted!
    :rtype: bool
    """
    if not os.path.exist(src):
        print('No such source "%s"' % src)
        return False

    src_buffer = ctypes.create_unicode_buffer(src, len(src) + 2)
    dst_buffer = ctypes.create_unicode_buffer(dst, len(dst) + 2)

    fileop = _SHFILEOPSTRUCTW()
    fileop.hwnd = 0
    fileop.wFunc = 2  # FO_COPY
    fileop.pFrom = wintypes.LPCWSTR(ctypes.addressof(src_buffer))
    fileop.pTo = wintypes.LPCWSTR(ctypes.addressof(dst_buffer))
    fileop.fFlags = 512  # FOF_NOCONFIRMMKDIR
    fileop.fAnyOperationsAborted = 0
    fileop.hNameMappings = 0
    fileop.lpszProgressTitle = None

    result = ctypes.windll.shell32.SHFileOperationW(ctypes.byref(fileop))
    return not result

已在Python 3.7和2.7上测试,也测试了长的源路径和目标路径。


最有用的答案,不过我读到自 Vista 以来,IFileOperation 已经取代了 SHFileOperationW。你找到了一种使用 ctypes 的方法吗? - Jay
1
谢谢!但是不行。我对IFileOperation一无所知。我可以想象需要更新ctypes.windll。我的意思是,“shell32”听起来不像是21世纪的东西 :| - ewerybody
也许在 Python 3.9 中 :) - Jay

1
查看 IFileCopy。IFileOperation 可能可通过 ctypes 和 shell32.dll 获得,我不确定。

ctypes.windll.shell32.SHFileOperation 存在,但 ctypes.windll.shell32.IFileOperation 不存在(Windows 10,Python 3.8)。 - Jay
@Jay,这是一个COM接口,而不是一个函数。 - undefined

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