views:

420

answers:

6

In Python it's possible to create a procedure that has no explicit return. i.e.:

def test(val):
    if 0 == val:
        return 8

Further, it's possible to assign the results of that function to a variable:

>>> a = test(7)
>>> print `a`
'None'

Why the heck is that? What language logic is behind that baffling design decision? Why doesn't that simply raise a compiler error?

EDIT: Yes, I realise that it works that way. Thanks. My question is WHY? It seems like such a sure fire way to introduce subtle bugs into your code. And it seems, as was mentioned below by e-satis, to go against the very wise pythonic adage that "explicit is better then implicit".

So, what's up with this? Is it just a design oversight or a simplifying assumption or a deliberate, considered decision?

EDIT: Would everyone agree that expressing the intent this way is much better:

def test(val):
    if 0 == val:
        return 8
    else:
        return None

If so, why would Python prefer the former style to raising a compile time exception?

EDIT: S.Lott brings up the point explicitly (and others do too, his just seems most clear to me) that this behaviour is a result of the fact that Python is a dynamic language and therefor can't check at compile time, the run time behaviour of the system.

Since there is no distinction between procedures (those functions that return no values) and functions (those that do), that there is nothing to enforce at compile time. Which means that when we come to run-time behaviour, we either have a catastrophic failure in some cases (we throw an exception) or we silently fail assuming that the programmer knew what they were doing and understood the default behaviour.

The pythonic way is to do the latter, the C-style way is to do the former.

That seems to make sense as a good reason to do it that way.

S.Lott's clear justification is buried in the comments to the accepted answer so I thought it best to summarise here.

I'll hold off on accepting the answer for a bit to see if S.Lott makes an official answer. Otherwise, I'll give the points to SilentGhost.

Thanks all.

+2  A: 

While every function might not have an explicit return it will have an implicit one, that is None, which incidentally is a normal Python object. Further, functions often return None explicitly.

I suppose returning None implicitly is just an ease-of-use optimisation.

P.S. I wonder what you propose compile would do in the following case:

def t():
    if True:
        return 'ham'

P.P.S. return statement indicates that something needs to be returned, be it None, 42 or 'spam'. Some functions, however, don't really return anything, like list.sort or __init__, therefore it would be an overhead to require a return None statement at the end of each __init__.

SilentGhost
Fail to compile. That's my point and question.
James
"Fail to compile"? How so? It's impossible to validate every possible logic path except in the most trivial of cases. Consider `return someOtherFunction()`. Would you have the compiler then check `someOtherFunction` for some kind of "completeness"?
S.Lott
but you understand that it's just an example: what if I have `if-else` there, would it be fine?
SilentGhost
If I understand you right SilentGhost, you're asking if adding an else on there would make it all better? If that else contained a return statement then yes, it would. If not, then I don't see how it helps much.S.Lott I disagree strongly that it's impossible. In fact, most other compilers do check exactly that. I agree that it's not trivial, but I also think that the Python community is very competent and this is a rather standard language feature of other (admittedly, non-Python) languages.
James
@James: Other compilers have all code available to them. Python is a dynamic language. The compiler does not have all code available to it. It cannot do this kind of analysis without actually *running* the code. So, rather than run the code to see if it returns, then running the code for real, it just runs the code.
S.Lott
Interesting. Again it circles back to a ramification of being a dynamic language, though not an insurmountable one IMHO. Could you write that as an explicit answer?
James
+2  A: 

It's beautiful ...

It returns None in the case where val != 0 which to me make sense.

Python 2.6.1 (r261:67515, Jun 18 2009, 17:24:16)
[GCC 3.3.3 (SuSE Linux)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> def test(val):
...     if 0 == val:
...         return 8
...
>>> a = test(8)
>>> print a
None
>>> print test(0)
8
>>>

Do you have any arguments why you think the behavior is wrong?

You can use c++ if you need strong compile time type checking but for python it seems extremely flexible and I assume that's what you want when you selected python for your task.

stefanB
Well, in Python, explicit is better than implicit, so I guess this may not be the best example.
e-satis
What e-satis said. The default behaviour is wrong because the return value is unspecified and therefor the intended behaviour is unclear. In Python I shouldn't be able to do the unspecified: it should bite me.
James
+1  A: 

Heh. Coming at it from a Java perspective, your question is obvious. It should throw an error.

But if you came at it from a Perl perspective, it's not obvious at all. You're setting a variable to no value, big deal.

It's all about the typing. Like Java, Python is strongly typed, but unlike Java, it is also dynamically typed. Most dynamically typed languages allow shenanigans like this.

Satanicpuppy
+4  A: 

Python never checks for human misbehavior. The language philosophy is to assume that programmers are grown up adults and know what they are doing.

Since :

  • Python is an autodocumented language, so you'll know in a blink what returns a function;
  • Python uses duck typing, so getting None by default can ease dynamic function call a lot.

Returning None make more sens than raising an error.

E.G :

function_stack = [func1, func2, func3]

for f in function_stack :
    a = f()
    if a is not None :
        # do something

No matter if f is f() is a function or a procedure. There is no such thing in Python. You can apply all the function, and process output if there is any.

e-satis
I disagree. It seems unusual to allow this construct as a deliberate design decision in any product. If I wanted the function to return None, I'd make it return None. If I wanted to have no return, then I should also be held to that contract and be unable to assign the return value to a variable.Part of good programming style is setting these sorts of "tripwires" up so that we don't make silly mistakes. This "feature" seems especially suited to precisely those kind of silly mistakes.
James
Yeah, like that "feature" where the parameters passed to a function can be *any* type - what crackpot came up with *that* idea?! @James, you might as well ask why Python uses indentation instead of braces; or how come the important methods have all those ugly underscores; or why is there no support for interfaces. If you can't grasp the utility of this dynamic behavior, try writing some code. And don't just write some Java that happens to be in Python, try using these very features you question. If it still seems unnatural to you, you would probably be more comfortable with Java or C#.
Paul McGuire
Wow, wow, easy Paul :-) Some people from other languages can feel frustrated with Python. Hey, I feel frustrated when I don't use Python ! I think the only think we can do is to say : this is the pythonistas way to code, this is why we think it's good. Let the others take the part they want, there is sense to there approach too. @James : Python trades error checking for flexibility. It's a deal every Python coders know about, and we are quite happy about it. You can feel like it's not for you, and you'll be right to feel that way.
e-satis
A: 

What compiler? The bytecode compiler checks your code for syntax on import, but syntax doesn't include "check all paths for a return". So when test() gets called at runtime, and the value of x isn't 0 and we just fall out of the function, what should we do? return 0? Why not -1, or "", or -infinity? So a nice neutral value should always be returned in the absence of an explicit return statement - and that value is None. This is a standard feature of the language - welcome to Python!

Paul McGuire
So, "it doesn't work because it doesn't work"? The system should barf when it doesn't know what decision to make rather then picking an arbitrary one. To me, this is precisely a syntax error and should behave the same way that using an undeclared variable does.
James
The default value returned in the absence of any explicit return statement is not arbitrary - it is always and consistently the singleton None. In Ruby, the design decision was to, in the absence of an explicit return statement, return the last computed value. In Smalltalk, there are only methods on objects, and the default return value, failing an explicit return statement, is the target object itself (sometimes written out as `^self`). Statically typed languages have to ensure that all return values comply with the function's type, but this is not necessary in dynamically typed languages.
Paul McGuire
Interesting. If I understand you correctly, you're saying that it behaves this way for two reasons:1) Historical context (ie// it didn't in the beginning - for whatever reason - and to break that behaviour now would be unwise2) It's an unnecessary check based on language definition so it's excluded (even though its inclusion may result in better code). Huh. Could you include that as an explicit answer so I can vote it up and accept it?
James
I'm not sure I actually said point #1, and I dont feel qualified to talk about Python's historical context, I was simply citing some other dynamic languages. As for what results in better code, if static typing was all there was to it, we wouldn't be suffering under null object references, or array overruns, or spaceships blowing up because a benign exception wasn't handled. To code well in a language, you must grasp and adopt its idioms - please have some faith that, despite a counter-intuitive design feature, a language with this feature *can* be used to produce quality and reliable code.
Paul McGuire
That it can be used, I don't doubt. It just seems a rather odd feature to include deliberately. And the fact that it seems to behave that way deliberately, makes me wonder about the logic behind it. Is there something wise about doing it that way that I just don't see?
James
A: 

It's because of the dynamic typing.

You don't have to make the distinction between, say, turbo pascal's procedure and function. They are all function, they return None by default, which is logically correct. If you don't say anything it returns nothing, None.

I hope it make more sense now-

Andrea Ambu
None and "Unknown" are two very different concepts.
James
When a python function ends without an explicit return, it returns None. This is documented. I don't see how "Unknown" enters into it."Explicit is better than implicit" has its limits. For one `if x:` is strongly preferred to `if bool(x) is True:`. For another, you shouldn't return a value unless you want to return a value, and you should know that `None` is being returned. It makes dynamic constructs much easier. I don't think your case is very strong unless you can demonstrate some use case that is significantly harder to express in python than with explicit returns.
jcdyer