I wrote this function to fill a list with object based on the desired index they should have.
Here the func:
def fill(lst, index, add, fillvalue=None):
'''Fills a list with the add value based on desired index'''
'''Let the value in place if present'''
for n in range(index):
try:
if lst[n]:
pass
except Exception:
lst.append(fillvalue)
try:
if lst[index]:
pass
else:
lst[index]=add
except Exception:
if index==len(lst):
#This if check probably not mandatory
lst.append(add)
return lst
edit: here's an usage example that would for example happen in a for loop
>>> my_list = [1, 7, 14]
>>> fill(my_list, 5, 6)
[1, 7, 14, None, None, 6]
>>> fill(my_list, 6, 12)
[1, 7, 14, None, None, 6, 12]
>>> fill(my_list, 3, 25)
[1, 7, 14, 25, None, 6, 12]
>>> fill(my_list, 4, 18)
[1, 7, 14, 25, 18, 6, 12]
Note:
>>> my_list = [1, 7, 14]
>>> fill(my_list, 1, 6)
#returns the list not modified, my_list[1] is already taken
[1, 7, 14]
It works good for my purpose (matrix generating). My matrixes are generated with many "for loops" passes, because the data I convert to is unordered. This is why the "None" elements are important in order to fill them out on a subsequent loop pass.
My questions are:
I'm wondering if I made everything as simple as possible? Or if there were a lib, that would do the same job?
For now the matrixes aren't so long but I guess, if I go further in my development, they will become big (1000+ lines). Should I somehow better use itertools?
I also somehow thought of first using a dict {index: value} and convert it to a list when filled up. Would it be better practice?
Edit: Now that I pasted the code in here I notice the arg name "add" is blued. My Vim-editor didn't told me that... Is it a name already taken by the python core?
1 Answer 1
if lst[n]
is not sufficient to determine if the value at indexn
isNone
,0
will also not pass the test. Useif lst[n] is not None
orif lst[n] is None
, depending on which one you want.The whole first loop is just trying to determine if you need to extend the list, but you could just do that:
lst.extend([fill_value] * (index - len(lst) + 1))
If the list is already long enough this does nothing, as a list multiplied by an integer smaller than one is the empty list, and extending with an empty list does nothing. It even works when you use negative indices.
This also guarantees that the index exists, so you don't need the
try..except
anymore. But if you usetry
andexcept
, always try to catch the narrowest exception possible. This way you make sure you don't catch an unexpected exception. In this case it would be anIndexError
instead of the more genericException
.The convention in Python is for functions to only do one of two things: either do not mutate the inputs and return a new output or mutate (some of) the input and return
None
(potentially implicitly). Your function falls into the second category.
With these changes your function could be a lot shorter:
def fill(lst, index, value, fill_value=None):
"""Place the `value` at index `index`,
but only if the value already there is `None`.
Extends the list with `fill_value`, if necessary.
"""
# No-op if list long enough or index negative
lst.extend([fill_value] * (index - len(lst) + 1))
if lst[index] is None:
lst[index] = value
my_list = [1, 7, 14]
# my_list = [1, 7, 14]
fill(my_list, 5, 6)
# my_list = [1, 7, 14, None, None, 6]
fill(my_list, 6, 12)
# my_list = [1, 7, 14, None, None, 6, 12]
fill(my_list, 3, 25)
# my_list = [1, 7, 14, 25, None, 6, 12]
fill(my_list, 4, 18)
# my_list = [1, 7, 14, 25, 18, 6, 12]
Note that you will not directly see the result anymore in an interactive session, since the list is no longer returned. But that's a feature, not a bug, as mentioned above.
Of course, if you need to do this multiple times directly after each other, you might want to define a function that needs to extend only once:
def fill_multiple(lst, indices, values, fill_value=None):
lst.extend([fill_value] * (max(indices) - len(lst) + 1))
for index, value in zip(indices, values):
if lst[index] is None:
lst[index] = value
Then you can use e.g a dictionary:
my_list = [1, 7, 14]
updates = {5: 6, 6: 12, 3: 25, 4: 18}
fill_multiple(my_list, updates.keys(), updates.values())
# my_list = [1, 7, 14, 25, 18, 6, 12]
I kept the indices and values separate so you can also just pass lists or anything else, not just a mapping. Depending on your usecase you might want to pass the dictionary to the function instead.
-
\$\begingroup\$ Thanks a lot for your complete answer. I like the way advanced coders are able to divide by 4 the line amount of some amateur code. Side question: from where comes this convention on functions? Or where could I read more about those conventions? \$\endgroup\$Halavus– Halavus2020年03月12日 16:29:40 +00:00Commented Mar 12, 2020 at 16:29
-
\$\begingroup\$ @Halavus It's actually surprisingly hard to find an authorative source for this, it is just conventional wisdom that it should be this way :). Even the canonical StackOverflow question about this has no sources. \$\endgroup\$Graipher– Graipher2020年03月13日 07:00:48 +00:00Commented Mar 13, 2020 at 7:00
-
1\$\begingroup\$ @Halavus "You might have noticed that methods like
insert
,remove
orsort
that only modify the list have no return value printed – they return the defaultNone
. 1 This is a design principle for all mutable data structures in Python." - Python docs. It doesn't really explain why tho. \$\endgroup\$2020年03月13日 12:34:39 +00:00Commented Mar 13, 2020 at 12:34
except Exception:
like that is a bad idea, see stackoverflow.com/questions/54948548/…. \$\endgroup\$