As @Eric states, there is a real type violation in this design. However,
the suggested trick is not a good idea: claiming that `A.meth1` would
accept any `Base` object is a lie, unless `meth1` would only use `Base`
members, in which case it could as well have been defined in the `Base`
class itself.
The Liskov Substitution Principle is at play here: the type `Base.Callback`
could be defined as `Callable[['Base'], bool]`. When you would fake that
`A.meth1` would conform to that signature, it would mean that you're
allowed to call `A.meth1(an_instance_of_B)`, since `an_instance_of_B` is
also an instance of `Base`.
What you _really_ seem to mean is to create a collection of
`Subclass.Callback` functions. But since the subclass is not known when
you're specifying the Base, youl cannot know its type.
To do this, you would need an extra type parameter that would 'curiously
recur' in the Subclass declaration, but I'm not sure if this would work.
```
from typing import Callable, Iterable, Generic, TypeVar
TSub = TypeVar('TSub')
class BaseT(Generic[TSub]):
callbacks: Iterable[Callable[[TSub], bool]] = tuple()
def fmap(self: TSub):
return tuple(cb(self) for cb in self.callbacks)
class A(BaseT['A']):
def meth1(self: 'A'):
return True
def meth2(self: 'A'):
return False
A.callbacks = (A.meth1, A.meth2)
a = A()
print(a.fmap()) # (True, False)
```
BUT
template.py:10: error: "TSub" has no attribute "callbacks"
template.py:22: error: Access to generic instance variables via class is
ambiguous
I'm not sure if Python's Generics are up to speed yet.
On Tue, Mar 2, 2021 at 9:38 PM Eric Traut
The reason this doesn't work is that the instance methods `A.meth1` and `B.meth2` expect `self` to be an instance of their own class (or a subtype thereof). For example, the `self` in `A` is implicitly typed as `A`. The full type of this method is `Callable[[A], bool]`. You are then trying to assign this to a `Callable[[Base], bool]`. Parameter types within a callable are contravariant, so this is a type violation. This makes logical sense if you consider that the callback could be invoked with an instance of `Base` (but not `A`) as the first argument. This would violate the assumption made by `A.meth1` is expecting the caller to pass an instance of `A`.
If you explicitly annotate the `self` parameter in `A.meth1` and `B.meth2` to accept an instance of `Base`, you can eliminate the type violation.
```python def meth1(self: Base) -> bool: ... ```
-- Eric Traut Contributor to Pyright & Pylance Microsoft Corp. _______________________________________________ Typing-sig mailing list -- typing-sig@python.org To unsubscribe send an email to typing-sig-leave@python.org https://mail.python.org/mailman3/lists/typing-sig.python.org/ Member address: kristoffel.pirard@gmail.com