3

I have the following (very simplified) dict. The get_details function is an API call that I would like to avoid doing twice.

ret = { 
    'a': a,
    'b': [{
        'c': item.c,
        'e': item.get_details()[0].e,
        'h': [func_h(detail) for detail in item.get_details()],
    } for item in items]
}

I could of course rewrite the code like this:

b = []
for item in items:
    details = item.get_details()
    b.append({
        'c': item.c,
        'e': details[0].e,
        'h': [func_h(detail) for detail in details],
    })

ret = { 
    'a': a,
    'b': b
}

but would like to use the first approach since it seems more pythonic.

4
  • on your second snippet, details are initialized based on item (whatever that is at that point..) but then item changes in the loop but details do not. Is that intended or false or simply bad variable naming ? Commented Aug 8, 2017 at 12:47
  • I think details is supposed to be inside the for loop in the second snippet. Commented Aug 8, 2017 at 12:53
  • @Ev.Kounis: It was an error of mine when trying to simplify existing code base. Thank you! Commented Aug 8, 2017 at 12:57
  • @BrendanGoggin: You are totally right, corrected code now! Commented Aug 8, 2017 at 12:57

3 Answers 3

6

You could use an intermediary generator to extract the details from your items. Something like this:

ret = { 
    'a': a,
    'b': [{
        'c': item.c,
        'e': details[0].e,
        'h': [func_h(detail) for detail in details],
    } for (item, details) in ((item, item.get_details()) for item in items)]
}
Sign up to request clarification or add additional context in comments.

Comments

3

I don't find the second one particularly un-pythonic; you have a complex initialization, and you shouldn't expect to boil down to a single simple expression. That said, you don't need the temporary list b; you can work directly with ret['b']:

ret = { 
    'a': a,
    'b': []
}

for item in items:
    details = item.get_details()
    d = details[0]
    ret['b'].append({
        'c': item.c,
        'e': d.e,
        'h': map(func_h, details)
    })

This is also a case where I would choose map over a list comprehension. (If this were Python 3, you would need to wrap that in an additional call to list.)

6 Comments

without being very different this seems much more readable and neat. +1
I'll note I could go either way with using d = details[0]. One moment I think the extra line is worth the less-cluttered dict literal, the next I think the indirection makes it less clear.
how about a third option d, *rest = item.get_details() and then 'h': map(func_h, [b] + rest)? not sure how this used to work with Python 2.7 (or even if...)
That's Python 3 syntax. If this were Python 3, though, you might write d, *_ = details = item.get_details() to get the first and all the elements in one (chained) assignment. Then there's no extra cost to recombine the head with the tail when setting h.
(The chained assignment is reminiscent of Haskell's at-pattern syntax, which would allow something like let details@(d:_) = get_details item in ....)
|
-1

I wouldn't try too hard to be more pythonic if it means looking like your first approach. I would take your second approach a step further, and just use a separate function:

ret = {
    'a': a,
    'b': get_b_from_items(items)
}

I think that's as clean as it can get. Use comments/docstrings to indicate what 'b' is, test the function, and then the next person who comes along can quickly read and trust your code. I know you know how to write the function, but for the sake of completeness, here's how I would do it:

# and add this in where you want it
def get_b_from_items(items):
    """Return a list of (your description here)."""
    result = []
    for item in items:
        details = item.get_details()
        result.append({
            'c': item.c,
            'e': details[0].e,
            'h': [func_h(detail) for detail in details],
        })
    return result

That is plenty pythonic (note the docstring-- very pythonic), and very readable. And of course, it has the advantage of being slightly more granularly testable, complex logic abstracted away from the higher level logic, and all the other advantages of using functions.

3 Comments

Why the downvote, please provide feedback in a comment if you can
i did not dv but it seems to me that you simply created a function that hides the mess rather than cleans it up.
@Ev. Kounis thanks for the response. Not quite how I'd phrase it, but yeah that's basically what I'm suggesting: Use comments and the function name to indicate what that b value is, but move the logic out of the main progession. As long as it works, and the next guy who comes along (even if it is the original programmer at a later date) can quickly understand what the b value is, I think that moving the mess out of the way is a good thing, and more pythonic than putting the code in the way (that being said, @khelwood's answer is pretty pretty).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.