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)