"Steve Horne" <sh at ttsoftware.co.uk> wrote in message

news:sr6l0t0ofsorqhojv77deecu2gjusgc8o9 at 4ax.com...

On 3 Nov 2000 19:39:07 GMT, msoulier at nortelnetworks.com (Michael P.

Soulier) wrote:

Hey people. If I want to put conditions in a lambda function, how

would I

[snip]

is a simple trick to doing this for conditions in general...

([no_result, yes_result] [(condition) + 1])

ie - put the possible answers in a list and then use the condition to

derive the subscript.

This is (roughly) ok *IF AND ONLY IF* it is OK to evaluate both results

independently of the condition's value.

Why "roughly": this will work if condition is either 0 or 1, but will

fail in different ways for most other values of 'condition'. Python

accepts many more kinds of values as 'true-or-false conditions'; any

non-zero number is 'true', an empty sequence is 'false' and other

sequences are 'true', etc.

It's easy to do the same by switching your idiom to:

(yes_result, no_result)[not (condition)]

as the "not" will obey exactly Python's general rules for what is

true and what is false, and it WILL return exactly 0 if condition

is true, exactly 1 if condition is false.

The 'deeper' problem remains: this will fail in some cases in

which if/else would work just fine, e.g....:

if foo != 0:

return 1/foo

else:

return 23

is NOT equivalent to:

return (1/foo,23)[not (foo!=0)]

because *both entries in the tuple will be evaluated, whatever

foo is worth*. So, when foo==0, the 'indexing' approach will

fail with a division-by-zero exception, while if/else would

give no problem.

This is because Python's general approach to evaluation is

"eager" (all arguments to a function are fully evaluated before

the function is called) and not "lazy" (only evaluate args

if and when their values are actually needed).

For cases in which this may be a problem, a closer approach

to 'conditionals' (if/else behavior) is supplied by Python's

operators "and" and "or". THOSE (and those only) avoid "too

much zeal" (eagerness) in evaluating their operands; their

semantics specifies "short-circuit" evaluation -- the right

side operand gets evaluated at all _ONLY_ if its value is

actually needed to give the operator's result.

For "and": if the left-side operand's value is "false", then

the whole "and" operation returns "false", and the right-side

operand *is NOT evaluated at all*. If the left-side operand's

value is "true", then the whole "and" operation returns as

its value the value of the right-side operand.

In other words, there IS semantic equivalence between:

return a and b

and

temp = a

if temp:

return b

else:

return temp

for ANY expressions a and b (I've used an explicit 'temp'

in the 'equivalent-semantics' construct to underline that

a is not multiply evaluated...).

"or" works similarly, i.e. it also "short-circuits", but

"the other way around": the equivalence becomes, then:

return a or b

and

temp = a

if temp:

return temp

else:

return b

So, the full semantic equivalence to:

if condition:

return yes_result

else:

return no_result

can *almost* be built up as an expression by:

return (condition and yes_result) or no_result

where the "almost" comes from the risk that "yes_result"

may evaluate to false... in which case the "no_result"

would unwontedly be evaluated and returned!

One trick to bypass even this issue may be...:

return ((condition and (yes_result,)) or (no_result,))[0]

By making the right-side of the "and" into a one-item

tuple, we know Python will _never_ evaluate it as "false"!

(We then also need to make the no_result into a similar

one-item tuple, so we can end by getting the 0-th [only]

element in either case...!).

So much subtlety is hardly ever needed. Most of the

time, either the indexing-trick or the simpler and/or

trick will be provably OK (if we know it's OK to

evaluate both results, and/or we know the yes_result

cannot be false). If any cases subsist where the

"full trick" seems necessary, I think I would always,

as an issue of style, prefer to eschew the lambda in

favour of a local named-function, where I will need

no dirty tricks whatsoever to express my wishes...

lambda is supposed to be there *as a convenience*

(it's basically just saving you the trouble of thinking

up a name for some inconsequential thingy) -- why keep

using it in cases where it turns out to be so massively

*IN*-convenient, as to force such complexities...?!-)

Say, for example, that I need to write a function

with the signature:

def invseq(numseq, onzero)

and the semantics: return a sequence with the numeric

inverse 1/x of each number x in numseq, except that,

if x==0, 'onzero' needs to be used in lieu of 1/x in

that spot in the result-sequence.

I think the cleanest, most readable solution is:

def invseq(numseq, onzero):

def inv(x, default=onzero):

if x: return 1/x

else: return default

return [inv(x) for x in numseq]

or, in older Python versions (or for list-comprehension-

haters:-):

def invseq(numseq, onzero):

def inv(x, default=onzero):

if x: return 1/x

else: return default

return map(inv, numseq)

even though the lambda-equivalent is more compact, and,

in this case, can be coded with the simple-and/or-trick:

def invseq(numseq, onzero):

return map(lambda x,default=onzero: ((x!=0) and (1/x)) or default,

numseq)

It seems to me that _naming_ the "invert" subfunction,

in this case, increases clarity, even though the line

count becomes 4 (moderate-length) lines versus one (a

bit too-long) line. An issue of style, of course.

Now, say we must handle _two_ sequences (of the same

length), returning a sequence that, at each spot,

has x/y (where x is the corresponding element of

the first sequence, y of the second one) _except_

that, for those spots in which y is 0, x+1 is to be

returned instead. The simple-trick...:

def divseqs(seqx, seqy):

return map(lambda x,y: ((y!=0) and (x/y)) or x+1, seqx, seqy)

doesn't work any more -- at those spots where x==0,

we'd be wrongly returning 1 instead! We need the

full-trick in this case...:

def divseqs(seqx, seqy):

return map(lambda x,y: (((y!=0) and (x/y,)) or (x+1,))[0], seqx, seqy)

and it seems to me that its readability is truly abysmal.

Wouldn't you rather write...:

def divseqs(seqx, seqy):

def div(x, y):

if y: return x/y

else: return x+1

return map(div, seqx, seqy)

or equivalently (Python 2):

def divseqs(seqx, seqy):

def div(x, y):

if y: return x/y

else: return x+1

return [div(x,y) for x, y in zip(seqx,seqy)]

...?

Alex