The Samurai Principle

P

Phlip

Pythonistas:

The "Samurai Principle" says to return victorious, or not at all. This
is why django.db wisely throws an exception, instead of simply
returning None, if it encounters a "record not found".

I illustrated the value of that concept, here:

http://c2.com/cgi/wiki?SamuraiPrinciple
 
A

Albert Hopkins

Pythonistas:

The "Samurai Principle" says to return victorious, or not at all. This
is why django.db wisely throws an exception, instead of simply
returning None, if it encounters a "record not found".

How does that compare to, say, the "Kamikaze Principle"? ;)

-a
 
P

Phlip

How does that compare to, say, the "Kamikaze Principle"? ;)

Return victorious AND not at all!

(All return values are packed up and thrown...;)
 
P

Phlip

Back to the topic, I tend to do this:

for record in Model.objects.filter(pk=42):
return record

return sentinel

Having lots of short methods helps, because return provides both
control-flow and a result value. But it abuses 'for' to mean 'if'. I
feel _reeeeally_ guilty about that!

But I miss this, from (cough) RoR:

record = Model.find(42) || sentinel

Django should provide this:

record = Model.objects.get(pk=42, _if_does_not_exist=sentinel)

sentinel could be a lambda that concocts a new record (where such a
record should not be created with get_or_create()). That would be
efficient when you don't spend time constructing it just so the happy-
path of .get() can throw it away.

Or sentinel could be None, or a NullObject that efficiently behaves
like a record but provides stubbed-out behaviors.

My committees will be submitting these proposals to the Django
committees shortly... C-:
 
I

Ian Kelly

Back to the topic, I tend to do this:

 for record in Model.objects.filter(pk=42):
    return record

 return sentinel

How is that any better than just catching the exception?

try:
return Model.objects.get(pk=42)
except Model.DoesNotExist:
return sentinel

The flow of control is much clearer this way.

Cheers,
Ian
 
P

Phlip

How is that any better than just catching the exception?

try:
    return Model.objects.get(pk=42)
except Model.DoesNotExist:
    return sentinel

The flow of control is much clearer this way.

It reminds me of Visual Basic.

And no it's not "much clearer". Exceptions are for catastrophic errors
that the caller should care not to handle. A "record not found" is not
a catastrophe. Read my original post.

AAAND you need to test that the DoesNotExist occurs for the exact
reason you expect. Your except is not complete. Making it complete is
very hard, and will break as soon as the model changes.
 
I

Ian Kelly

And no it's not "much clearer".

It's clearer because it does exactly what it says it does, unlike your
approach that masquerades as a loop.
Exceptions are for catastrophic errors

No, they're for flagging "exceptional" states. /Errors/ are for
catastrophic errors. The fact that errors are a subset of exceptions
is just for convenience in handling.
AAAND you need to test that the DoesNotExist occurs for the exact
reason you expect.

I'm not following you here. The only possible reason the exception
can occur is if no matching row exists. If there were some other
reason for raising an exception, then a different exception would be
raised.
Your except is not complete. Making it complete is
very hard, and will break as soon as the model changes.

Still not following you. What is it missing, and how will it break?
 
T

Tim Chase

It reminds me of Visual Basic.

And no it's not "much clearer". Exceptions are for catastrophic errors
that the caller should care not to handle. A "record not found" is not
a catastrophe.

Exceptions are not limited to catastrophic errors, simply
exceptional (not the common) cases. E.g. iterators raising
StopException when exhausted.
Traceback (most recent call last):
.... print v
....
0
1

Running out of things to iterate over is pretty non-catastrophic
in my book. :)

Using exceptions as in the grandparent's post seem perfectly fine
to me. The other option would be to LBYL:

items = list(MyModel.objects.filter(...))
if len(items) == 1:
do_something(items[0])
else:
what_the(...)

-tkc
 
P

Phlip

Exceptions are not limited to catastrophic errors, simply
exceptional (not the common) cases.  E.g. iterators raising
StopException when exhausted.

Exceptions are not "because we should only return one type of thing".
They are for situations which the caller should care not to handle.
Exceptions are for propagating. A "record not found" is an exemplary
example of a situation the caller _should_ handle.
   items = list(MyModel.objects.filter(...))
   if len(items) == 1:
     do_something(items[0])
   else:
     what_the(...)

Both your version and mine read an entire cursor. But mine only rezzed
the first object, whereas yours rezzed every object in the cursor,
just to throw most of them away!
 
T

Tim Chase

Exceptions are not "because we should only return one type of thing".
They are for situations which the caller should care not to handle.
Exceptions are for propagating. A "record not found" is an exemplary
example of a situation the caller _should_ handle.

Um...first you state "Exceptions are for catastrophic errors that
the caller should not care to handle. A 'record not found' is not
a catastrophe" and then you contradictingly go on to state "A
'record not found' is an exemplary example of a situation the
caller _should_ handle". I'm not sure I follow your logic here.
Exceptions allow both (1) the ability to handle the exceptional
condition locally if you want to (including suppressing it) and
(2) propagate the exception if you want to make the caller handle it.

And if you really want, you can just subclass QuerySet to provide
your own get_or_none() method to return your sentinel.
items = list(MyModel.objects.filter(...))
if len(items) == 1:
do_something(items[0])
else:
what_the(...)

Both your version and mine read an entire cursor. But mine only rezzed
the first object, whereas yours rezzed every object in the cursor,
just to throw most of them away!

If a .get() returns more than one object (non-unique criteria are
used), what _should_ it return? Agreed, if it pulls back a
bajillion records, that's bad, so if you're concerned your
conditions might do that despite the expectation they bring back
1-and-only-1 (.get() currently raises an exception if it brings
back more than one result db/models/query.py around line 342
where MultipleObjectsReturned is raised), then I'd just slice them:

items = list(MyModel.objects.filter(...)[:1])
if items:
do_something(items[0])
else:
what_the(...)

-tkc
 
B

Bruno Desthuilliers

Phlip a écrit :
I don't see how anyone could WTF that. Are you pretending to be a newb
who doesn't understanding it? F'em.

F'... newbies is definitly not the pythonic mindset. Python's mindset is
about doing the most obvious thing, no trying to be smart. The obvious
code here is:

try:
return Model.objects.get(pk=42)
except Model.DoesNotExist:
return sentinel

so yes, your above snippet is bordering on WTF since it's not immediatly
obvious - it takes at least one more second for me to parse, and I'm
definitly not a Python nor Django newbie. That's something I'd
immediatly rewrite if I had to work on this code.
I would guess that Django provides some basic rules for avoiding name
collisions.

yes : common sense.
Nobody should call a field "pk__in"

Nope, but "default" - which would be the obvious keyword here - is also
a perfectly legitimate field name.
Know I gotta learn to add a new method to an existing class!

It's as straightforward as possible once you know Python's object model:

def somefunc(self, whatever):
self.do_something_with(whatever)

import somemodule
somemodule.SomeClass.mymethod = somefunc
 
B

Bruno Desthuilliers

Phlip a écrit :
It reminds me of Visual Basic.

Strange enough, your own code snippet reminds me of what I used to find
when fixing VB programs some ten years ago.
And no it's not "much clearer".

It is for any Python programmer - it's even TheOneObviousWay.
Exceptions are for catastrophic errors

Chapter and verse, please ?

Exceptions are for exceptional situations. When you call queryset.get,
you do expect to have one single instance matching the lookup - specialy
when doing a pk lookup.

AAAND you need to test that the DoesNotExist occurs for the exact
reason you expect.

Bullshit. The only reason you'd get this exception is because there's no
record matching your where clause.
Your except is not complete.

Why so ?
Making it complete is
very hard, and will break as soon as the model changes.

Why so ?
 
P

Phlip

try:
   return Model.objects.get(pk=42)
except Model.DoesNotExist:
   return sentinel

Visual Basic Classic had a Collection Class, which worked essentially
like a real language's Hash, Map, or Dict.

Except - it had no operation to test membership. It also could not
enumerate by key and value (which would be 'for k,v in dict.items()').
To test for membership, you _had_ to catch an exception - using VB's
tragically clumsy exception model.

Hours of fun. That leads us to this topic:

http://www.google.com/search?q=don't+use+exceptions+for+normal+control+flow
 
B

Benjamin Kaplan

Visual Basic Classic had a Collection Class, which worked essentially
like a real language's Hash, Map, or Dict.

Except - it had no operation to test membership. It also could not
enumerate by key and value (which would be 'for k,v in dict.items()').
To test for membership, you _had_ to catch an exception - using VB's
tragically clumsy exception model.

Hours of fun. That leads us to this topic:

http://www.google.com/search?q=don't+use+exceptions+for+normal+control+flow
--


An experienced C programmer can program C in any language, but that
doesn't mean it's a good idea to.

When you're using a language, you should use the style that the
language emphasizes. While you shouldn't use exceptions for control
flow in C++, Java, or C#, there's nothing wrong with using them as
such in Python.
 
T

Terry Reedy

They are for situations which the caller should care not to handle.

Python is simply not designed that way. Exception raising and catching
is a common flow-control method in Python. If you cannot stand that,
Python is not for you.
 
L

Lawrence D'Oliveiro

In message
Phlip said:
Pythonistas:

The "Samurai Principle" says to return victorious, or not at all. This
is why django.db wisely throws an exception, instead of simply
returning None, if it encounters a "record not found".

Does catching the exception not defeat the “Samurai Principle�
 
G

Gregory Ewing

Lawrence said:
Does catching the exception not defeat the “Samurai Principle�

Not if it lets you turn defeat into victory. Or
redefine victory so that it includes defeat.
Or something.
 
P

Phlip

Python is simply not designed that way. Exception raising and catching
is a common flow-control method in Python. If you cannot stand that,
Python is not for you.

While I'm at it, I'm going to log into comp.lang.java.misc and explain
to everyone why static typing is overkill, and implementation
inheritance is good for you.

Everyone gets defensive about the design flaws in their own language.
But the django.db situation is not even a design flaw; just a
misinterpretation of the Samurai Principle. int('yo') shall throw an
exception, but a missing record could be the result you were looking
for, so it's not exceptional.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top