19.5. Flexibility

19.5.1. Summary of mechanisms for flexibility in Python

Earlier, in Section 19.2.5, we have seen that one of the powerful properties a software piece can have is to be reusable. We also concluded that the more flexible it is, the more general and thus reusable it will be. Let us now summarize some mechanisms in object-oriented programming that help in achieving more flexibility.

  • Genericity: genericity (as available in the ADA programming language), is a technique to define parameters for a module, exactly as you define parameters for a function, thus making the module more general. For instance, you can define the type of an element of a Table module, or the function to move to the next item, as being generic. There is no specific syntax in Python to define generic components, but in a way, it is not really necessary because Python is dynamically typed, and can handle functions as parameters for other functions.
  • Inheritance: as we have just seen in Section 19.4 this describes the ability, in object-oriented programming, to derive a class from another, either to extend it or to specialize it.
  • Overloading, which refers to the possibility for an operator or a method to behave differently according to the actual data types of their operands or arguments. This feature does not directly address the problem of flexibility: it is rather an elegant syntactic mean not to have different operators or names to perform similar tasks on different objects or sets of objects. In this sense, it is actually a form of polymorphism. In object-oriented programming, overloading moreover exactly means being able to redefine a method in a derived class, thus enabling polymorphism of instances: given an instance, it is possible that you do not know exactly to which class in a hierarchy it belongs (e.g DNA or Protein). In other programming languages, such as Java, there is another kind of overloading: it is possible, within the same class, to have several definitions of the same method, but with different signatures, i.e a different set of parameters. This does not exist in Python, and as described below (see Section 19.5.2), you have to handle it manually.
  • Polymorphism: refers to the possibility for something to be of several forms, e.g, for an object to be of any type. In Python, for instance, lists are polymorphic, since you can put items of any type in a list. Overloading is one aspect of polymorphism, polymorphism of methods. Polymorphism brings a lot of flexibility, just only because you do not have to define as many classes as potential data type for it, as you would have to do in statically typed languages. In this sense, Python helps in designing more simple classes frameworks.

Some of these techniques, mainly inheritance, are available in Python, other, such as genericity and overloading, are not, or in a restricted way.

19.5.2. Manual overloading

If you want a method to behave differently according to the parameters, you must either create subclasses or write the code to analyze the parameters yourself. In Example 19.5, there is an example a method dealing with different types of parameters, that are manually handled.

Example 19.5. Curve class: manual overloading

    def __getitem__(self, key):
        """
        Extended access to the curve.
        c[3]
        c['toto']
        c['x<100']
        c['x==y']
        """
        _x = []
        _y = []
        _result = None
        if type(key) is ListType:                                         (1)
            _x = key
            _x.sort()
            _y = self.y(_x)
            _result = Curve(zip(_x, _y))
        elif type(key) is SliceType:                                      (2)
            _x = self._range_x(key.start, key.stop)
            _y = apply(self.y, _x)
            if _x and _y:
                _result = Curve(zip(_x, _y))
        elif self._curve.has_key(key):                                    (3)
            _result = self._curve[key]
        elif type(key) is StringType:                                     (4)
            _x = self._tag_x(key)
            if len(_x) > 0:                                               (5)
                _x.sort()
                _y = self.y(_x)
                _result = Curve(zip(_x, _y))
            else:                                                         (6)
                _result = self._getexpr(key)
        if _result is not None:
            return _result
        
	    
1

Recognizes the argument as a list.

2

Recognizes the argument as a slice.

3

Recognizes the argument as an index.

4

Recognizes the argument as a string, that is going to be further analyzed as a tag name or an expression.

5

Recognizes the argument as a tag name.

6

Recognizes the argument as an expression, such as 'x< y' or 'y>10'.

Python is not able to do this analysis automatically, because it is dynamically typed. You do not have any mean to specify the type of the parameter. In a language providing static typing and full method overloading, i.e also within the same class, you could have several __getitem__ definitions that would look like:

    def __getitem__(self, key: List):
         ...
    def __getitem__(self, key: Slice):
         ...
    def __getitem__(self, key: String):
         ...
	    
As you can notice, this is of course not valid Python code, since there is no possibility to define the type of a parameter in Python.