... and to unit test, btw.
(This isn't about Python specifically. This is about dynamically typed languages in general that are only concerned with whether a given class supports the right interface. I've just been using Python recently and that's the only dynamically typed language I'm presently exposed to)
Consider for a moment the following Python class which attempts to create a reasonable set of rules to validate an instance property:
Code:
class Player:
@property
def name(self):
return self._name
def __init__(self, login, name):
self.login = login
self.name = name
@name.setter
def name(self, v):
if v is None:
self._name = None
return
try:
v = v.strip()
except:
raise TypeError('Must be a string or None.')
self._name = v if v else None
What @name.setter essentially does is to check if name is a whitespace or empty string and cast it to a None object if that is the case. Seems an awful lot of code to write for such a simple rule.
I could write the following simpler version (and that's what I have in my own code when doing this kind of stuff):
Code:
@name.setter
def name(self, v):
if not isinstance(v, string):
raise TypeError('Must be a string or None.')
v = v.strip()
self._name = v if v else None
But notice how I am now breaking the contract you sign when adopting any dynamically typed language;
"if you are reasoning about your object type, your design has a weakness. Your code should only be concerned if an instance supports the right interface."
Those are pretty words. But the fact is that I can have any object that supports the strip() method that returns something completely different. An interface surely isn't just a name, but the semantics that go into that name.
This means that without checking for a string instance, not even wrapping v = v.strip() inside a try block, will avoid potential unhandled exceptions in the code that makes use of the Player class.
The previous, more verbose, version of the setter method doesn't perform type checking so I must be doing it right. Or am I?
The fact is that I'm a programmer and I think like a programmer. Even with dynamically typed languages we think in terms of object types. So I will be coding the rest of my program in the knowledge that the Player.name property is a string and I'll be performing all sorts of string related things with my the Player.name property.
The problem with that is that you may have already noticed that my longer version setter has a bug. It allows for any object type that implements a method named strip() to be assigned to Player.name. Remember, an interface is not just a name, but the semantics that go with that name. Problem is dynamic programming languages are only concerned with the name part. But it is very likely that hypothetical object strip() method doesn't even return a string. So I really need to cover this up in my setter. And the only way to do it is to type-check.
What makes this bug more devilish is that if my class is part of an API, users of my class will correctly make very few assumptions about my API implementation details (including property internal types) and will get a cryptic unhandled exception when the bug surfaces in their code. We all have seen those.
And to put the cherry on top of that burnt cake, I'm also obliged to include a type-test in my unit test suit for that class.
--------------------------------
So, let's finalize with a bit of advice:
Do check your types in your dynamic typed languages. It really is ok. Don't buy entirely into the idea that your programming language will keep you safe from type declarations. No programming language does. And dynamic typed languages are no different.
But do make an effort to avoid checking your types. The really ok place to type-check is within your own objects implementation details, where you may need to perform validation to avoid raising unnecessary exceptions to the caller or to implement proper procedures for when the requirements of the object interface aren't met.