#πŸ”’ Is it possible to avoid nesting here?

62 messages Β· Page 1 of 1 (latest)

terse jay
#

I have code that goes roughly like this:

def foo(keys):
    a = create_a()
    b = None
    c = None

    args = []
    for key in keys:
        if key in a:
            arg = a[key]
        else:
            b = b or create_b()
            if key in b:
                arg = b[key]
            else:
                c = c or create_c()
                if key in c:
                    arg = c[key]
                else:
                    raise RuntimeError(f"Key {key} not found anywhere")
        args.append(arg)
    
    return some_func(args)

I'm looking for a bunch of keys within objects a, b and c. b and c are created lazily only when a key wasn't found in a.

My real code also has a for loop in each step so the nesting is getting out of hand.

Is it possible to do this without the else nesting? I don't think "easier to ask for forgiveness" can help with this at all, but I'm open to being proven wrong.

surreal ploverBOT
#

@terse jay

Python help channel opened

Remember to:

  • Ask your Python question, not if you can ask or if there's an expert who can help.
  • Show a code sample as text (rather than a screenshot) and the error message, if you've got one.
  • Explain what you expect to happen and what actually happens.

:warning: Do not pip install anything that isn't related to your question, especially if asked to over DMs.

long nebula
#
    b = None
    b = b or create_b()
#

any reason its not just create_b()

terse jay
#

I do have a good reason. for the sake of argument let's just say it's expensive.

#

I only create b when I couldn't find what I'm looking for in a

long nebula
#

only idea i can come up with is iterating over all dictionaries instead of the giant if/else chain

long nebula
#

is what im asking

terse jay
#

oh. the line b = b or create_b() is in a for loop so can be called multiple times. The first time b is None so it called create_b(). Any time after that, b is truthy so create_b() doesn't get called (short circuit evaluation)

long nebula
#

ah

#

im dumb

terse jay
fresh token
#
objs = [None] * 3
creators = [create_a, create_b, create_c]
args = [find_key(key) for key in keys]

def find_key(key):
    for i, obj in enumerate(objs):
        if obj is None:
            obj = objs[i] = creators[i]()
        if key in obj:
            return obj[key]
    raise RuntimeError(f"Key {key} not found")

i would do something like this

long nebula
#

i was writing that but with itertools cycle

#

thats just cleaner tho pithink

fresh token
#

also why RuntimeError and not KeyError?

terse jay
#

why does sqlite3.Row raise IndexError instead of KeyError?

#

sometimes these things are just the way they are

#

!d sqlite3.Row

surreal ploverBOT
#

class sqlite3.Row```
A `Row` instance serves as a highly optimized [`row_factory`](https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.row_factory) for [`Connection`](https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection) objects. It supports iteration, equality testing, [`len()`](https://docs.python.org/3/library/functions.html#len), and [mapping](https://docs.python.org/3/glossary.html#term-mapping) access by column name and index.

Two `Row` objects compare equal if they have identical column names and values.

See [How to create and use row factories](https://docs.python.org/3/library/sqlite3.html#sqlite3-howto-row-factory) for more details.
fresh token
#

well presumably rows are counted so indexed by natural numbers, so index error makes sense since sequence of rows
but yeah doesn't really matter

long nebula
#

this works i think (?)

weary sail
#

it's pretty ugly though.

long nebula
#

fair point pithink

#

if u abstract it into more lines/mini-functions its prob a lot better

weary sail
#

honestly, perhaps just put the loop into a function. then you can early return

long nebula
#
def get_dict(dicts, key):
    for d in dicts:
        if key in d:
            return d

args = [get_dicts([a,b,c], key)[key] for key in keys]

u mean something like this ? pithink

weary sail
#

without changing too much of the original code:

def foo(keys):
    a = create_a()
    b = None
    c = None

    def get_arg(key):
        nonlocal b, c
        if key in a:
            return a[key]
        b = b or create_b()
        if key in b:
            return b[key]
        c = c or create_c()
        if key in c:
            return c[key]
        raise RuntimeError(f"Key {key} not found anywhere")

    args = []
    for key in keys:
        arg = get_arg(key)
        args.append(arg)

    return some_func(args)
#

the only reason you couldn't do it in the orignal code, was because of the args.append at the end. So just wrap it in a function, so you can.

long nebula
#

nonlocal is such a rare keyword

terse jay
weary sail
#

it's a "tuple" with dict-like features.

terse jay
long nebula
#

me stoopid

terse jay
long nebula
terse jay
long nebula
long nebula
#

i just made that line to be a general idea of what i had in mind

#

want me to ?

weary sail
#

no one said it needs to be in one line πŸ˜‰

long nebula
#

exactly

#
args = [next(filter(lambda x: key in x, [(a := a or create_a()), (b := b or create_b()), (c := c or create_c())]))[key] for key in keys]
#

its so much better if u just make it in multiple lines

#
args = [
    next(
        filter(
            lambda x: key in x, 
            [
                (a := a or create_a()), 
                (b := b or create_b()), 
                (c := c or create_c())
            ]
        )
    )[key] for key in keys
]
#

if u do it like this its still kinda ugly

#

if this was inside a class u could make a b c properties and handle their creation if they dont exist that way

#

but i dont think it is pithink

weary sail
#

btw. you could also annotate your create functions with something like @functool.cache ... that way it should only be called once as well (though it might be called again if python garbage collects any existing instance of the dict).

south oak
#

on the train rn, don't have much time.

though I am thinking something along the lines of dict' union; using the other dict's values if current one is None.

Alternatively, if you're looking for missing keys; perhaps using the .get() method or a defaultdict() would get you a similar behavior to what you want.

#

maybe instead of a separate functions, make a general create() function to generate/map the values to their respective key.

fresh token
#

if you didn't want to cache, i would've recommended a collections.ChainMap

long nebula
south oak
surreal ploverBOT
#
Python help channel closed for inactivity

This help channel has been closed. Feel free to create a new post in #1035199133436354600. To maximize your chances of getting a response, check out this guide on asking good questions.