From: Arnaud Delobelle on
Hi all,

Hi have a set of classes that represent mathematical objects which can
be represented as a string using a 'latex' method (after Knuth's famous
typesetting system). As I want to be able to typeset some builtin types as
well, I have a generic function, latex(), as follows:

def latex(val):
try:
return val.latex()
except AttributeError:
if isinstance(val, (tuple, list)):
return ", ".join(map(latex, val))
elif isinstance(val, dict):
return ", ".join(
"%s=%s" % (latex(k), latex(v))
for k, v in sorted(val.iteritems())
)
else:
return str(val)

It's EAFP and I have used this for a while with no problem. Recently I
added a new class for 'n choose r' objects, as follows:

class Choose(Expression):
def __init__(self, n, r):
self.subexprs = n, r
self.n = n
self.r = r
def calc(self, ns=None, calc=calc):
return choose(calc(self.n, ns), calc(self.r, ns))
def latex(self):
return "{%s \\choose %s}" % (latex(self.n), latex(self.k))

When I create a Choose object and try to get its latex representation,
this happens:

>>> c = Choose(5, 3)
>>> latex(c)
'<qmm.maths.expressions.Choose object at 0x17c92d0>'

This puzzled me for a bit: why is it not trying to use the latex()
method of the Choose object? I read and reread the definition of the
latex() method for a while until I found that there was a typo. Where
it says:

latex(self.k)

it should say:

latex(self.r)

Thus it triggers an AttributeError, which is exactly the kind of
exception that I am catching in the latex() function after trying
val.latex(). (Of course I could have caught this by calling c.latex()
directly but it's such a short method definition that I couldn't imagine
missing the typo!).

This means that EAFP made me hide a typo which would have been obviously
detected had I LBYLed, i.e. instead of

try:
return val.latex()
except AttributeError:
...

do

if hasattr(val, 'latex'):
return val.latex()
else:
...


So was it wrong to say it's EAFP in this case? Should I have known to
LBYL from the start? How do you decide which one to use? Up to now, I
thought it was more or less a matter of taste but now this makes me
think that at least LBYL is better than catching AttributeError.

Thanks for any guidance.

--
Arnaud
From: Ben Finney on
Arnaud Delobelle <arnodel(a)googlemail.com> writes:

> As I want to be able to typeset some builtin types as well, I have a
> generic function, latex(), as follows:
>
> def latex(val):
> try:
> return val.latex()
> except AttributeError:
[…]

> It's EAFP and I have used this for a while with no problem.
[…]

> I found that there was a typo. Where it says:
>
> latex(self.k)
>
> it should say:
>
> latex(self.r)
>
> Thus it triggers an AttributeError, which is exactly the kind of
> exception that I am catching in the latex() function after trying
> val.latex(). (Of course I could have caught this by calling c.latex()
> directly but it's such a short method definition that I couldn't
> imagine missing the typo!).
>
> This means that EAFP made me hide a typo which would have been obviously
> detected had I LBYLed

The correct approach here is to reduce the operations being done in the
'try' suite to a minimum.

The fact that you're calling a function in the 'try' block obviously
opens you to the potential for an AttributeError from that function.
Since that's not what you intend to catch, you need to separate those
concerns.

def latex(val):
def make_result_in_the_absence_of_a_latex_method():
result = transmogrify(val)
return result

try:
typeset_func = val.latex
except AttributeError:
typeset_func = make_result_in_the_absence_of_a_latex_method

result = typeset_func()
return result

--
\ “Two hands working can do more than a thousand clasped in |
`\ prayer.” —Anonymous |
_o__) |
Ben Finney
From: Malte Helmert on
Arnaud Delobelle wrote:

> This means that EAFP made me hide a typo which would have been obviously
> detected had I LBYLed, i.e. instead of
>
> try:
> return val.latex()
> except AttributeError:
> ...
>
> do
>
> if hasattr(val, 'latex'):
> return val.latex()
> else:
> ...
>
>
> So was it wrong to say it's EAFP in this case?

I would say that it's not really the EAFP concept that is problematic
here, but rather that the try block encompasses more code than it
should. Generally try blocks should be as narrow as possible, i.e., they
should contain only the part where you really want to catch a potential
failure.

"return val.latex()" does two separate things that might fail: the
lookup of val.latex, and the actual method call. If I understood you
correctly, you only want to catch the AttributeError in the "val.latex"
lookup here, and hence I'd say the "correct" application of EAFP here
would be something like:

try:
foo = val.latex
except AttributeError:
...
else:
return foo()

Whether that's any better than LBYL in this particular case is of course
debatable -- one nice thing compared to your LBYL version is that it
doesn't look up val.latex twice upon success. But you could also get
that via the LBYLish:

latex_method = getattr(val, "latex")
if latex_method:
return latex_method()
....

Malte

From: Malte Helmert on
Ben Finney wrote:

> def latex(val):
> def make_result_in_the_absence_of_a_latex_method():
> result = transmogrify(val)
> return result
>
> try:
> typeset_func = val.latex
> except AttributeError:
> typeset_func = make_result_in_the_absence_of_a_latex_method
>
> result = typeset_func()
> return result

In this particular case, where in the case of an AttributeError you want
to use a fallback callable with the same signature as the bound method
you get in case of success, I'd say getattr with a default is the nicest
approach:

def latex(val):
def make_result_in_the_absence_of_a_latex_method():
result = transmogrify(val)
return result
return getattr(val, "latex",
make_result_in_the_absence_of_a_latex_method)()


Doesn't work as nicely if you don't have
make_result_in_the_absence_of_a_latex_method's functionality bundled
into a suitable function already, though.

Malte

From: Matthew Barnett on
Arnaud Delobelle wrote:
> Hi all,
>
> Hi have a set of classes that represent mathematical objects which can
> be represented as a string using a 'latex' method (after Knuth's famous
> typesetting system). As I want to be able to typeset some builtin types as
> well, I have a generic function, latex(), as follows:
>
> def latex(val):
> try:
> return val.latex()
> except AttributeError:
> if isinstance(val, (tuple, list)):
> return ", ".join(map(latex, val))
> elif isinstance(val, dict):
> return ", ".join(
> "%s=%s" % (latex(k), latex(v))
> for k, v in sorted(val.iteritems())
> )
> else:
> return str(val)
>
> It's EAFP and I have used this for a while with no problem. Recently I
> added a new class for 'n choose r' objects, as follows:
>
> class Choose(Expression):
> def __init__(self, n, r):
> self.subexprs = n, r
> self.n = n
> self.r = r
> def calc(self, ns=None, calc=calc):
> return choose(calc(self.n, ns), calc(self.r, ns))
> def latex(self):
> return "{%s \\choose %s}" % (latex(self.n), latex(self.k))
>
> When I create a Choose object and try to get its latex representation,
> this happens:
>
>>>> c = Choose(5, 3)
>>>> latex(c)
> '<qmm.maths.expressions.Choose object at 0x17c92d0>'
>
> This puzzled me for a bit: why is it not trying to use the latex()
> method of the Choose object? I read and reread the definition of the
> latex() method for a while until I found that there was a typo. Where
> it says:
>
> latex(self.k)
>
> it should say:
>
> latex(self.r)
>
> Thus it triggers an AttributeError, which is exactly the kind of
> exception that I am catching in the latex() function after trying
> val.latex(). (Of course I could have caught this by calling c.latex()
> directly but it's such a short method definition that I couldn't imagine
> missing the typo!).
>
> This means that EAFP made me hide a typo which would have been obviously
> detected had I LBYLed, i.e. instead of
>
> try:
> return val.latex()
> except AttributeError:
> ...
>
> do
>
> if hasattr(val, 'latex'):
> return val.latex()
> else:
> ...
>
>
> So was it wrong to say it's EAFP in this case? Should I have known to
> LBYL from the start? How do you decide which one to use? Up to now, I
> thought it was more or less a matter of taste but now this makes me
> think that at least LBYL is better than catching AttributeError.
>
> Thanks for any guidance.
>
In addition to the other replies, you should've tested the Choose class. :-)