First I want to thank everyone that posts to this group. I read it
daily and always learn something new even if I never feel like I have
anything to contribute but my questions.
When I define a method I always include a return statement out of
habit even if I don't return anything explicitly:
def something():
# do something
return
Is this pythonic or excessive? Is this an unnecessary affectation
that only adds clock ticks to my app and would I be better off
removing "returns" where nothing is returned or is it common practice
to have returns.
Even when I'm not explicitly returning something I like to add
"return" because it's a good additional visual marker for me to see
where a method definition ends especially in cases where I may use a
nested method.
Thanks for the discussion!
Roger.
returning nothing does nothing :)
>>> from dis import dis
>>> def f1():
... print "hello"
...
>>> def f2():
... print "hello"
... return
...
>>> dis(f1)
2 0 LOAD_CONST 1 ('hello')
3 PRINT_ITEM
4 PRINT_NEWLINE
5 LOAD_CONST 0 (None)
8 RETURN_VALUE
>>> dis(f2)
2 0 LOAD_CONST 1 ('hello')
3 PRINT_ITEM
4 PRINT_NEWLINE
3 5 LOAD_CONST 0 (None)
8 RETURN_VALUE
>>>
> Even when I'm not explicitly returning something I like to add
> "return" because it's a good additional visual marker for me to see
> where a method definition ends especially in cases where I may use a
> nested method.
>
Well, I suppose at least you aren't writing "return None" ...
Normally a blank line or two suffices for me.
Take a look at PEP 8 for some discussion for Python coding style.
http://www.python.org/dev/peps/pep-0008/
regards
Steve
--
Steve Holden +1 571 484 6266 +1 800 494 3119
Holden Web LLC http://www.holdenweb.com/
It's not particularly excessive but it is uncommon. A nekkid return
can sometimes be essential within a function body, so a non-essential
nekkid return could be considered just noise.
G.
If it's the last statement in the function body, it is indeed "excessive".
OTHO I sometimes end a function with an explicit "return None" when
there are branches with early returns, ie:
def func():
if some_condition:
return something
return None
to make really clear what happens - even if it _should_ be clear without
the last statement. IOW : use your own judgement.
--
Manish Sinha
Personal Blog: http://www.manishsinha.info
Tech Blog: http://manishtech.wordpress.com
OpenPGP Key: 99E6658F
> Roger wrote:
>> Hi Everyone,
>>
>> First I want to thank everyone that posts to this group. I read it
>> daily and always learn something new even if I never feel like I have
>> anything to contribute but my questions.
>>
>> When I define a method I always include a return statement out of habit
>> even if I don't return anything explicitly:
>>
>> def something():
>> # do something
>> return
>>
>> Is this pythonic or excessive? Is this an unnecessary affectation that
>> only adds clock ticks to my app and would I be better off removing
>> "returns" where nothing is returned or is it common practice to have
>> returns.
>>
> It's an unnecessary affectation, but I don't believe it adds any clock
> ticks to your app, as the function has to return anyway. The dis module
> shows you they both generate exactly the same code:
...
>> Even when I'm not explicitly returning something I like to add "return"
>> because it's a good additional visual marker for me to see where a
>> method definition ends especially in cases where I may use a nested
>> method.
>>
> Well, I suppose at least you aren't writing "return None" ... Normally a
> blank line or two suffices for me.
Curious. When I see a bare return, the first thing I think is that the
author forgot to include the return value and that it's a bug.
The second thing I think is that maybe the function is a generator, and
so I look for a yield. If I don't see a yield, I go back to thinking
they've left out the return value, and have to spend time trying to
understand the function in order to determine whether that is the case or
not.
In other words, even though it is perfectly valid Python, bare returns
always make the intent of the function less clear for me. I'm with Bruno
-- if you have a function with early exits, and you need to make the
intent of the function clear, explicitly return None. Otherwise, leave it
out altogether.
--
Steven
To me this is the soundest argument. Thanks for the advice. I think
I'll follow this as a rule of thumb hereafter.
You shouldn't, though; Generators can't contain any return statement.
Yes, they can. It doesn't return a value, it just raises a StopIteration error.
In [18]: def g():
for i in range(5):
if i == 3:
print 'Early exit.'
return
print 'Should not happen.'
yield i
....:
....:
In [25]: list(g())
Early exit.
Out[25]: [0, 1, 2]
--
Robert Kern
"I have come to believe that the whole world is an enigma, a harmless enigma
that is made terrible by our own mad attempt to interpret it as though it had
an underlying truth."
-- Umberto Eco
What gave you that impression?
<experimentation>
Python 2.6.1 (r261:67517, Dec 4 2008, 16:51:00) [MSC v.1500 32 bit
(Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> def foo():
... for i in range(4):
... yield i
... return
...
>>> foo
<function foo at 0x00F5AF30>
>>> x = foo()
>>> x
<generator object foo at 0x00F61080>
>>> list(x)
[0, 1, 2, 3]
>>> def bar():
... for i in range(4):
... yield i
... return 42
...
File "<stdin>", line 4
SyntaxError: 'return' with argument inside generator
</experimentation>
<manual>
(go to http://docs.python.org/reference/datamodel.html#the-standard-type-hierarchy
then scroll down)
Generator functions
A function or method which uses the yield statement (see section
The yield statement) is called a generator function. Such a function,
when called, always returns an iterator object which can be used to
execute the body of the function: calling the iterator’s next() method
will cause the function to execute until it provides a value using the
yield statement. When the function executes a return statement or
falls off the end, a StopIteration exception is raised and the
iterator will have reached the end of the set of values to be
returned.
</manual>
Please don't. Follow MRAB's advice, with the corollary that a
generator is forced by the compiler to be a "procedure" in MRAB's
terminology.
Yup, this is what I took away from this discussion. Thanks!
I fail to see any *practical* difference between MRAB's and Steven's
POVs. In both cases, it boils down to
- don't use a bare return at the end of a def statement's body,
- either use only bare returns ('procedure') or explicitely return None
('function')
Steven's treatment was somewhat discursive, and didn't explicitly
mention the 'procedure' possibility. In fact, this sentence "if you
have a function with early exits, and you need to make the intent of
the function clear, explicitly return None." would if applied to a
'procedure' cause a stylistic horror as bad as a bare return at the
end of the def.
Ok. You're right.
One style of coding I heard about once only permits returns at the end
of a function. It claims it makes it easier to see the function as a
mathematical object.
It's a slick idea, but multiple exit points are really practical.
Incidentally, generators have multiple entry points. They "yield
multiple times, they have more than one entry point and their
execution can be suspended" -- http://docs.python.org/reference/expressions.html#yield-expressions
The discussion makes me think that 'clear' is subjective, just like
'natural' has 39 definitions.
> One style of coding I heard about once only permits returns at the end
> of a function. It claims it makes it easier to see the function as a
> mathematical object.
That's silly. You treat the function as a black box: input comes in, and
output comes out. You have no idea of what happens inside the black box:
it could loop a thousand times, take 150 different branches, or take one
of 37 different exit points. From the outside, it's still exactly like a
mathematical object. Internal complexity is irrelevant. This is why
mathematicians can perform algebra on complicated functions like Bessel's
function (of the first or second kind), without needing to care that
actually calculating Bessel's function is quite tricky.
What I think the one-return-per-function style is aiming at is that it is
(sometimes) easier to analyse the internals of the function if there are
few branches. The more complicated branches you have, the harder it is to
analyse the function. Early exits on their own are not the cause of the
complexity: it's the number of branches leading to the early exit that
causes the problem.
Avoiding early exits is an over-reaction to the Bad Old Days of spaghetti
code. But used wisely, early exists can simplify, not complicate, code.
Consider the following:
def find_ham(alist):
for item in alist:
if isinstance(item, Ham):
return item
raise ValueError('no ham found')
def find_spam(alist):
found_item = None
for item in alist:
if found_item is not None:
if isinstance(item, Spam):
found_item = item
if found_item is None:
raise ValueError('no spam found')
else:
return found_item
The second version has double the number of lines of code of the first.
It introduces an extra variable "found_item" and two extra if blocks. I
don't think the claim that the version with an early exit is more
complicated than the version without can justified.
--
Steven
You'd think they would have noticed that. Eliminating early exits
doesn't change the number of branches!
> Avoiding early exits is an over-reaction to the Bad Old Days of spaghetti
> code. But used wisely, early exists can simplify, not complicate, code.
To make your case, you don't even need to prove that a wise early exit
can simplify. You just need that an early exit can simplify. (The
conclusive case is the early exit is better. For that, it's
sufficient but not necessary to show that simplicity is your reader's
highest priority. What's instructive about that is the
counterexample: when is simpler not better? (Not rhetorical. Or, if
never, then it's the top priority and consistent with every top
priority, if my calculations are correct; but it remains to show that
early exits can simplify.)
snip better simpler
> The second version has double the number of lines of code of the first.
> It introduces an extra variable "found_item" and two extra if blocks. I
> don't think the claim that the version with an early exit is more
> complicated than the version without can justified.
In a lab report, this is the part where the author would be arguing,
"And the object accelerated at 9.8 m/s^2, which proves our
hypothesis." I think you're showing that early exits can simplify,
but you're bringing in more factors. I think you're trying to say
that less lines of code is simpler, and less variables is simpler.
Those lack proofs.
You need:
M: Less lines of code is simpler.
m: Simpler is always better.
C: Less lines of code is better.
Or something. But no amount of thrashing, bludgeoning, wibbling,
whimpering, or beating children with blunt objects, is going to change
your reader's knowledge of good. Only experience is. Fine fine, so
leather belts are bad. That doesn't make fewer lines of code good.
However, the burden of proof is (definitely IMO) on the people that
are unilaterally opposed to early returns. They need:
M: Early exits are never simpler.
m: Simpler is always better.
C: Early exits are never better.
Oddly enough, you share a minor premise.
Mostly, yes. It can also be a way to help avoiding "resource leaks"
(memory or whatever) - just like try/finally blocks or the 'with'
statement in Python.
> But used wisely, early exists can simplify, not complicate, code.
>
> Consider the following:
>
> def find_ham(alist):
> for item in alist:
> if isinstance(item, Ham):
> return item
> raise ValueError('no ham found')
>
>
> def find_spam(alist):
> found_item = None
> for item in alist:
> if found_item is not None:
> if isinstance(item, Spam):
> found_item = item
> if found_item is None:
> raise ValueError('no spam found')
> else:
> return found_item
>
> The second version has double the number of lines of code of the first.
And doesn't do the same thing (hint: you forgot a break statement). Also
and FWIW, the "single exit" golden rule (hem) comes from languages
without exception handling. So a fair comparison would be:
def find_egg(alist):
for item in alist:
if isinstance(item, Egg):
return item
return None
vs:
def find_sausage(alist):
found = None
for item in alist:
if isinstance(item, Sausage):
found = item
break
return found
Granted, this still doesn't make the second version better than the
first !-)
But:
> I don't think the claim that the version with an early exit is more
> complicated than the version without can justified.
Certainly not in this simple (simplistic ?) example. Now in C or Pascal,
functions tend to be much longer and complicated, thanks to all the gory
details one has to take care of.
Not that I'm advocating "no early return" as a "golden rule" - specially
in Python where functions tend to be short and readable -, but there
really are cases (depending on the language *and* concrete use case)
where avoiding early returns makes for more readable and robust code.
My 2 cents...