views:

134

answers:

4

I find this design pattern comes up a lot:

try: year = int(request.GET['year'])
except: year = 0

The try block can either fail because the key doesn't exist, or because it's not an int, but I don't really care. I just need a sane value in the end.

Shouldn't there be a nicer way to do this? Or at least a way to do it on one line? Something like:

year = int(request.GET['year']) except 0

Or do you guys use this pattern too?


Before you answer, I already know about request.GET.get('year',0) but you can still get a value error. Wrapping this in a try/catch block to catch the value error just means the default value appears twice in my code. Even worse IMO.

+10  A: 

You're probably better off to use get()

year = int(request.GET.get("year", 0))

This will set year to what ever request.GET['year'] is, or if the key doesn't exist, it will return 0. This gets rid of your KeyError, but you could still have a ValueError from request.GET['year'], if it is not convert'able to an int.

Regarding your question (the try/except), a common idiom in Python is EAFP.

EDIT:

If you're really concerned, why not write your own method to do this:

def myGet(obj, key, type, defaultval):
    try:
        return type(obj.get(key, defaultval))
    except ValueError:
        return defaultval



# In your code
year = myGet(request.GET, 'year', int, 0)
Nick Presta
Yeah...that's exactly the problem. It might not be convertible.
Mark
That's true, but that's the nature of Python. I don't agree with catching all possible exceptions (even when you know there are only 2 realistically possible exceptions) and explicitly catching both exceptions is pretty standard, as far as I'm concerned. What's wrong with the try/except block? It is very Pythonic.
Nick Presta
I just don't like having a big long list of try/catch blocks at the top of every function to account for every possible error, when all I want is a int! `year` appears in there twice, with what you're suggesting, the default value appears in there twice too. Makes refactoring even more difficult. If you use `year` in once place, but `yaer` in another place, you might not even notice because one of those branches could be pretty rare... and then all of a sudden your app explodes because `year` is actually undefined (or `None`...whatever python does)
Mark
That is true of almost all languages. The scenario you describe is not solved by using if/else blocks instead of try catch.If you're really concerned, why not write your own method that takes a value, and returns some default you specify?
Nick Presta
Create a wrapper function then that has the try-except in it, and call the wrapper to grab the value?
Amber
Yeahh... I guess the wrapper sol'n works.
Mark
+2  A: 

No way to do it in one line (that I can think of), but I'd do it like this, using get():

try:
    year = int(request.GET.get("year", 0))
except ValueError:
    year = 0

Also, it's generally better to catch a specific exception, not all exceptions.

mipadi
Works, but now you have `0` in there twice, and it's even more verbose. I know you're supposed to catch specific exceptions... but for something simple like this?
Mark
True. In simple cases, a bare `except` isn't bad, but in complicated cases (and who knows when you'll add more lines of code) it can end up masking unanticipated problems.
mipadi
+4  A: 

I'd use a helper function:

def get_int(request, name, default=0):
    try:
        val = int(request.GET[name])
    except (ValueError, KeyError):
        val = default
    return val

then:

year = get_int(request, 'year')

It keeps the complexity of the try/catch in one place, and makes for tidy functions, where you have one line per parameter in your view functions.

Ned Batchelder
I guess this is the obvious sol'n eh? You wrapped it up nicely though. +1 :)
Mark
A lot of people had this answer... please don't kill me for choosing this one (\*coualexgh\*). You other guys have more votes anyway ;)
Mark
+6  A: 

Shouldn't there be a nicer way to do this?

There is -- it's known as "a function"...:

def safeget(adict, key, type, default):
    try: return type(adict.get(key, default))
    except (ValueError, TypeError): return default

year = safeget(request.GET, 'year', int, 0)

FWIW, I don't think I've ever used this "pattern" -- the various error cases you're ignoring seem like they should be handled separately for UI reasons (a missing optional field defaulting is fine, but if somebody's mistakenly typed, say, 201o (the 0 and o keys being closed and, in some fonts, their results appearing similar), it generally doesn't seem nice to silently turn their input into 0. So, I don't think it's so frequent, nor highly advisable, to warrant anything like a special syntax form in the language, or even a built-in function.

But the nice thing about helper functions like safeget is that you and I can peacefully agree to disagree on the design issues involved (maybe we're just used to doing different kinds of software, for example!-) while letting each of us easily have exactly the helper functions each desires in their personal "utilities" modules!-)

Alex Martelli
In this particular case, it'll be used in an ajax call. It should never receive the wrong value type unless someone's been tampering with it. Which I guess is all the more reason to let it raise an error, so that *I* know when *I* screwed up... but oh well.
Mark
@Mark Or more seriously, if you detect someone tampering with your website you may wish to log it and do something about it.
fmark
@fmark: They're not going to get any useful information out of tampering with it... but, they might try some more serious attacks after that, so who knows. Maybe I should block them early before they do some serious damage.
Mark