4343except ImportError :
4444 getgrnam = None
4545
46+ _WINDOWS = os .name == 'nt'
4647posix = nt = None
4748if os .name == 'posix' :
4849 import posix
49- elif os . name == 'nt' :
50+ elif _WINDOWS :
5051 import nt
5152
52- COPY_BUFSIZE = 1024 * 1024 if os . name == 'nt' else 16 * 1024
53+ COPY_BUFSIZE = 1024 * 1024 if _WINDOWS else 16 * 1024
5354_HAS_SENDFILE = posix and hasattr (os , "sendfile" )
54- _HAS_FCOPYFILE = posix and hasattr (posix , "_fcopyfile" ) # OSX
55+ _HAS_FCOPYFILE = posix and hasattr (posix , "_fcopyfile" ) # macOS
5556
5657__all__ = ["copyfileobj" , "copyfile" , "copymode" , "copystat" , "copy" , "copy2" ,
5758 "copytree" , "move" , "rmtree" , "Error" , "SpecialFileError" ,
@@ -88,9 +89,9 @@ class _GiveupOnFastCopy(Exception):
8889 file copy when fast-copy functions fail to do so.
8990 """
9091
91- def _fastcopy_osx (fsrc , fdst , flags ):
92+ def _fastcopy_fcopyfile (fsrc , fdst , flags ):
9293 """Copy a regular file content or metadata by using high-performance
93- fcopyfile(3) syscall (OSX ).
94+ fcopyfile(3) syscall (macOS ).
9495 """
9596 try :
9697 infd = fsrc .fileno ()
@@ -168,8 +169,11 @@ def _fastcopy_sendfile(fsrc, fdst):
168169 break # EOF
169170 offset += sent
170171
171- def _copybinfileobj (fsrc , fdst , length = COPY_BUFSIZE ):
172- """Copy 2 regular file objects open in binary mode."""
172+ def _copyfileobj_readinto (fsrc , fdst , length = COPY_BUFSIZE ):
173+ """readinto()/memoryview() based variant of copyfileobj().
174+ *fsrc* must support readinto() method and both files must be
175+ open in binary mode.
176+ """
173177 # Localize variable access to minimize overhead.
174178 fsrc_readinto = fsrc .readinto
175179 fdst_write = fdst .write
@@ -179,28 +183,21 @@ def _copybinfileobj(fsrc, fdst, length=COPY_BUFSIZE):
179183 if not n :
180184 break
181185 elif n < length :
182- fdst_write (mv [:n ])
186+ with mv [:n ] as smv :
187+ fdst .write (smv )
183188 else :
184189 fdst_write (mv )
185190
186- def _is_binary_files_pair (fsrc , fdst ):
187- return hasattr (fsrc , 'readinto' ) and \
188- isinstance (fsrc , io .BytesIO ) or 'b' in getattr (fsrc , 'mode' , '' ) and \
189- isinstance (fdst , io .BytesIO ) or 'b' in getattr (fdst , 'mode' , '' )
190-
191191def copyfileobj (fsrc , fdst , length = COPY_BUFSIZE ):
192192 """copy data from file-like object fsrc to file-like object fdst"""
193- if _is_binary_files_pair (fsrc , fdst ):
194- _copybinfileobj (fsrc , fdst , length = length )
195- else :
196- # Localize variable access to minimize overhead.
197- fsrc_read = fsrc .read
198- fdst_write = fdst .write
199- while 1 :
200- buf = fsrc_read (length )
201- if not buf :
202- break
203- fdst_write (buf )
193+ # Localize variable access to minimize overhead.
194+ fsrc_read = fsrc .read
195+ fdst_write = fdst .write
196+ while True :
197+ buf = fsrc_read (length )
198+ if not buf :
199+ break
200+ fdst_write (buf )
204201
205202def _samefile (src , dst ):
206203 # Macintosh, Unix.
@@ -215,7 +212,7 @@ def _samefile(src, dst):
215212 os .path .normcase (os .path .abspath (dst )))
216213
217214def copyfile (src , dst , * , follow_symlinks = True ):
218- """Copy data from src to dst.
215+ """Copy data from src to dst in the most efficient way possible .
219216
220217 If follow_symlinks is not set and src is a symbolic link, a new
221218 symlink will be created instead of copying the file it points to.
@@ -224,7 +221,8 @@ def copyfile(src, dst, *, follow_symlinks=True):
224221 if _samefile (src , dst ):
225222 raise SameFileError ("{!r} and {!r} are the same file" .format (src , dst ))
226223
227- for fn in [src , dst ]:
224+ file_size = 0
225+ for i , fn in enumerate ([src , dst ]):
228226 try :
229227 st = os .stat (fn )
230228 except OSError :
@@ -234,26 +232,34 @@ def copyfile(src, dst, *, follow_symlinks=True):
234232 # XXX What about other special files? (sockets, devices...)
235233 if stat .S_ISFIFO (st .st_mode ):
236234 raise SpecialFileError ("`%s` is a named pipe" % fn )
235+ if _WINDOWS and i == 0 :
236+ file_size = st .st_size
237237
238238 if not follow_symlinks and os .path .islink (src ):
239239 os .symlink (os .readlink (src ), dst )
240240 else :
241241 with open (src , 'rb' ) as fsrc , open (dst , 'wb' ) as fdst :
242- if _HAS_SENDFILE :
242+ # macOS
243+ if _HAS_FCOPYFILE :
243244 try :
244- _fastcopy_sendfile (fsrc , fdst )
245+ _fastcopy_fcopyfile (fsrc , fdst , posix . _COPYFILE_DATA )
245246 return dst
246247 except _GiveupOnFastCopy :
247248 pass
248-
249- if _HAS_FCOPYFILE :
249+ # Linux / Solaris
250+ elif _HAS_SENDFILE :
250251 try :
251- _fastcopy_osx (fsrc , fdst , posix . _COPYFILE_DATA )
252+ _fastcopy_sendfile (fsrc , fdst )
252253 return dst
253254 except _GiveupOnFastCopy :
254255 pass
256+ # Windows, see:
257+ # https://github.com/python/cpython/pull/7160#discussion_r195405230
258+ elif _WINDOWS and file_size > 0 :
259+ _copyfileobj_readinto (fsrc , fdst , min (file_size , COPY_BUFSIZE ))
260+ return dst
255261
256- _copybinfileobj (fsrc , fdst )
262+ copyfileobj (fsrc , fdst )
257263
258264 return dst
259265
@@ -1147,7 +1153,7 @@ def disk_usage(path):
11471153 used = (st .f_blocks - st .f_bfree ) * st .f_frsize
11481154 return _ntuple_diskusage (total , used , free )
11491155
1150- elif os . name == 'nt' :
1156+ elif _WINDOWS :
11511157
11521158 __all__ .append ('disk_usage' )
11531159 _ntuple_diskusage = collections .namedtuple ('usage' , 'total used free' )
0 commit comments