python – Renumber files in a folder consecutively


You have two bugs in get_digits_minimum_length ():

If len (str (start_number))> length_numbers:
digit_length = start_number

First, he probably meant digit_length = len (start_number). But even that is not enough, because the final number can have more digits than the initial number. Therefore, it must be:

digit_length = max (digit_length, len (str (start_number + len (file names) - 1)))

File manipulation

A better way to make a temporary directory within directory is tempfile.mkdtemp (dir = directory). It guarantees that the name of the new directory will not collide with any existing file or directory. (Automatically generates a different name as necessary for that to happen).

The temporary directory needs to have permissions. stat.S_IRWXU. In unix, stat.S_IWRITE (or stat.IWUSR) is insufficient, since you will not be able to call listdir () in the temporary directory.

The program calls get_filenames () three times. Not only is it slow and useless, but it also presents a possibility of inconsistencies due to the conditions of the race, if a file is created or deleted while the program is running.

shutil.move () it's excessive, when os.rename () It should work just as well. Also, it would be better to simply do os.rmdir () rather than shutil.rmtree () to clean the temporary directory, because os.rmdir () Affirms that the directory is empty. (You do not want to accidentally delete any of your files, right?)

Global state

os.chdir () It affects the global state of a process. I will try to avoid doing it, if possible. And if you do, then I would call it from a prominent place in your code, so that some seemingly innocuous utility (get_filenames ()) has no unexpected insidious side effects in the rest of your code. After calling os.chdir (), you do not have to ever mention directory again in your code, because each operation is relative to the current directory.

Design and efficiency

I am a little annoyed by some of its very short help functions: make_filename (), make_folder ()Y erase_directory (). In my opinion, they make the code more difficult to read, because they add very little value to the call of the standard library, but I have to dedicate a mental effort to keep track of what they do. Each of these functions is called from a single place, which makes its existence worth less.

I imagine that there could be use cases in which this program is executed repeatedly for some directory. In that case, I would be moving files in and out of the temporary directory, most of them in vain. A better strategy would be Put more work into assigning the source file names to your desired destinations (as in my filename_map () below), so that no more manipulation of the file system is done than necessary.

Suggested solution

import os
import re
import statistics
import temporary file

def filename_map (prefix, suffix, start number):
Make a dictionary that assigns names of source files to their renumbered
destination file name.
pattern = re.compile (re.escape (prefix) + & # 39; ([0-9]+) & # 39; + re.scape (suffix), re.I)
num_to_fn = {
pattern.fullmatch (fn) .group (1): fn
for fn in os.listdir ()
if pattern.fullmatch (fn)
digit_length = max (
max ((len (num) for num in num_to_fn), default = 0),
len (str (start number + len (num_to_fn) - 1))
he came back {
num_to_fn[num]: prefix + str (i) .zfill (digit_length) + suffix
for i, num in enumerate (ordered (num_to_fn, key = int), start_number)

def filling_gaps (prefix, suffix, start number = 1):
Rename files in the current directory whose names consist of the dice
prefix, followed by some digits, and the given suffix, so that
They are numbered consecutively from the specified start number.
if start_number <0:
increase ArgumentError ("Start number {0} is less than 0", start number)

fn_map = {
src_fn: dst_fn
for src_fn, dst_fn in filename_map (prefix, suffix, start number) .items ()
yes src_fn! = dst_fn
if it is not fn_map:
Back # Nothing to change your name

temp_directory = tempfile.mkdtemp (dir = & # 39;. & # 39 ;, prefix = & # 39; renumber & # 39;)
os.chmod (temp_directory, stat.S_IRWXU)
for src_fn, dst_fn in fn_map.items ():
os.rename (src_fn, os.path.join (temp_directory, dst_fn))
for dst_fn in fn_map.values ​​():
os.rename (os.path.join (temp_directory, dst_fn), dst_fn)
os.rmdir (temp_directory)

os.chdir (r & # 39; E:  spam & # 39;)
filling_gaps (& # 39; spam & # 39 ;, & # 39; .txt & # 39 ;, 1)