Is it a good practice to always manage errors with exceptions?

I'm creating a game with Python 3.4 and Pygame, and I'm using the object-oriented paradigm. In my classes, especially in __init__, I'm full of the type checkings, to prevent runtime errors. For example, this is my class Board (for now), and it 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 would like to know if doing the types checkings is a common practice in Python, and if, in my case, I am abusing them (type checkings).

Author: nbro, 2014-12-09

2 answers

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

Before better explaining why it is necessary to understand that this is "in general" - it is not a rule: of course there are cases when it is desirable and necessary 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 that is precisely the dynamic typing.

For example, in the above cases, you check if the parameter is of type "tuple", but with a static comparison, by type " tuple": type(resolution) != tuple - notice 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-type subclasses!

See:

>>> 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 checking, use Always isinstance and and never type(variavel) == tipo - otherwise you just break the object orientation paradigm.

Second thing : as I said, in Python it is better to avoid this kind of checking. If the function will work when receiving a list with length 3, Why throw a "type error", just why is it a tuple?

How do you prevent your program from making wrong calls? Hence comes the third thing: (maybe it should be the first) - Tests - to make sure your program doesn't do unexpected things write tests - both unit and integration. In this case, you'll take 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 break. Write tests for all functionality of the program that you complete - and can do this even before writing to such functionality.

And last but not least: you're scrambling with Python - a language that allows runtime modification of the behavior of functions and classes,and a host of other things - and getting parameter - by-parameter checking with isinstance (or type(parametro) ==) - you're actually swimming against the current.

You can easily 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 little - used annotations syntax that can serve to put directly next to each parameter what type is expected for it- see

Https://www.python.org/dev/peps/pep-3107 / for annotations syntax, 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 becomes a little more complex because of having to do an introspection in the decorara function to take the names of the parameters passed as positional. (Although in Python 3.4, stdlib's inspect module would make this easier).

The decorator above it can be used like this:

>>> 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 parameter checking doesn't improve your project at all, or how much code you have to write.

You don't want the errors to reach the end user at runtime, which is correct. But what is 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 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

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

(and in its function itself, whether the first parameter is a "real" Surface or anything that has the same functionality - for Pygame's internal functions is that the object needs to be a Surface)

 9
Author: jsbueno, 2015-06-05 13:30:26

My practice in any language that has exceptions is guided by a principle: when the method has no way of fulfilling what its name promises, it ends in exception.

But note that this does not include type checking. Especially in a dynamic language like Python, I prefer to give the object passed to me a chance of working in my method. If the object doesn't implement something I need, I automatically gain a free runtime exception.

Um example exception that my method itself would produce would be a ValueError when I need a number greater than zero and I get 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 some operation.

 4
Author: sergiopereira, 2014-12-10 02:54:16