Is it good practice to always manage errors with exceptions?

8

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 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 would like to know if doing types checkings is a common practice in Python, and if, in my case, I am abusing them (type checkings).

    
asked by anonymous 09.12.2014 / 01:22

2 answers

8

As for "securing types" in Python like you're doing in code: In Python this is not considered best practice.

Before explaining 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 necessary to test the type of data that comes.

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

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

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 never type(variavel) == tipo - otherwise you just break the Object Orientation.

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

How do you prevent your program from making wrong calls? Hence the third thing : (maybe it should be the first) - testing - to make sure your program does not do unexpected things write tests - both unitary and integration. In this case, you will get the type 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 some of these breaks. Write tests for all the functionality of the program you complete - and you can do this even before you write such functionality.

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

You can easily write a decorator for the above cases, so you can describe the expected types in a single line above each function / method. Since you are using Python3, there is even an annotated annotation (annotations), which can be used to put directly next to each parameter what type is expected for it - see

link for the syntax of annotations, and link for a recipe on how to use annotations for type syntax .

Without using 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 is a little more complex due to having to do an introspection in the function would have to take the names of the parameters passed as positional. (Although in Python 3.4, the inspect module of stdlib would facilitate this).

The above decorator 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 check does not improve on anything, your project, or how much code you have to write.

You do not want the 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 pygame.draw.rect without a Surface in the first parameter

>>> 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 same "TypeError" - with or without a static check done on your part of the code. And if you do not want the error to reach the end user, you have to have an "except" by capturing the TypeError of the same form.

(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 must be a Surface)

    
09.12.2014 / 12:43
4

My practice in any language that has exceptions is guided by a principle: when the method can not 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'd rather give a chance to the object that was passed to me in my method. If the object does not implement something I need, I automatically gain a free exception from the runtime.

An example exception that my method would even produce would be 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 is not entitled to perform any operation.

    
10.12.2014 / 03:54