Indexable get method. [1,2,3].get(4) # None

Another example are simple lookahead parsers/tokenizers:

while tokens:
    token = tokens.pop(0)
    match token:
        case ...:
            if tokens.get(0) != ...:
                ...

Or even something simple as:

if s.get(0) == "*" and s.get(1) != "*":  # not a list, but similar in spirit
    ...
5 Likes

Use case of slice functionality. Rolling window with constant extrapolation:

def rolling_window(lst, n=1, default=None):
    for i in range(len(lst)):
        yield lst@get(slice(i, i + n), default)

In [32]: list(rolling_window(l, 3, default=len(l) - 1))
Out[32]: [[0, 1, 2], [1, 2, 3], [2, 3, 3], [3, 3, 3]]

Note, did not encounter this, but I see myself making use of it in similar ways if it was there.

Why not use a set? Then you can unconditionally add.

I think this implementation is broken. If the bisect returns zero, you’ll unexpectedly return the largest element in the list, not the default.

1 Like

As mentioned, I need to build a list of ascending IDs for binary search with bisect.

Ah you’re right. My bad. Forget about this use case then. Thanks!

1 Like

Ah, I missed that the two examples were linked.

@alicederyn On second thought, although it isn’t my personal use case, I imagine that it may still be useful for simplifying other bisect recipes such as find_gt where a default value is returned instead of raising an exception:

def find_gt(a, x):
    'Find leftmost value greater than x'
    i = bisect_right(a, x)
    if i != len(a):
        return a[i]
    return None

into:

def find_gt(a, x):
    return a.get(bisect_right(a, x))
1 Like

I still struggle to imagine a real-world use case for having a default value for missing items in a list slice. Do share some if you can think of any.

Note that your example of a rolling window can also be done with a modified sliding_window recipe from the itertools doc, although I can’t think of a good use for the fill values:

def sliding_window(iterable, n, default):
    "Collect data into overlapping fixed-length chunks or blocks."
    # sliding_window('ABCDEFG', 4, 'X') → ABCD BCDE CDEF DEFG EFGX FGXX GXXX
    iterator = iter(iterable)
    window = deque(islice(iterator, n - 1), maxlen=n)
    for x in chain(iterator, repeat(default, n - 1)):
        window.append(x)
        yield tuple(window)
1 Like

Isn’t slicing with a default exactly equivalent to using or?

a.get(slice, default) 
a[slice] or default

As in, there already is a default for slicing, and it’s pretty intuitive?

I didn’t attribute any substantial weight to it, It is more of a fun quick possibility.

I thought if I share it maybe someone will come up with actually useful applications.

Also, its performance in practice is very similar to the recipe you provided. So it’s just a possibility without fancy recipes (or dependencies) for certain cases.

I don’t think so. What I had in mind is:

Script
class get:
    def __init__(self, idx, default=None):
        self.idx = idx
        self.default = default

    def __rmatmul__(self, lst):
        idx, default = self.idx, self.default
        if isinstance(idx, slice):
            start = 0 if idx.start is None else idx.start
            stop = len(lst) if idx.stop is None else idx.stop
            step = 1 if idx.step is None else idx.step
            result = list()
            for i in range(start, stop, step):
                try:
                    item = lst[i]
                except (IndexError, KeyError):
                    item = default
                result.append(item)
            return result
        try:
            return lst[idx]
        except (IndexError, KeyError):
            return default
[0, 1, 2]@get(slice(1, 4))    # [1, 2, None]

If it’s not useful, no need for big discussions. I have no investment in this apart from “it’s nice to me and it’s not taking any valuable space. What else are you going to do with slice type input here?” :slight_smile:

How would that work with slices that mix positive and negative indices?

a.get(slice(1, -1))
2 Likes

Same as in any slicing

get_impl
class get:
    def __init__(self, idx, default=None):
        self.idx = idx
        self.default = default

    def __rmatmul__(self, lst):
        idx, default = self.idx, self.default
        if isinstance(idx, slice):
            n = len(lst)
            start, stop, step = idx.start, idx.stop, idx.step
            if start is None:
                start = 0
            elif start < 0:
                start = n + start
            if stop is None:
                stop = n
            elif stop < 0:
                stop = n + stop
            if step is None:
                step = 1
            result = list()
            for i in range(start, stop, step):
                try:
                    item = lst[i]
                except (IndexError, KeyError):
                    item = default
                result.append(item)
            return result
        try:
            return lst[idx]
        except (IndexError, KeyError):
            return default
[0, 1, 2]@get(slice(1, -1))    # [1]

One just randomly came to my mind. But with 1 modification: “In slice mode it accepts array of defaults”

NULL = object()

sys.argv = [<path>, 4, 5]

defaults = [0, 1, 2, NULL]
a, b, c, end = sys.argv.get(slice(1, 5), defaults)
if end is not NULL:
    raise TypeError('Too many args')
print(a)    # 4
print(b)    # 5
print(c)    # 2
1 Like

Slice functionality would be useful for lookahead>1

One more case I have encountered today, where this would have been a good fit.

l = [0, 0, 0, 0, 1]
trimmed_zeros = list(itertools.dropwhile(operator.not_, l))

Now I need first value in the list with a fallback. Then, if it is lower than 10, I need to take the 2nd with the fallback to 1st if it doesn’t exist. With list.get:

optimal_unit = trimmed_zeros.get(0, FALLBACK)
if optimal_unit < 10:
    optimal_unit = trimmed_zeros.get(1, optimal_unit)

Without it (superfluous length checks):

:
optimal_unit = trimmed_zeros[0] if len(trimmed_zeros) else FALLBACK
if optimal_unit < 10 and len(trimmed_zeros) > 1:
    optimal_unit = trimmed_zeros[1]

Or this (creating iterator for 2 values just doesn’t feel right):

it = iter(trimmed_zeros)
optimal_unit = next(it, FALLBACK)
if optimal_unit < 10:
    optimal_unit = next(it, optimal_unit)