speg
speg

Reputation: 2019

How to safeguard with dynamic typing

Our project is getting kind of large, and the other day I ran into this problem, when I tried to add a simple error message to a function:

def create_report(id):
    report = new_report(id)
    if not report:
        raise api_error('Could not find report with id %d' % (id,))
    ...

The problem? id was a string and it crashed when it tried to format it as a number. I was not the original author of the function and wrongly assumed that id would be a number. Instead it is supposed to be a string. Oops.

If this was a strongly typed language I would get an error from the compiler right away; what is the best way to go about these sorts of things? Should I be checking the type of every parameter (seems like a lot of legwork), or should I be putting everything in try: blocks? Maybe we should write a comment in every function describing it's parameters? Or was I simply supposed to have known better?

Upvotes: 1

Views: 189

Answers (6)

zenpoy
zenpoy

Reputation: 20126

I think the problem here is that you assert something about the value in the part of code which should get executed when something unexpected has happened. Maybe a better approach would be to change new_report(id) to raise an exception if something goes wrong - specifying if it's a value error or the id couldn't be found. Then your code should be:

def create_report(id):
    report = new_report(id)

...

def new_report(id):
    try:
       # find the report by id
       # if couldn't find raise api_error
    except ValueError:
       # explain that id is the wrong type

Upvotes: 0

Ned Batchelder
Ned Batchelder

Reputation: 375574

Even better than the other answers' %s, I find using %r is best if the message is intended for developers' eyes. This will help you distinguish between subtle cases. For example, if you call this function with '12 ' for an id, a %s message won't show the trailing space. %r uses the repr() of the value, and so will include the quotes, helping you to see the precise value.

Upvotes: 0

Himanshu Jindal
Himanshu Jindal

Reputation: 637

Try using %s and then using str(variable). This will make sure that everything gets converted to string (even list and tuples) and no TypeError takes place

def create_report(id):
    report = new_report(id)
    if not report:
        raise api_error('Could not find report with id %s' % (str(id),))
    ...

Upvotes: 0

Sigma
Sigma

Reputation: 993

A possible strategy to make such things more robust regarding parameter types would be to write:

def create_report(id):
    report = new_report(id)
    if not report:
        raise api_error('Could not find report with id %s' % (id,))
    ...

It won't always look nice, but at least it won't easily break.

But in general, it's always better to have some explicit contract for the function. The docstring is the right place for that.

Upvotes: 0

irrenhaus3
irrenhaus3

Reputation: 309

If you really want to make sure a string printing function doesn't crash, you're probably best off using %s, which converts the integrated value to a string using pythons str()-function. And seeing how it's an ID you're talking about, I'll assume it's of type int - in which case this solution should work out fine (If you're using floats, I'm not too sure how many digits str() will keep).

Edit Aw boy, I'm slow...

Upvotes: 0

kindall
kindall

Reputation: 184151

That format string should have used %s, not because id is a string but because it's the best choice generally. %d requires a numeric type, but %s will convert other types to strings if necessary. You should use %d only when you need to change the numeric formatting.

Upvotes: 2

Related Questions