Handling Types in Python – Ignore it, Convert it, or Check it
January 27, 2016
Next month I’ll be speaking about Python types at PyCaribbean in Santa Domingo Dominican Republic. My title will be “Python: As much typing as you want,” obviously this means that I’ve been doing some thinking quite a bit about Python typing of late.
Sometimes we think of Python and other scripting languages as devoid of types. Of course this is not true, almost every language since Assembly has included types to a greater or lesser extent. Even features like Perl’s coercion are just built on top of and intended to convert types—not based on ignoring types.
In the case of Python all of our variables are actually a single type, reference. They are all references to objects and those objects are typed. You can assign any reference to point to any object, but those objects have a class, which is their type and controls their behavior. You can see Python’s types at work if you do something like 2 + ‘2’ which of course equals TypeError. We can check the types with type(3j) or isinstance(42,(int,float)). But how do we deal with these types in Python? Python has 3 common strategies.
One very common strategy in Python is to leverage the fact that all of our variables are references, your code may not care what it was fed. If your code doesn’t do anything where the type matters maybe it doesn’t care. For example:
In all cases here it doesn’t matter what the type is: it will be printable, it will be comparable, it will be possible to turn it into a pickle.
Our next strategy is to convert to the type we want, just to be sure that the type works as we need it to. This also has a few variations:
self.x = int(self.x) self.array = list(seq_or_list) self.value = value*1.0 return self.x * self.array
These techniques are often working on the convention that python constructors return a new object of the same type, making them safe to run on an object that’s already the right type.
A less favored strategy in Python is to check the type of data explicitly. We can of course do this, but this is by far not the norm. As soon as we start doing type checks—no matter how obvious the need—we break the Python norm of duck typing. Nonetheless there are a few cases where we do exactly this.
assert isinstance(self.x, str) if not isinstance(self.left, self.right.__class__): raise TypeError('Left and right must be matching types') if not all(isinstance(x, (str, int, float, bool, None)) for x in vals): raise TypeError('Unable to convert to json') return 'OK'
Python is even doing this itself. Consider what happens in Python 3 when we compare two different types.
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
TypeError: unorderable types: int() > str()
There is another category of techniques, more common to developers in other languages. We can convert or check the types when they are set as a field on an object. In other languages this would be getters and setters, but I’ll explore the Python equivalent in another post.