Checking unwanted type change in Python

619 Views Asked by At

I come from static-type programming and I'm interested in understanding the rationale behind dynamic-type programming to check if dynamic-type languages can better fit my needs.

I've read about the theory behind duck programming. I've also read that unit testing (desirable and used in static-type programming) becomes a need in dynamic languages where compile-time checks are missing.

However, I'm still afraid to miss the big picture. In particular, how can you check for a mistake where a variable type is accidentally changed ?

Let's make a very simple example in Python:

#! /usr/bin/env python

userid = 3
defaultname = "foo"

username = raw_input("Enter your name: ")
if username == defaultname:
    # Bug: here we meant userid...
    username = 2

# Here username can be either an int or a string
# depending on the branch taken.
import re
match_string = re.compile("oo")
if (match_string.match(username)):
        print "Match!"

Pylint, pychecker and pyflakes do not warn about this issue.

What is the Pythonic way of dealing with this kind of errors ?

Should the code be wrapped with a try/catch ?

1

There are 1 best solutions below

0
On

This will not give you checks at compile time, but as you suggested using a try/catch, I will assume that runtime checks would also be helpful.

If you use classes, you could hook your own type checks in the __setattr__ method. For example:

import datetime

# ------------------------------------------------------------------------------
# TypedObject
# ------------------------------------------------------------------------------
class TypedObject(object):     
    attr_types = {'id'         : int,
                  'start_time' : datetime.time,
                  'duration'   : float}

    __slots__ = attr_types.keys()

    # --------------------------------------------------------------------------
    # __setattr__
    # --------------------------------------------------------------------------
    def __setattr__(self, name, value):
        if name not in self.__slots__:
            raise AttributeError(
                "'%s' object has no attribute '%s'" 
                % (self.__class__.__name__, name))
        if type(value) is not self.attr_types[name]:
                raise TypeError(
                    "'%s' object attribute '%s' must be of type '%s'" 
                    % (self.__class__.__name__, name, 
                       self.attr_types[name].__name__))
        # call __setattr__ on parent class
        super(MyTypedObject, self).__setattr__(name, value)

That would result in:

>>> my_typed_object            = TypedObject()

>>> my_typed_object.id         = "XYZ"      # ERROR
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 28, in __setattr__
TypeError: 'MyTypedObject' object attribute 'id' must be of type 'int'

>>> my_typed_object.id         = 123        # OK

You could go on and make the TypedObject above more generic, so that your classes could inherit from it.

Another (probably better) solution (pointed out here) could be to use Entought Traits