Best way to get nearest non-zero value from list
I really like your idea of splitting the list -- it's very functional! Let's look at optimizing that approach, though.

def nearest_non_zero(lst, idx):
    # note that I parameterized the list. Should make it easier to test.
    # though I'll use [4,2,6,7,3,0,0,9,4,2,5,8,1,7] as in your code for
    # all examples throughout

    if lst[idx] &gt; 0:
        return lst[idx]
        # I find it easier to track early-exit routes through code than
        # to rule everything out. Let's short-circuit if possible!

    before, after = reversed(lst[:idx]), lst[idx+1:]
    # using `reversed` here lets us avoid creating a new list, which
    # is otherwise necessary if we did `(lst[:idx])[::-1]`. Imagine
    # using that on a list with length of ten million. Yikes!


So far I've really only cleaned up a little bit of code, nothing big, but here is where I'll make a decent departure from your original code. Your code does this:

if len(before) &gt;= len(after):
    loop_length = len(before)
else:
    loop_length = len(after)


This is unnecessary, and it forces you to write in the guards inside your for loop to make sure you're not indexing a value outside of the list length. That's too much work. There's a couple better ways, the primary one being itertools.zip_longest.

The builtin zip pulls together two (or more!) lists into one list of tuples. For instance

&gt;&gt;&gt; a = [1, 2, 3]
&gt;&gt;&gt; b = ['a', 'b', 'c']
&gt;&gt;&gt; zip(a, b)
[(1, 'a'), (2, 'b'), (3, 'c')]
&gt;&gt;&gt; zip(b, a)
[('a', 1), ('b', 2), ('c', 3)]


However if you have lists of uneven size, it will only go so far as the shortest list

&gt;&gt;&gt; a = [1, 2]
&gt;&gt;&gt; b = ['a', 'b', 'c']
&gt;&gt;&gt; zip(a, b)
[(1, 'a'), (2, 'b')]  # where's 'c'?!?!


That's not what we want here. We want to zip together every value from both the before and after lists and compare them. That's what itertools.zip_longest does. But of course it needs another value: what should I use to pair with a value that doesn't exist? That's the parameter fillvalue, which by default is None

&gt;&gt;&gt; a = [1, 2]
&gt;&gt;&gt; b = ['a', 'b', 'c']
&gt;&gt;&gt; itertools.zip_longest(a, b)
[(1, 'a'), (2, 'b'), (None, 'c')]  # there's 'c', phew!

&gt;&gt;&gt; a = [1, 2]
&gt;&gt;&gt; b = ['a', 'b', 'c']
&gt;&gt;&gt; itertools.zip_longest(b, a, fillvalue="foobar")
[('a', 1), ('b', 2), ('c', 'foobar')]


That's exactly what we want! In this case we want our fillvalue to explicitly be a failing case (since if the number doesn't really exist we obviously don't want to use it), and our failing case is zero.

After that we compare to zero our "before" value, then our "after" value (to implement that preference for before rather than after), and return either if it's appropriate to do so.

import itertools

for b_val, a_val in itertools.zip_longest(before, after, fillvalue=0):
    if b_val &gt; 0:
        return b_val
    elif a_val &gt; 0:
        return a_val
else:
    # what do you do if you have a list that's all zeroes?


This keeps us from having to deal with any indexing at all, which is common idiomatically in Python. If you have to do for i in range(...): some_list[i], then you're probably doing something wrong!

Our final code, then is:

from itertools import zip_longest

def nearest_non_zero(lst, idx):
    if lst[idx] &gt; 0:
        return lst[idx]
    before, after = lst[:idx], lst[idx+1:]
    for b_val, a_val in zip_longest(reversed(before), after, fillvalue=0):
        # N.B. I applied `reversed` inside `zip_longest` here. This
        # ensures that `before` and `after` are the same type, and that
        # `before + [lst[idx]] + after == lst`.
        if b_val &gt; 0:
            return b_val
        if a_val &gt; 0:
            return a_val
    else:
        return 0  # all zeroes in this list