TL;DR:
Why is the Callable generic type contravariant in the arguments as stated by the PEP 483 and how is my analysis of that question (in)accurate? (Said analysis at the bottom of the post)
Context:
I'm currently studying topics about use of annotations for type analysis (MyPy, Pyright...). And I was reading the PEP 483 – The Theory of Type Hints that is related.
This PEP states the following in the part dedicated to _Covariance and contravariange:
One of the best examples to illustrate (somewhat counterintuitive) contravariant behavior is the callable type. It is covariant in the return type, but contravariant in the arguments.
This is what I'm trying to understand : why are callables contravariant in the arguments?
More from the PEP 483:
The PEP 483 provides the following notions and definitions:
Subtype relationship:
Type subtype is a subtype of type basetype if:
- every value from
subtypeis also in the set of values ofbasetype; and - every function from
basetypeis also in the set of functions ofsubtype.
Contravariance:
If subtype is a subtype of basetype, then a generic type constructor GenType is called "contravariant" if GenType[basetype] is a subtype of GenType[subtype] for all such subtype and basetype.
My analysis attempt:
The question "Why is Callable contravariant in the arguments?" could be rephrased as:
With type subtype being a subtype of type basetype, why is Callable[[basetype], None] a subtype of Callable[[subtype], None]?
Which would imply, according to the above content from PEP 483, that:
- Every value for
Callable[[basetype], None]would be included in the set of values ofCallable[[subtype], None]. - Every function of
Callable[[subtype], None]would also be in the set of functions ofCallable[[basetype], None].
Answering my main question would be answering why these two conditions are verified.
1. Is every value for Callable[[basetype], None] included in the set of values of Callable[[subtype], None]?
subtype being a subtype of basetype, every value of type subtype is therefore a value of type basetype.
Therefore, any callable taking an argument of type basetype will also accept an argument of type subtype.
Which means that any value (callable value) of type Callable[[basetype], None] is also a value of type Callable[[subtype], None].
Therefore, yes: every value of Callable[[basetype], None] is also a value of the set of values of Callable[[subtype], None].
NB: The contrary (covariance one could naively expect as I did first) can be not possible.
let's define the following from the PEP 483's own examples:
class Employee: ... class Manager(Employee): def manage(self): ... def my_callable(m: Manager): m.manage()Here, despite
my_callablebeing of typeCallable[[Manager], None], providing anEmployeeto it would go against type safety and it's made obvious by the definition of theManager.managemethod. This means that despite being of typeCallable[[Manager], None],my_callableisn't of typeCallable[[Employee], None], which is enough to refute a systematic covariance.
2. Is every function of Callable[[subtype], None] included in the set of functions of Callable[[basetype], None]?
I'm less sure about this question and how to answer it.
Let's define the following function taking a callable as a parameter:
def my_func(c: Callable[[subtype], None]): ... Would any function with such a signature (Callable[[Callable[[subtype], None]], None]) be a function of type Callable[[Callable[[basetype], None]], None]?
Would any function accepting a Callable[[subtype], None] also accept a Callback[[basetype], None]? (I suppose of all that boils down to that question).
I suppose that any such function would pass to it's callable parameter a subtype instance, and that therefore a Callable[[basetype], None] would accept such subtype instance which would make passing a Callable[[basetype], None] OK.
But is it really enough to assert that every function such as my_func would also belong to the set of functions of type Callable[[Callable[basetype], None], None]?