If it looks like a duck, quacks like a duck and walks like a duck, it’s a duck
Probably the greatest feature of python is its dynamic nature. By this we mean that variable names are not bound to types and they also can be assigned at run time, this concept is also know as duck typing and an example of it is the following:
#!/usr/bin/python class Person(object): pass def print_name(obj): ''' Expects obj to expose a property name''' print obj.name p = Person() # we add name dynamically p.name = 'John' # print name print_name(p) # we can change name from a string to anyother type p.name = 123 # print name continues to work print_name(p)
At this point, you should notice that the print_name function, silently is making the assumption that obj is supporting the attribute name. Of course this will not always be the case since we can pass any kind of a variable to the function causing an exception to be thrown.
It’s easier to ask forgiveness than permission
A very common solution for this kind of problem in python is the following:
def print_name(obj): try: print obj.name except TypeError: pass
This pattern is commonly known as It’s easier to ask forgiveness than permission. In other words we assume that the passed in object indeed supports name and we try to use it, if it turns out that it not supported an TypeError exception will be raised and caught allowing our program to continue to function.
Look before you leap
Another way to achieve similar behaviour, is to apply a technique known as Look before you leap meaning that, we verify that an object is supporting a specific operation before we try to use it. The following code is an example of this technique:
def print_name(obj): if isinstance(obj, Person): print obj.name
Another way to achieve a similar effect is the following:
def print_name(obj): if hasattr(obj, "name"): print obj.name
Possible Problematic Behaviour
Although the solutions we have seen so far, can allow a program to continue its execution without breaking in case of a wrong argument, we still can see some problematic behaviour that might cause side effects, possibly leading to bogus behaviour.
For example, checking for the type of the passed object will ignore any other object than Person defining name which might not be our intention. Also using the hasattr again might lead to cases like the following:
class Person(object): name = 'To be defined' def print_name(obj): if hasattr(obj, "name"): print obj.name print_name(Person)
The output of this snippet will be:
To be defined
while what would have seen more reasonable in this case would have been something like this:
def print_name(obj): if hasattr(obj, "__name__"): print obj. __name__ # do your stuff ...
With the following output:
Another example of undesired behaviour can be demonstrated in the following example:
class Person: def __init__(self, name): self._name = name def get_name(self): return self._name; def print_name(obj): if isinstance(obj, Person): print obj.get_name() person = Person('john') # print_name will work as expected print_name(person) # we delete name from person del person._name # now we will have an unhandled exception! print_name(person)
More than the problems I discussed so far as far as duck typing is going, there are also issues with proper documentation. Given the flexibility of dynamic typing, chances are that users of specific module will have to delve in directly to the source code trying to understand its details and how it is working. A developer who just started using a system, is very possible to misuse it and even start use attributes consisting implementation details breaking encapsulation, making the maintenance of the code difficult and error prone.
The problem became obvious since python’s early stages
The discussion was very long and controversial since it was about a change that seemed not pythonic at all!
A few years latter Guido van van Rossum came with his notorious blog posting:Interfaces or Abstract Base Classes? which led to PEP 3119 introducing the concept of Abstract Base Classes as we use it today.
In this posting we will not deal with historical aspects of ABCs (Abstract Base Classes) nor with their non pythonic nature but we will focus in understanding their implementation mechanics and details, so keep on reading to the next page!