Returning none

Tim Peters tim_one at email.msn.com
Sun Aug 29 17:32:27 EDT 1999


[C.Laurence Gonsalves]
>|> ...
>|> Personally, I think falling out of functions and expecting
>|> None to be implicitly returned is a very Perl-ish thing to do...

[Tim]
>| [sez Perl actually returns the last expression evaluated, which
>|  is what CLG intended in his example]

[CLG]
> Yes, in this case. I was referring to the way that Perl tries
> to guess what you meant rather than treat dubious code as
> incorrect.

Except that Python isn't trying to guess what you mean here -- as before,
functions returning None by default is not a convenience, it's a cheap way to
make an error likely when mistakenly using a procedure as a function.  It's not
a bulletproof approach, but it is *an* approach, and mischaracterizing its
nature weakens your case.

>|> Why don't we add $_ to Python as well?

>| [and why $_ makes more sense in Perl than it would in Python]

> I was being sarcastic.

I know -- that's why I wasn't <wink>.  Dismissing ideas out of hand as all loss
and no gain is as unfruitful as embracing suggestions as if all gain and no
cost.  As PaulP said, in reality it's a balancing act.

> I was pointing out that if Python is going to start making wild
> guesses about one thing, then it might as well do it everywhere
> else as well.

Except (a) it's not making wild guesses here; and, (b) compromises are indeed
made everywhere anyway, so we're already at the bottom of the slippery slope
you fear <wink>.

>| ...
>} Python doesn't return None for [convenience]; it's instead a cheap
>| way to make [an error likely]

> That sort of argument

It's not an argument:  it's the fact of how this particular compromise came
into being.

> also says that when I go outside the bounds of a list in Python,
> I should get back None, since in C I would access random stack/heap
> trash.

I agree that having an out-of-bounds list reference silently return None would
also be a way to make it (merely) likely to lead to an error.

> Instead, Python raises an exception. I think it should do the same
> if you don't return anything, and then try and access that nothing.

If there were no costs associated with this, or if the costs were thought
smaller than the benefits, don't you think Python already would?

In the list case, it cost nothing:  no additional syntax or long-winded rules
were required, and the interpreter had to check for out-of-bounds anyway (else
risk having the interpreter itself blow up due to a bogus C operation).

As to benefits, I'm with most of the other respondents:  I can't recall this
insecurity (it certainly is one!) ever causing a significant problem -- and
I've been programming in Python even longer than Tom Christiansen <wink>.

> ...
> That's why I also suggested some other possible rules that would
> enable some amount of compile-time checking, so that function
> writers who do questionable things would be alerted.

The best hope for this is that it gets folded into the push for optional type
declarations in Python2; that will need a way to indicate function return types
(or lack thereof) anyway.

...

>| Do you imagine Guido didn't consider this when he designed
>| the language?

> So Python is *absolutely perfect* and shouldn't be changed at all?

No.  You showed no sign of having considered why it might be the way it is, so
I was inviting you to do so.  Sarcasm was not the hoped-for result.

> I guess us mere mortals shouldn't even bother making any suggestions
> then.

Ditto.

  Python 2 will just be Python 1.5.2 with a new name...

Ditto.

> In another post I suggested another idea that requires little or no
> modifications to the grammar, but would just add some new runtime and
> compile time checks.

You should study eval_code2 (in Python/ceval.c) if you want to suggest
implementations.  That would give you a handle on part of the "cost" side of
the tradeoffs.

> Functions using return (or falling off the end) would return nothing
> at all (rather than None),

The interpreter can't do that as currently structured -- a call must return a
pointer to a PyObject.  You could invent a new internal NotARealObject type and
return a pointer to an object of that type, but then it gets messier.

Currently a CALL_FUNCTION opcode simply pushes the object it gets back -- it
has *no idea* whether the result will be used later or discarded, so doesn't
have enough info to know whether to complain.  So you'll have to make
CALL_FUNCTION very much smarter about its context.

But then you have miserable semantic issues to resolve; for example, while you
clearly must allow f to return nothing given the statement

    f()

is it also OK for f() to return nothing in the statement

    f(), g(), h()

?  You must allow that else break existing code, but the result of f() *is*
used by a later BUILD_TUPLE opcode:  it's not the case that "returning nothing"
can work here, even if the interpreter were structured in a way that such a
thing were possible -- BUILD_TUPLE requires an object.

In this example, the tuple itself is later discarded, but there's no way for
CALL_FUNCTION to know that without prior flow & lifetime analysis (Python has
neither today), and a rigorous definition of which opcodes do and don't count
as "real uses".

Simply returning None was a pragmatic compromise that saved a world of
implementation pain in return for a tiny chance of letting an error go
undiagnosed forever, and an excellent chance of catching an error but later
than you might hope for.

> and function definitions would require that the function either
> always return a value, or never returns a value. This can be very
> easily checked at compile time.

While this part is more clearly doable, it's also a probabilistic compromise.
In your original example, it wouldn't complain (you had no returns, so this
scheme would let it pass).  Better for optional declarations to allow you to
say explicitly whether you intend to return a result or not, rather than
replace one "guessing" scheme with another.

It also introduces an ugliness Python doesn't suffer today; e.g.,

def first_factor(n):
    for i in xrange(2, n):
        if n % i == 0:
            return i
    complain(ValueError, n, "doesn't have a non-trivial factor")

def complain(exc, *args):
    msg = string.join(map(str, args), " ")
    raise exc(msg)

There's nothing wrong with either function, but to stop a bogus complaint the
first would need a

    return 0   # make the stupid compiler shut up

line at the end.  To avoid this kind of artificial ugliness in a language with
exceptions requires something akin to Java's (many) pages of "definite
assignment" rules.  I happen to *like* Java's rules here (they catch many
errors at compile-time Python cannot, and almost never give me bogus
complaints), but without static declarations (or global inference coupled with
weakened dynamics) Python can't approach that.

nothing-is-a-pure-win-ly y'rs  - tim






More information about the Python-list mailing list