As explained by @jonrsharpe, you do not pass an iterable of Constructible instances to map_is but an iterable of classes. That means that you should define the function that way:
def map_is(cs: Iterable[Type[Constructible]], i: int):
return (C(i) for C in cs)
That is enough for mypy to validate the code.
But there is an unrelated problem: you never declared any __hash__ nor __equal__ special method. That means that in assert values == (X(5), Sq(5)) the equality used is the one defined on the object class (same as is). So after the above fix, the code executes successfully but still raises an AssertionError, because the objects do have same value, yet they are distinct objects...
Protocol can't constrain __init__
What to use in replacement of an interface/protocol in python - Stack Overflow
python - typing.Protocol class `__init__` method not called during explicit subtype construction - Stack Overflow
Interfaces with Protocols: why not ditch ABC for good?
Videos
New in Python 3.8:
Some of the benefits of interfaces and protocols are type hinting during the development process using tools built into IDEs and static type analysis for detection of errors before runtime. This way, a static analysis tool can tell you when you check your code if you're trying to access any members that are not defined on an object, instead of only finding out at runtime.
The typing.Protocol class was added to Python 3.8 as a mechanism for "structural subtyping." The power behind this is that it can be used as an implicit base class. That is, any class that has members that match the Protocol's defined members is considered to be a subclass of it for purposes of static type analysis.
The basic example given in PEP 544 shows how this can be used.
Copyfrom typing import Protocol
class SupportsClose(Protocol):
def close(self) -> None:
# ...
class Resource:
# ...
def close(self) -> None:
self.file.close()
self.lock.release()
def close_all(things: Iterable[SupportsClose]) -> None:
for thing in things:
thing.close()
file = open('foo.txt')
resource = Resource()
close_all([file, resource]) # OK!
close_all([1]) # Error: 'int' has no 'close' method
Note: The typing-extensions package backports typing.Protocol for Python 3.5+.
In short, you probably don't need to worry about it at all. Since Python uses duck typing - see also the Wikipedia article for a broader definition - if an object has the right methods, it will simply work, otherwise exceptions will be raised.
You could possibly have a Piece base class with some methods throwing NotImplementedError to indicate they need to be re-implemented:
Copyclass Piece(object):
def move(<args>):
raise NotImplementedError(optional_error_message)
class Queen(Piece):
def move(<args>):
# Specific implementation for the Queen's movements
# Calling Queen().move(<args>) will work as intended but
class Knight(Piece):
pass
# Knight().move() will raise a NotImplementedError
Alternatively, you could explicitly validate an object you receive to make sure it has all the right methods, or that it is a subclass of Piece by using isinstance or isubclass.
Note that checking the type may not be considered "Pythonic" by some and using the NotImplementedError approach or the abc module - as mentioned in this very good answer - could be preferable.
Your factory just has to produce instances of objects having the right methods on them.
You can't instantiate a protocol class directly. This is currently implemented by replacing a protocol's __init__ with a method whose sole function is to enforce this restriction:
def _no_init(self, *args, **kwargs):
if type(self)._is_protocol:
raise TypeError('Protocols cannot be instantiated')
...
class Protocol(Generic, metaclass=_ProtocolMeta):
...
def __init_subclass__(cls, *args, **kwargs):
...
cls.__init__ = _no_init
Your __init__ doesn't execute because it isn't there any more.
This is pretty weird and messes with even more stuff than it looks like at first glance - for example, it interacts poorly with multiple inheritance, interrupting super().__init__ chains.
Summary
This has been fixed in later versions of Python (>=3.11) and in typing_extensions>=4.6.0.
Context
Per the comment
This issue in the Python bug tracker looks related: bugs.python.org/issue44807 ... So we may see some updates to
Protocol/__init__interaction in Python version 3.11.
Python 3.11 did address this problem
The
__init__()method of Protocol subclasses is now preserved. (Contributed by Adrian Garcia Badarasco in gh-88970.)
For versions before 3.11, the typing_extensions>=4.6.0 package also has the fix.
Changed in version 4.6.0: Backported the ability to define
__init__methods on Protocol classes.
Example
Using import from typing_extensions==4.12.2
>>> from abc import ABC
... from typing_extensions import Protocol # DIFFERENT IMPORT
...
... class AbstractBase(ABC):
... def __init__(self):
... print("AbstractBase.__init__ called")
...
... class Concrete1(AbstractBase):
... ...
...
... c1 = Concrete1() # prints "AbstractBase.__init__ called"
...
... class ProtocolBase(Protocol):
... def __init__(self):
... print("ProtocolBase.__init__ called")
...
... class Concrete2(ProtocolBase):
... ...
...
... c2 = Concrete2() # now prints "ProtocolBase.__init__ called"
AbstractBase.__init__ called
ProtocolBase.__init__ called
Hello, if one finds interfaces useful in Python (>=3.8) and is convinced that static type-checking is a must, then why not ditch ABC and always use Protocols? I understand that the fundamental idea of a protocol is slightly different from an interface, but in practice, I had great success replacing abc's with Protocols without regrets.
With abc you would write (https://docs.python.org/3/library/abc.html) :
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def eat(self, food) -> float:
passWhereas with Protocols it's gonna be (good tutorial):
from typing import Protocol
class Animal(Protocol):
def eat(self, food) -> float:
...Scores in my subjective scoring system :)
| Capability | ABC | Protocols |
|---|---|---|
| Runtime checking | 1 | 1 (with a decorator) |
| Static checking with mypy | 1 | 1 |
Explicit interface (class Dog(Animal):) | 1 | 1 |
Implicit interface with duck-typing (class Dog:) | 0.5 (kind of with register, but it doesn't work with mypy yet) | 1 |
Default method implementation (def f(self): return 5) | -1 (implementations shouldn't be in the interfaces) | -1 (same, and mypy doesn't catch this) |
| Callback interface | 0 | 1 |
| Number of code lines | -1 (requires ABC inheritance and abstracmethod for every method) | 0 (optionalProtocol inheritance) |
| Total score | 1.5 | 4 |
So I do not quite see why one should ever use ABC except for legacy reasons. Other (IMHO minor) points in favour of ABC I've seen were about interactions with code editors.
Did I miss anything?
I put more detailed arguments into a Medium. There are many tutorials on using Protocols, but not many on ABC vs Protocols comparisons. I found a battle of Protocols vs Zope, but we are not using Zope, so it's not so relevant.