[Python-ideas] Implementation of shutil.move

Steven D'Aprano steve at pearwood.info
Sun Aug 14 06:05:27 CEST 2011


Masklinn wrote:
> On 13 août 2011, at 19:08, MRAB <python at mrabarnett.plus.com> wrote:
>> On 13/08/2011 15:59, David Townshend wrote:
>>> There was a suggestion on the issue tracker that it might be better to
>>> add an optional argument to shutil.move rather than create a new
>>> function. Does anyone have any comments or suggestions about this?
>>>
>> [For reference, it's issue 12741 ("Add function similar to shutil.move
>> that does not overwrite".)]
>>
>> My preference is for adding an optional argument because the difference
>> in behaviour seems too small to justify a different function.
> Likewise, and even more so because the current behaviour is platform-dependent and dome platforms already behave as proposed, so the proposal really unifies the behaviour across platforms. 


I dispute this. I don't think the suggested function will work as 
described, at least not as given in the bug tracker, and certainly not 
in a platform-independent way.

I don't think you can make it completely platform independent, because 
the underlying file system operations are too different. E.g. file 
renames are atomic on POSIX systems, but not on Windows. Glossing over 
these real differences will merely give people a false sense of 
security. It is important for people to understand the limitations of 
what is possible on their system, and not be given wrong ideas.

(That's not to say that the proposed function is useless. It may be the 
closest thing that some users will get to the desired behaviour.)

For documentation purposes alone, having two separate functions is valuable.

The suggested implementation on the tracker

http://bugs.python.org/issue12741

wraps the existing shutil.move function. The most natural implementation 
is a separate function that calls the first function. To have one 
function implement both behaviours requires an unnatural implementation, 
such as:

def move(src, dest, flag=False):
     if flag:
         try:
             fd = os.open(dst, os.O_EXCL | os.O_CREAT)
         except OSError:
             raise Error('Destination exists')
         try:
             move(src, dst, False)  # recursive call
         finally:
             os.close(fd)
     else:
          # Insert current implementation here...

Slightly less ugly might be to rename the current function move to 
_move, then have this:

def move(src, dest, flag=False):
     if flag:
         try:
             fd = os.open(dst, os.O_EXCL | os.O_CREAT)
         except OSError:
             raise Error('Destination exists')
         try:
             _move(src, dst)
         finally:
             os.close(fd)
     else:
          _move(src, dst)


Either case is a prime example of why Guido's rule of thumb that 
functions shouldn't take arguments which select between two (or more) 
alternative behaviours is a good design principle. Better to keep two 
separate functions.


-1 on a flag argument to shutil.move.

+1 on a "safe" version of shutil.move.

+0 on the given implementation. I'm not convinced that the proposed 
function actually is any safer.

On the tracker, the OP says:

"If this fails, then it means that the file exists, but if it passes, 
then dst is locked so no other process can create it."

But that's not what actually happens. On my Linux box, I can do this in 
one terminal:

 >>> dst = 'spam'
 >>> fd = os.open(dst, os.O_EXCL | os.O_CREAT)
 >>> os.path.exists(dst)
True
 >>> open(dst).read()  # Confirm file is empty.
''


I then switch to another terminal, and do this:

[steve at sylar ~]$ cat spam  # File exists, and is empty.
[steve at sylar ~]$ echo "Writing to a locked file" > spam
[steve at sylar ~]$


And back to the first one:

 >>> open(dst).read()
'Writing to a locked file\n'

So despite the allegedly exclusive lock, another process was able to 
overwrite the file after the lock was taken.




-- 
Steven




More information about the Python-ideas mailing list