Home > Software design >  Multiple strings replacement from dictionary
Multiple strings replacement from dictionary

Time:09-23

I'm going to create script which will take the key and value from the dictionary and use it for replacement in set of files. I need replace "foo" to "foo1" if "target_value" found in the file. There are many different foo's. So, I guess dictionary will be suitable for that.

I started from the simple things:

    with fileinput.FileInput(filelist, inplace=True) as file:
        for line in file:
            if line.find('target_value'):
                print(line.replace("foo", "foo1"), end='')

For some reason this script just ignore line.find and replace everything with last line of code. Could you help?

CodePudding user response:

Python's find command returns -1 if the value is not found, so you need something like:

with fileinput.FileInput(filelist, inplace=True) as file:
    for line in file:
        if line.find('target_value') > -1:
            line = line.replace("foo", "foo1")
        print(line, end='')

CodePudding user response:

The trouble with str.find is that it returns the index at which "target_value" occurs in line, so any integer from 0 to len(line)-len(target_value)-1. That is, unless, "target_value" doesn't exist in line; then str.find returns -1 but the value of bool(-1) is True. In fact, the only time line.find('target_value') is False is when "target_value" is the first part of line.

There are a couple options:

with fileinput.FileInput(filelist, inplace=True) as file:
    for line in file:
        if line.find('target_value') != -1:
            print(line.replace("foo", "foo1"), end='')

Or:

with fileinput.FileInput(filelist, inplace=True) as file:
    for line in file:
        if 'target_value' in line:
            print(line.replace("foo", "foo1"), end='')

The second option is more readable and tends to perform better when line is long and "target_value" doesn't occur in the beginning of line.

>>> timeit('"target_value" in s', setup='s = "".join("foobar baz" for _ in range(100)) "target_value"  "".join("foobar baz" for _ in range(100))')
0.20444475099975534

>>> timeit('s.find("target_value") != -1', setup='s = "".join("foobar baz" for _ in range(100)) "target_value"  "".join("foobar baz" for _ in range(100))')
0.30517548999978317

CodePudding user response:

Instead of .find(), you can use if 'target_value' in line: which is more expressive and does not involve return value conventions.

If you have multiple target keywords (and multiple replacements per target), you could build your dictionary in like this

replacements = { 'target_value1': [('foo','foo1'), ('bar','bar1')],
                 'target_value2': [('foo','foo2'), ('bar','bar2')],
                 'target_value3': [('foobar','foosy'),('barfoo','barry')]}

Then find which target value(s) are present and perform the corresponding replacements:

with open(fileName,'r') as f:
     content = f.read()
     for target,maps in replacements.items(): # go through target keywords
         if target not in content: continue   # check target (whole file)
         for fromString,toString in maps:     # perform replacements for target
             content = content.replace(fromString,toString)

# you probably want to save the new content at this point
with open(fileName,'w') as f:
    f.write(content)

Note that, in this example, I assumed that the target keyword flags the whole file (not each line). If the target keyword is specific to each line, you will need to break down the content into lines and place the logic inside a loop on lines to perform the replacements on a line by line basis

You don't actually need the replacement data to be a dictionary:

replacements = [ ['target_value1',('foo','foo1'), ('bar','bar1')],
                 ['target_value2',('foo','foo2'), ('bar','bar2')],
                 ['target_value3',('foobar','foosy'),('barfoo','barry')]]

with open(fileName,'r') as f:
     content = f.read()
     for target,*maps in replacements:    # go through target keywords
         if target not in content: continue
         for fromString,toString in maps: # perform replacements for target
             content = content.replace(fromString,toString)
  • Related