Question

I've just now studied about covariance and contravariance in static languages (more specifically C#). This concept is rather clear to me, however I'm in doubt on how this applies to dynamic languages like Python.

Since Python is duck typed (or structural typed) it seems to me that there is not even a notion of variance and covariance in this language?

If I'm not mistaking, things like contravariance and covariance are checked at compile-time in a language like C#, this is possible because a variable has a type, and the value it is bound to has a type as well and these have to match or be co(ntra)- or invariant. However, since there is no notion of typing at compile-time with a language like Python I don't know how to apply this concept.

Any thoughts?

My thoughts: The concept of covariance means that whenever we expect a type of X, any subclass of X will do as well. So, in Python this doesn't come up, because there is no type check to do this. The only check that happens is wether or not the object has the necessary implementations by means of methods and attributes.

In C# for example where we have a method:

void DoStuff(Person p)
{
    p.Dance();
};

We could very well call this method with an instance of a Student (if this is a subclass of Person).

Now, in Python, we could very well pass in a Bird object that would not be related to Person in any way in the inheritance hierarchy(except it would also inherit from object), as long as this bird implements Dance().

This is why I'm asking about this concept. From what I understand Python is covariant and contravariant in.. well, everything?

Was it helpful?

Solution

Actually, that's not quite (my understanding of) variance. Variance is about the behaviour of type constructors, such as generics in C#. Using a Student in place of a Person is ordinary subtyping. Using a List<Student> in place of a Lis<Person> is the subject or covariance and contravariance.

As for the actual question: Well, Python doesn't have a static type system, and as a consequence does not have a concept like type constructors. The language is not concerned with these things at all. It just goes ahead and allows whatever values you pass and if it works, great.

However, the concept of variance is very useful in any language with something to the effect of subtyping and non-atomic values. For example, although there is no List<Person>, you might (without telling the language, because the language doesn't even begin to think about these things) establish some invariants:

  • some list students must contain only Student objects (or objects implicitly conforming to the interface of Person, in the interest of duck typing).
  • some function print_persons must be given a list of Person objects and only reads it
  • some function add_person must be given a list of Person objects and adds to it

Using the same reasoning as in a static language, you can see that print_persons(students) is correct while add_person(students) might be incorrect.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top