Home > Software design >  python and iteration specifically "for line in first_names"
python and iteration specifically "for line in first_names"

Time:12-08

If I 'pop' an item off an array in python, I seem to shoot myself in the foot by messing up the array's total length? See the following example: Also am I just being an idiot or is this normal behaviour? And is there a better way to achieve what I am trying to do?

first_names = []
last_names = []
approved_names = []
blacklisted_names = []
loopcounter = 0

with open("first_names.txt") as file:
    first_names = file.readlines()
    #first_names = [line.rstrip() for line in first_names]

for line in first_names:
    line = line.strip("\r\n")
    line = line.strip("\n")
    line = line.strip(" ")
    if line == "":
        first_names.pop(loopcounter)
        #first_names.pop(first_names.index("")) # Does not work as expected
        #loopcounter -= 1 # Does not work as expected either......
    loopcounter  = 1
        
loopcounter = 0


def save_names():
    with open("first_names2.txt",'wt',encoding="utf-8") as file:
        file.writelines(first_names)

and the resulting files:

first_names.txt 
{

Abbey
Abbie
Abbott
Abby


Abe
Abie
Abstinence
Acton
}

And the output file

{
Abbey
Abbie
Abbott


Abe
Abie
Abstinence
Acton
}

CodePudding user response:

list.pop() removes an item from a list and returns the value (see e.g. this ref). For the very basic task of cleaning and writing the list of names, an easy edit would be:

with open("first_names.txt") as file:
    first_names = file.readlines()

cleaned_lines = []
for line in first_names:
    clean_l = line.strip("\r\n").strip("\n").strip(" ")
    if clean_l != "":
        cleaned_lines.append(clean_l)

with open("first_names2.txt",'wt',encoding="utf-8") as file:
    file.writelines(cleaned_lines)

If you don't want to create a cleaned copy of the list first_names, you could iteratively append single lines to the file as well.

with open("first_names.txt") as file:
    first_names = file.readlines()

with open("first_names2.txt",'wt',encoding="utf-8") as file:
    for line in first_names:
        clean_l = line.strip("\r\n").strip("\n").strip(" ")
        if clean_l != "":
            file.writelines([clean_l, ])

CodePudding user response:

In general it is not a good idea to mutate a list on which you're iterating, as you stated in your question. If you pop an element from the list you don't necessarily mess up the array's length, but you may encounter unexpected behavior when dealing with which index to pop. In this case you may skip some elements of the array.

A quick solution would be to make a copy of the list and use the built-in enumerate() method as follows:

copy = first_names.copy()
for i, line in enumerate(copy):
    line = line.strip("\r\n")
    line = line.strip("\n")
    line = line.strip(" ")
    if line == "":
        first_names.remove(i)

More on enumerate() here.

CodePudding user response:

The usual practice would be to filter or create a new list, rather than change the list you are iterating. It's not uncommon to create a new list with the changes you want, and then just assign it back to the original variable name. Here is a list comprehension. Note the if statement that filters out the undesirable blank lines.

first_names = [name.strip() for name in first_names if name.strip()]

https://docs.python.org/3/glossary.html#term-list-comprehension

And you can do the same with iterators using map to apply a function to each item in the list, and filter to remove the blank lines.

first_names_iterator = filter(lambda x: bool(x), map(lambda x: x.strip(), first_names))
first_names = list(first_names_iterator)

https://docs.python.org/3/library/functions.html#map
https://docs.python.org/3/library/functions.html#filter

The last line demonstrates that you could have just passed the iterator to list's constructor to get a list, but iterators are better. You can iterate through them without having to have the whole list at once. If you wanted a list, you should use list comprehension.

The lambda notation is just a fast way to write a function. I could have defined a function with a good name, but that's often overkill for things like map, filter, or a sort key.

Full code:

test_cases = [
    'Abbey',
    '  Abbie  ',
    '',
    'Acton',
]

print(test_cases)
first_names = list(test_cases)
first_names = [name.strip() for name in first_names if name.strip()]
print(first_names)

first_names = list(test_cases)
for name in filter(lambda x: bool(x),
                   map(lambda x: x.strip(),
                       first_names)):
    print(name)
  • Related