# [Tutor] function return values

Cameron Simpson cs at cskk.id.au
Fri Dec 3 02:04:54 EST 2021

```On 03Dec2021 16:34, Phil <phillor9 at gmail.com> wrote:
>On 2/12/21 18:16, Cameron Simpson wrote:
>>For a function returning a valid result or no result you have two main
>>approaches in Python:
>>- return the result or None (or None-ish, as you are doing)
>>- return the result or raise a ValueError("no solution found")
>
>Thank you Cameron for your detailed reply which has given me something
>
>I think the return of None, None, None is the simplest option.

That's fine.

>I've seldom ever used exceptions and I've never found a case where I
>could use the yield statement. I can see that it would be useful if I
>needed to return multiple values of the same variable from a function.

Generators are good when:
- there's a series of values, sometimes unbounded (eg primes)
- the series is expensive to compute, either per item or in total ("all
the primes" has infinite cost)
because they compute until they yield a value. The code consuming/using
those values can process that value. It can then resume the generator
(which has simply paused at the "yield") or just cease using it, so the
generator no longer runs. So they compute only what gets consumed,
nothing more.

>I'll try to keep the yield statement in mind it could be useful
>somewhere and better than the use of a list.

Not that generator functions (functions containing a "yield" statement)
are used _differently_ to normal functions.

def gen():
x = 1
while True:
yield x
x += 2

This generates odd numbers. The _return_ of the function is an iterable,
the generator:

g = gen()

At this point the function _has not run_. If you print(g) you'll see a
generator object. It is iterable, and a common way to use it is to
iterable with a for-loop:

for n in g:
print(n)

Each iteration of the loop rns the function until it yields, and you get
what it yielded as the next value in the iteration. So the for-loop
about assigns odd numbers to "n".

>>or this for your None-ish approach:
>>
>>     item, row, column_list =_ self.boxPairsInSameRow(candidate_pairs, x , y)
>>     if (item, row, column_list) == (None, None, None):
>
>Should the Nones be enclosed within brackets? I didn't do that. I know
>that "if (a + b) == 2" is C syntax.

That's a precedence thing (and a readability thing, possibly). This is a
3-tuple:

None, None, None

You know you don't need the brackets - your return-statement used no
brackets, and it returns a tuple. (All Python functions return exactly
one object - your object just happens to be a tuple with 3 elements
inside it).

_However_, the == operator binds more tightly that the , operator. So
this:

if item, row, column_list == None, None, None:

is actually a 5-tuple:

item
row
column_list==None
None
None

Being a non-empty tuple, it is always true. Not what you wanted to test.

This:

if (item, row, column_list) == (None, None, None):

is a Boolean expression comparing 2 3-tuples. Inside brackets to get the
groups right. So "item, row, column_list" is the left hand 3-tuple, and
it is in brackets to keep it together. No different to:

(2 + 3) * 5

2 + 3 * 5

meaning 17, because arithmetic precedence groups the "*" tighter than
the "+".  Brackets to override default operator precedence.

>>A third alternative turns on your statement: "Once the pairs have been
>>found on a row then there is no need to search the remaining row or
>>rows". Is it _meaningful_ to search for more solutions, even if you
>>_currently_ just want the first one?
>
>There can only be a pair and they must be on the same row, not
>necessarily on the first row. So what I wrote was not correct. I do
>not need to continue searching once a pair has been found on a row but
>I do need to search beyond the first row in case the pair is on row 1
>or 2. I don't think the use of a generator is called for here.

On that basis, I don't think so either.

Cheers,
Cameron Simpson <cs at cskk.id.au>
```