[TIP] Test runner checking for absence of 'None' in all functions (pytest)

Robert Collins robertc at robertcollins.net
Sun Jan 24 10:27:51 PST 2016


On 24 January 2016 at 10:27, David <ldl08 at gmx.net> wrote:
> Dear fellow Pythonistas,
>
> I am currently reading a book [1] that gives the following coding tip:
> "Prefer Exceptions to Returning None". You can see code examples out of
> the book further below.
>
> As I am teaching myself pytest these days, I would like to write a test
> runner (which eventually is to be part of a Continuous Integration
> setup) that fails should it detect any function that returns None.
>
> My problem is that I would like to limit the test runner to look
> *inside* of functions (and/or other structures).
>
> I can very well write a parser that checks, per line, if the words
> "return" and "None" are used. But that would cover the entire file in
> question, never mind its structure.
>
> Do you have any ideas how to check the 'absence of None' within
> functions and the like?

In addition to the answers you've already had, you could use a
systrace hook to detect a function returning None, though thats likely
to prevent coverage operating (unless some cooperative scheme is
figured out - coverage may already have one, I don't know). Further
its going to be complicated by determining whether a function is
appropriate in choosing not to propogate an exception.

> I am looking forward to your suggestions!
>
> Greetings and thanks,
>
> David
>
>
>
>
> # bad code
> def divide(a, b):
>     try:
>         return a / b
>     except ZeroDivisionError:
>         return None

So I agree that that is poor - it makes the type of the function's
range more complex and thus harder to reason about, and while as it
stands it doesn't hide information (since there is only one situation
where None is returned, you would find it awkward to extend it for
more errors.

But, what about

def status(state):
     logging.debug("Tracking %d of %d items.", state.tracked, state.seen)

- is this bad or erroneous code? If so - I'm genuinely interested in why.

If not, then clearly returning None (which it does, implicitly in this
case) isn't bad all the time, and what about this example?

def load_log(state):
    try:
        with open(debug_name(state), "rt") as f:
            log = parse_log(f)
    except IOError as e:
        if e.errno != errno.ENOENT:
            raise
        log = empty_log()
    state.log = parse_log(f)

Which, although its a little awkward due to the need to look inside
the ioerror. seems fairly clean to me - exceptions are a flow control
mechanism, and do not solely represent fatal, unrecoverable
conditions. This function still returns None though, and if we were to
push any part of its exception handling down a level we'd still want
to be signalling via an exception, and this function still doesn't
need to return anything. (*)

Anyhow, just some food for thought. I suspect I don't disagree with
Brett's tip in general, it just seems like more context around it is
needed? (I don't have the book, so can't comment on what it actually
says)

-Rob

*) Note though that a functional style would prefer no mutating state
at all, but Python and functional idioms have a complex relationship
:)


-- 
Robert Collins <rbtcollins at hpe.com>
Distinguished Technologist
HP Converged Cloud



More information about the testing-in-python mailing list