Is it good practice to always manage mistakes with exceptions?

Asked

Viewed 286 times

9

I’m creating a game with Python 3.4 and Pygame, and I’m using the paradigm object-oriented. In my classes, especially in __init__, am full the type checkings, to prevent Runtime errors. For example, this is my class Board (for now), and is full of exceptions handlings:

class Board:
    def __init__(self, resolution, color, image_path):
        if type(resolution) != tuple or len(resolution) != 2:
            raise TypeError('incorrect format for resolution')
        if type(color) != tuple or len(color) < 3 or len(color) > 4:
            raise TypeError('incorrect format for color')
        if type(image_path) != str
            raise TypeError('image_path is not a str')

        self.resolution = resolution
        self.color = color # background color
        self.image_path = image_path        
        self.surface = pygame.display.set_mode(self.resolution)
        self.surface.fill(self.color)
    #

    def write(self, msg, color, pos, size=20, bold=False, italic=False):
        '''This function writes some text in a surface passed as parameter.'''
        if type(surface) != pygame.Surface:
            raise TypeError('surface is not pygame.Surface')
        if type(msg) != str:
            raise TypeError('msg is not a str')
        if type(color) != tuple or len(color) < 3 or len(color) > 4:
            raise TypeError('incorrect format for color')
        if type(pos) != tuple or len(pos) != 2:
            raise TypeError('incorrect format for pos')
        if type(size) != int or size < 0:
            raise TypeError('incorrect format for size')
        if type(bold) != bool:
            raise TypeError('bold is not a boolean')
        if type(italic) != bool:
            raise TypeError('italic is not a boolean')

        font = pygame.font.SysFont(None, size, bold, italic)
        text = font.render(msg, True, color)
        self.surface.blit(text, text.get_rect())

I wonder if you do the types checkings is a common practice in Python, and if, in my case, I am abusing them (type checkings).

  • 2

    Exceptions help but can be a plague. I’ve written a lot about it but nothing about Python. I don’t understand the specific workings of this language but I imagine there’s abuse in the use of exceptions in it as well. Have a look at http://answall.com/a/30168/101. There are links to other answers that discuss the subject. You can also search on website. My answers tend more towards C# and eventually Java but much of what is in these answers can be taken advantage of. The important thing is that you understand that there are alternatives and abuse is always bad.

  • 1

    It’s just you haven’t read everything I’ve been linking. It will take more than an hour for you to read everything but you can learn a lot of useful things. Of course you can skip certain parts (although if you want to dig deeper it’s better not to). I talk about issuing exceptions in some answers. I especially talk about the wrong use of exceptions when the problem is not an exceptional case. In this case they seem to be exceptional cases. You might also be interested in: http://answall.com/a/42603/101 )I don’t know what the Python culture is) but I think programmers also prefer Unit Tests.

  • In Python it has to be like this, as there is no overload you will always have to test the types of parameters, it is normal.

  • 1

    "It’s easier to Ask forgiveness than it is to get permission - Grace Hopper", in Python it is common to use exceptions, no problems. Regarding the votes to close, I don’t see this question as "mostly based on opinions", this isn’t just about "programming style".

  • 1

    In fact, the question is well posed, and it is a frequent doubt in Python: definitivametne there are no reasons to close it unless it is duplicated.

2 answers

8


As for "securing types" in Python as you are doing in the code: In Python this is not considered the best practice.

Before explaining better why it is necessary to understand that this is "in general" - it is not a rule: of course there are cases where it is desirable and need to test the type of data that comes.

But as a rule, this is something that ties your program to a practice of "static typing," and you throw away one of the greatest forces of language which is precisely dynamic typing.

For example, in the above cases, you check if the parameter is of the "tuple" type, but with a static comparison, by the "tuple" type": type(resolution) != tuple - Note that this works if the object is a "tuple" (tuple) - but it will fail for any other type of object. Not only will other sequences, such as lists, arrays, or custom objects you create, but it will fail even for tuple-like sub-classes!

Behold:

>>> from collections import namedtuple
>>>
>>> a = namedtuple("r g b")(255,0,0)
>>> a = namedtuple("color", "r g b")(255,0,0)
>>> a
color(r=255, g=0, b=0)
>>> type(a) == tuple
False
>>> isinstance(a, tuple)
True

So, first thing: if you are going to do static type check, use at all times isinstance and never type(variavel) == tipo - otherwise you just break the Object Orientation paradigm.

Second thing: Like I said, in Python it’s best to avoid that kind of checking. If the function will work when receiving a list with length 3, why play a "type error", just because it is a tuple?

How do you prevent your program from making wrong calls? Hence the third thing: (maybe I should be the first) - testing - to make sure your program doesn’t do unexpected things write tests - both unit and integration. In this case, you’ll get the kind of error you have with integration tests: write test functions that call the functions that use these classes (functions that create these objects would be unit tests) - and see if any of these breaks. Write tests for every feature of the program you complete - and you can do so even before you write such a feature.

And last but not least: you’re working with Python - a language that allows you to run-time modification of the behavior of functions and classes,and a number of other things - and keep checking parameter by parameter with isinstance (or type(parametro) ==) - you’re really swimming against the current.

It is easily possible to write a decorator for the above cases so that you can describe the expected types in a single line above each function/method. Since you are using Python3, there is even a syntax of annotations (Annotations), little used that can be used to place directly next to each parameter what type is expected for it - see

https://www.python.org/dev/peps/pep-3107/ for the syntax of Annotations, and http://code.activestate.com/recipes/578528-type-checking-using-python-3x-annotations/ for a recipe for how to use Annotations for type syntax.

Without using the recipe, or Annotations, you can also write a decorator for type checking - see:

from functools import wraps
from collections import OrderedDict

class CheckTypes:
    def __init__(self, *args):
        self.parameters = OrderedDict(args)

    def __call__(self, func):
        @wraps(func)
        def checker(*args, **kw):
            for parameter_name, passed_arg in zip(self.parameters, args):
                self._check_parameter(parameter_name, passed_arg)
            for parameter_name, passed_arg in kw.items():
                self._check_parameter(parameter_name, passed_arg)
            return func(*args, **kw)
        return checker

    def _check_parameter(self, parameter_name, passed_arg):
        if self.parameters[parameter_name] is None:
            return
        if not isinstance(passed_arg, self.parameters[parameter_name]):
            raise TypeError("Parâmetro {} não é do tipo {}".format(
                parameter_name, self.parameters[parameter_name]))

The decorator with the use of Annotations gets a little more complex because of having to do an introspection in the memorize function to catch the names of the parameters passed as positional. (Although in Python 3.4, the module inspect of stdlib would facilitate this).

The decorator above can be used so:

>>> class Bla:
...     @CheckTypes(("self", None), ("resolution", tuple), ("color", tuple), ("image_path", str))
...     def __init__(self, resolution, color, image_path):
...         pass
... 

>>> 
>>> Bla((),(),"")
<__main__.Bla object at 0x7f35abecf050>
>>> Bla(1,(),"")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 9, in checker
  File "<stdin>", line 20, in _check_parameter
TypeError: Parâmetro resolution não é do tipo <class 'tuple'>

And before I forget -- fourth thing: see that your checking of parameters does not improve at all nor your project, nor how much code you have to write.

You do not want errors to reach the end user at runtime, which is correct. But what’s the difference between

def minha_funcao(param1):
    if not isinstance(param1, pygame.Surface):
        raise TypeError("param1 não é do tipo Surface")

and

def minha_funcao(param1):
    pygame.draw.rect(param1, ...)

Note that when we call the pygame.draw.rect without a Surface in the first parameter occurs

>>> pygame.draw.rect("", (255,0,0), (0,0,0,0))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: must be pygame.Surface, not str

That is: the very same "Typeerror" - with or without a static check done on your part of code. And if you don’t want the error to reach the end user, you have to have an "except" capturing Typeerror in the same way.

(And in its very function, whether the first parameter is a "real" surface or anything that has the same functionality - for the internal functions of Pygame is that the object needs to be a Surface)

  • 1

    Although the answer is interesting to me it seems that you do not extend well the difference between static checking and type dynamics. In Python it is only possible to do dynamic checking. It is possible to use external tools, it is possible to do unit tests to check if the types are correct in the situations determined by the tests (and only in these situations)but this check that can be considered static is not part of the language and it does not guarantee anything after that (you even talk about it). Any checkup at runtime will be dynamic. There are still a few more questionable conceptions.

  • 1

    Ok @bigown - I’ve been using pthon for so long, that the "static" of the other linguagensnem enmtramais in my head. But in fact, where you read "static" in the answer, the correct would be "explicit" and "hardcoded".

  • Ideally, you want to use assert isinstance(obj, type), 'error' and not if not isinstance(…): …: so if you run Python with -O, you can omit these type checks (with the if, you are always paying the cost of these checks).

3

My practice in any language that has exceptions is guided by a principle: when the method has no way to fulfill what its name promises, end in exception.

But note that this does not include type checking. Especially in a dynamic language like Python, I prefer to give an opportunity to the object that I was given to work on in my method. If the object doesn’t implement something I need, I automatically get an exception free of charge of the Runtime.

An example of an exception that my own method would produce would be a ValueError when I need a number greater than zero and receive a negative. Another example would be to return an exception created by me, such as PermissionError if I determine that the user has no right to perform any operation.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.