Comments (87)
I promised offline to write up a proposal for structural subtyping. This is the first iteration of one. If we reach a consensus, I can write this into a PEP.
Motivation
Currently, typing
defines ABCs for several common Python protocols such as Iterable
and Sized
. The problem with them is that a class has to be explicitly marked to support them, which is arguably unpythonic and unlike what you'd normally do in your non statically typed code. For example, this conforms to the current PEP:
from typing import Sized, Iterable, Iterator
class Bucket(Sized, Iterable[int]):
...
def __len__(self) -> int: ...
def __iter__(self) -> Iterator[int]: ...
My intention is that the above code could be written instead equivalently without explicit base classes in the class definition, and Bucket
would still be implicitly considered a subtype of both Sized
and Iterable[int]
by using structural subtyping:
from typing import Iterator
class Bucket:
...
def __len__(self) -> int: ...
def __iter__(self) -> Iterator[int]: ...
As I mentioned earlier, there are many individual design decisions that we need to agree on. I'm proposing answers to many of them here.
1) What to call these types?
Let's call them protocols. The reason is that the term iterator protocol, for example, is widely understood in the community, and coming up with a new term for this concept in a statically typed context would just create confusion.
This has the drawback that the term 'protocol' becomes overloaded with two subtly different meanings: the first is the traditional, well-known but slightly fuzzy concept of protocols such as iterable; the second is the more explicitly defined concept of protocols in statically typed code (or more generally in code that just uses the typing
module). I argue that the distinction isn't importat most of the time, and in other cases people can just add a qualifier such as "protocol classes" (for the new-style protocols) or "traditional/non-class/implicit protocols".
2) How to define and use a protocol?
There would be a new class typing.Protocol
. If this is explicitly included in the the base class list, the class is a protocol. Here is a simple example:
from typing import Protocol
class SupportsClose(Protocol):
def close(self) -> None: ... # See 3) for more about the '...'.
Now if we define a class Resource
with a close
method that has a suitable signature, it would implicitly be a subtype of SupportsClose
, since we'd use structural subtyping for protocol types:
class Resource:
...
def close(self) -> None:
self.file.close()
self.lock.release()
Protocol types can be used in annotations, of course, and for type checking:
def close_all(things: Iterable[SupportsClose]) -> None:
for t in things:
t.close()
f = open('foo.txt')
r = Resource(...)
close_all([f, r]) # OK!
close_all([1]) # Error: 'int' has no 'close' method
Note that both the user-defined class Resource
and the IO
type (the return type of open
) would be considered subtypes of SupportsClose
because they provide a suitable close
method.
If using the current typing
module, our only option to implement the above example would be to use an ABC (or type Any
, but that would compromise type checking). If we'd use an ABC, we'd have to explicitly register the fact that these types are related, and this generally difficult to do with library types as the type objects may be hidden deep in the implementation of the library. Besides, this would be uglier than how you'd actually write the code in straight, idiomatic dynamically typed Python. The code with a protocol class matches common Python conventions much better. It's also automatically extensible and works with additional, unrelated classes that happen to implement the required interface.
3) How to define a method in a protocol?
I propose that most of the regular rules for classes still apply to protocol classes (modulo a few exceptions that only apply to protocol classes). I'd like protocols to also be ABCs, so all of these would be valid within a protocol class:
# Variant 1
def __len__(self) -> int: ...
# Variant 2
def __len__(self) -> int: pass
# Variant 3
@abstractmethod
def __len__(self) -> int: pass
# Variant 4
def __len__(self): pass
# Variant 5
def __len__(self) -> int:
return 0
# Variant 6
def __len__(self) -> int:
raise NotImplementedError
For variants 1, 2 and 3, a type checker should probably always require an explicit implementation to be defined in a subclass that explicitly subclasses the protocol (see below for more about this), because the implementations return None
which is not a valid return type for the method. For variants 4, 5 and 6, we can use the provided implementation as a default implementation. The default implementations won't be used if the subtype relationship is implicit and only via structural subtyping -- the semantics of inheritance won't be changed.
I also propose that a ...
as the method body in a protocol type makes the method implicitly abstract. This would only be checked statically, and there won't be any runtime checking. The idea here is that most methods in a protocol will be abstract, and having to always use @abstractmethod
is a little verbose and ugly, and has the issue of implicit None
return types confusing things. This would be the recommended way of defining methods in a protocol that don't have an implementation, but the other approaches can be used for legacy code or if people just feel like it. The recommended syntax would mirror how methods are defined in stub files.
4) How to define a data attribute in a protocol?
Similar to 3), there will be multiple valid ways of defining data attributes (or properties). All of these will be valid:
class Foo(Protocol):
a = ... # type: int # Variant 1
b = 0 # Variant 2
# Variant 3
@property
def c(self) -> int: ...
# Variant 4
@property
def c(self) -> int:
return 0
# Variant 5
@property
def d(self) -> int:
raise NotImplementedError
# Variant 6
@abstractproperty
def e(self) -> int: ...
# Variant 7
@abstractproperty
def f(self) -> int: pass
Also, properties with setters can also be defined. The first three variants would be the recommended ways of defining attributes or properties, but similar to 3), the others are possible and may be useful for supporting legacy code.
When using an ...
initializer, @abstractproperty
or pass
/...
as property body (and when the type does not include None
), the data attribute is considered abstract and must always be explicitly implemented in a compatible class.
Attributes should not be defined in the body of a method by assignment via self
. This restriction is a little arbitrary, but my idea is that the protocol class implementation is often not shared by subtypes so the interface should not depend on the default implementation. This is more of a style than a technical issue, as a type checker could infer attributes from assignment statements within methods as well.
When using the ...
initializer, the ...
initializer might leak into subclasses at runtime, which is unfortunate:
class A(Protocol):
x = ... # type: int
class B(A):
def __init__(self) -> None:
self.x = 1
b = B()
print(b.x) # 1
print(B.x) # Ellipsis
If we'd use a None
initializer things wouldn't be any better. Maybe we can modify the metaclass to recognize ...
initializers and translate them to something else. This needs to be documented, however. Also, in this proposal there is no way to distinguish between class and instance data attributes.
I'm not sure sure what to do with __init__
. I guess a protocol could provide a default implementation that could be used in explicit subclasses.
Overall, I'm probably the least happy about this part of the proposal.
5) How to explicitly declare that a class conforms to a protocol?
I propose that protocols can be used as regular base classes. I can see at least three things that support this decision. First, a protocol class could define default implementations for some methods (typing.Sequence
would be an example if we decide to turn it into a protocol). Second, we want a way of statically enforcing that a class actually implements a protocol correctly (but there are other ways to achieve this effect -- see below for alternatives). Third, this makes it possible to turn an existing ABC into a protocol and just have things (mostly) work. This would be important for the existing ABCs in typing
what we may want to change into protocols (see point 8 for more about this). The general philosophy would be that Protocols
are mostly like regular ABCs, but a static type checker will handle them somewhat specially.
Note that subclassing a protocol class would not turn the subclass into a protocol unless it also has Protocol
as an explicit base class. I assume that we can use metaclass trickery to get this to work correctly.
Some terminology could be useful here for clarity. If a class includes a protocol in its MRO, the class is an (explicit) subclass of the procotol. If a class ia a structural subtype of a protocol, it is said to implement the protocol and to be compatible with a protocol. If a class is compatible with a protocol but the protocol is not included in the MRO, the class is an implicit subclass of the protocol.
We could also explicitly add an assignment for checking that a class implements a protocol. I've seen a similar pattern in some Go code that I've reviewed. Example:
class A:
def __len__(self) -> float:
return ...
_ = A() # type: Sized # Error: A.__len__ doesn't conform to 'Sized'
# (Incompatible return type 'float')
I don't much care above the above example, as it moves the check away from the class definition and it almost requires a comment as otherwise the code probably wouldn't make any sense to an average reader -- it looks like dead code. Besides, in the simplest form it requires us to construct an instance of A
which could problematic if this requires accessing or allocating some resources such as files or sockets. We could work around the latter by using a cast, for example, but then the code would be really ugly.
6) Should we support protocols extending protocols?
I think that we should support subprotocols. A subprotocol can be defined by having both one or more protocols as the explicit base classes and also having typing.Protocol
as an immediate base class:
from typing import Sized, Protocol
class SizedAndCloseable(Sized, Protocol):
def close(self) -> None: ...
Now the protocol SizedAndCloseable
is a protocol with two methods, __len__
and close
. Alternatively, we could have implemented it like this, assuming the existence of SupportsClose
from an earlier example:
from typing import Sized
class SupportsClose(...): ... # Like above
class SizedAndCloseable(Sized, SupportsClose, Protocol):
pass
The two definitions of SizedAndClosable
would be equivalent. Subclass relationships between protocols aren't meaningful when considering subtyping, as we only use structural compatibility as the criterion, not the MRO.
If we omit Protocol
in the base class list, this would be regular (non-protocol) class that must implement Sized
. If Protocol
is included in the base class list, all the other base classes must be protocols. A protocol can't extend a regular class.
7) Should we support optional attributes?
We can come up with examples where it would be handy to be able to say that a method or data attribute does not need to be present in a class implementing a protocol, but if it's present, it must conform to a specific signature or type. One could use a hasattr
check to determine whether they can use the attribute on a particular instance.
In the interest of simplicity, let's not support optional methods or attributes. We can always revisit this later if there is an actual need. The current realistic potential use cases for protocols that I've seen don't require these. However, other languages have similar features and apparently they are pretty commonly used. If I remember correctly, at least TypeScript and Objective-C support a similar concept.
8) Should some ABCs in typing be protocols instead?
I think that at least these classes in typing
should be protocols:
Sized
Container
Iterable
Iterator
Reversible
SupportsAbs
(and otherSupports*
classes)
These classes are small and conceptually simple. It's easy to see which of these protocols a class implements from the presence of a small number of magic methods, which immediately suggest a protocol.
I'm not sure about other classes such as Sequence
, Set
and IO
. I believe that these are sufficiently complex that it makes sense to require code to be explicit about them, as there would not be any sufficiently obvious and small set of 'marker' methods that tell that a class implements this protocol. Also, it's too easy to leave some part of the protocol/interface unimplemented by accident, and explicitly marking the subclass relationship allows type checkers to pinpoint the missing implementations -- or they can be inherited from the ABC, in case that is has default implementations. So I'd currently vote against making these classes protocols.
9) Should we support recursive protocols?
Sure, why not. They might useful for representing self-referential data structures like trees in an abstract fashion, but I don't see them used commonly in production code.
10) Should we support generic protocols?
Generic protocol are important. For example, SupportsAbs
, Iterable
and Iterator
would be generic. We could define them like this, similar to generic ABCs:
T = TypeVar('T', covariant=True)
class Iterable(Protocol[T]):
def __iter__(self) -> Iterator[T]: ...
11) Should these be interoperable with other similar implementations?
The protocols as described here are basically a small extension to the existing concept of ABCs. I argue that this is the way they should be understood, instead of as something that replaces Zope interfaces, for example.
12) How should isinstance
work?
We shouldn't implement any magic isinstance
machinery, as performing a runtime compatibility check is generally difficult: we might want to verify argument counts to methods, names of arguments and even argument types, depending the kind of protocol we are talking about, but sometimes we wouldn't care about these, or we'd only care about some of these things.
My preferred semantics would be to make isinstance
fail by default for protocol types. This would be in the spirit of duck typing -- protocols basically would be used to model duck typing statically, not explicitly at runtime.
However, it should be possible for protocol types to implement custom isinstance
behavior when this would make sense, similar to how Iterable
and other ABCs in collections.abc
and typing
already do it, but this should be specific to these particular classes. We need this fallback option anyway for backward compatibility.
13) Should every class be a protocol by default?
Some languages such as Go make structural subtyping the only or the primary form of subtyping. We could achieve a similar result by making all classes protocols by default (or even always). I argue that this would be a bad idea and classes should need to be explicitly marked as protocols, as shown in my proposal above.
Here's my rationale:
- Protocols don't have some properties of regular classes. In particular,
isinstance
is not well-defined for protocols, whereas it's well-defined (and pretty commonly used) for regular classes. - Protocol classes should generally not have (many) method implementations, as they describe an interface, not an implementation. Most classes have many implementations, making them bad protocol classes.
- Experience suggests that most classes aren't practical as protocols anyway, mainly because their interfaces are too large, complex or implementation-oriented (for example, they may include de facto private attributes and methods without a
__
prefix). Most actually useful protocols in existing Python code seem to be implicit. The ABCs intyping
andcollections.abc
are a kind-of exception, but even they are pretty recent additions to Python and most programmers do not use them yet.
14) Should protocols be introspectable?
The existing introspection machinery (dir
, etc.) could be used with protocols, but typing
would not include an implementation of additional introspection or runtime type checking capabilities for protocols.
As all attributes need to be defined in the class body based on this proposal, protocol classes would have better support for introspection than regular classes where attributes can be defined implicitly -- protocol attributes can't be initialized in ways that are not visible to introspection (using setattr
, assignment via self
, etc.). Still, some things likes types of attributes wouldn't be visible at runtime, so this would necessarily be somewhat limited.
15) How would Protocol
be implemented?
We'd need to implement at least the following things:
- Define class
Protocol
(this could be simple, and would be
similar toGeneric
). - Implement metaclass functionality to detect whether a class is a protocol or not. Maybe add a class attribute such as
__protocol__ = True
if that's the case. Verify that a protocol class only has protocol base classes in the MRO (except forobject
). - Optionally, override
isinstance
. - Optionally, translate
...
class attribute values to something
else (properties?).
from typing.
@JukkaL @ambv Here is the first draft of Protocols PEP that I promised to write https://github.com/ilevkivskyi/peps/blob/protocols/pep-0544.txt (this is mostly based on what Jukka wrote above)
I will give you the write rights, so that you could make changes directly there (or you can make PRs when you want). I also have a plan of implementing this in mpy/typing/typeshed we could discuss this later when we agree on the specification.
EDIT: I renamed the file just to run the build, the PEP number is not yet "official".
from typing.
@ilevkivskyi, this looks good. I reserved the number. I'll be at the Mercurial sprint for the rest of the week but I'll definitely get to this on Monday.
Will be obvious to everyone, including newcomers from some other language.
This is not the case. Like many other terms in computing science, this one is heavily overloaded. You might understand protocols as stateful runtime behavior, but you can also use this term to define structural interfaces. So, while "interface" might sound friendlier to Java people, "protocol" sounds just as obvious to iOS programmers.
You chose the "Iterator protocol", mentioned in the Python documentation. The same documentation uses "protocol" interchangably with "interface", for instance:
- the number protocol
- the mapping protocol
- the descriptor protocol
...and so on.
from typing.
For mypy, could we start by adding this to
mypy_extensions
?
Why do you think it is better to add this to mypy_extensions
instead of adding this directly to typing
? I think think this will be a widely used feature. Also it is quite uncontroversial, we still need to agree on details, but the general idea of structural subtyping seems to be clear.
My current plan for implementation is (not necessarily in this order):
- Add runtime implementation to
typing
- Update
typeshed
- Add protocol type to
types.py
. Here I think we could potentially reuseTypedDict
and make protocols justInstance
s with a special fieldprotocol_type
that will point to aTypedDict
mapping member names to member types. - Recognize protocol definitions in
semanal.py
- Implement actual changes to
meet.py
,join.py
,subtypes.py
, etc. - Add support for recursive protocols.
I think it is better to make this in several steps (PRs), rather than in single one. What do you think?
from typing.
PEP 544 is now accepted, mypy (and some other type checkers) have good support for the PEP, all necessary infrastructure updates have been made. So I am finally closing this issue as fixed.
from typing.
I like almost all of this. Let's take this to python-ideas now! I have a few nits and questions, but they're not important enough to wait, and they're not very deep. (There's something niggling about making e.g. Sized a Protocol and not implementing isinstance(), since collections.abc.Sized does implement it.)
from typing.
Yeah, it can be entirely methods, entirely attributes or a mix.
from typing.
@ilevkivskyi Looks good. I'll write some comments next week.
from typing.
Actually I cannot find a way in GitHub to comment on the file in your repo.
Some random questions:
-
Can protocols be used in casts? Currently (L298) you enumerate where they can be used -- maybe instead just say they can be used wherever types are used? (Or are there restrictions? Apart from subclassing, which is a separate topic anyways.)
-
Methods with (nearly) empty bodies seem to have so many variations that I wonder if this convenience isn't too confusing. Why do we need multiple ways anyway?
-
Given that a valid implementation doesn't have to inherit from the protocol(s) it implements, what's the use case for allowing implementations and non-protocol attributes in protocol classes at all?
-
Related, it seems confusing that using
@abstractmethod
makes the implementation serve as a default but leaving it out requires implementation -- this is backwards from the regular meaning of "abstract" methods. (E.g. take yourPoint(RGB)
example -- if RGB was an ABC, Point would be in error for omittingto_byte()
but the lack of anintensity()
implementation would not be considered an error.) -
There is also the issue that sometimes the default implementation really should be just a "pass" statement. But your PEP currently gives that a special meaning. I guess I am really just pushing for either some other explicit marker for methods and attributes that are meant to be part of the protocol, or for making everything except
__private
names a part of the protocol. Or perhaps insist on...
? But I think we'll end up with an explicit marker. EIBTI. -
The rules around defining subprotocols deviate from the rules for ABC -- with the latter, abstract-ness is simply defined by having at least one abstract method being unimplemented.
-
Recursive protocols deserve a subheading or at least a new paragraph. (L524)
-
There ought to be a list of all the ABCs that we want to upgrade to being Protocols. (E.g. Iterable, Sized, etc.) -
L547 "or recursive protocols, structural subtyping is decided positively for all situations that are type safe" sounds very tautological. The example leads me to think that this is just about empty lists?
-
I know
Intersection
is a mouthful, butAll
as the dual ofUnion
feels terminologically murky. Unless you expect this to be very common I hope we can just writeIntersection
. -
Related, I could write an intersection of two protocols using M.I., right?
class HashableFloatSequence(Hashable, Sequence[float], Protocol):
pass
Have you thought about implementation yet?For mypy, could we start by adding this tomypy_extensions
?What about eventually backporting to 3.5 and 3.6?
I'm stopping now, more maybe later.
from typing.
Re @runtime
-- why not make this behavior the default, with the implied [Any]
parameter(s) for generic classes?
I suspect the example there is iffy:
def process(items: Iterable[int]):
if isinstance(items, Iterator):
# 'items' have type 'Iterator[int]' here
That's not how it currently works in mypy, reveal_type(items)
shows Iterator[Any]
PS. typo: issublclass
.
from typing.
I've reviewed the current draft of PEP 544 at PyCon US 2017 sprints. It looks good from PyCharm's perspective. @JukkaL was kind to answer some questions during my review. The only remaining issue is we would like protocols to be available earlier than Python 3.7, perhaps, as a part of typing_extensions
, see #435.
from typing.
from typing.
I have some concerns about turning Sequence
etc. into protocols. Several of the method signatures in these ABCs are pretty subtle, so it's quite easy to write a class that almost implements, say, Sequence
, but not quite because of some signatures being incompatible. With an explicit base class a mismatch will immediately be reported by a type checker -- otherwise it's quite possible to write something that looks like a sequence but actually isn't, and checking that by only reading the code can be hard. This isn't a major problem for most existing protocols in typing
since the signatures are pretty obvious.
Also, currently isinstance
works with them, so we'd have to support that in the future -- so these protocols would be "runtime" protocols. This brings the risk that the runtime and static views of subtyping become inconsistent because of minor signature differences.
Examples of somewhat tricky signatures in Sequence
:
@overload # Overloads are a somewhat tricky feature
def __getitem__(self, i: int) -> _T_co: ...
@overload
def __getitem__(self, s: slice) -> Sequence[_T_co]: ...
def __contains__(self, x: object) -> bool: ... # object argument type may be unexpected
from typing.
It's meant to be used for structural subtyping. It is quite similar to ABCs, but neither is a replacement for the other. The details of how to use Protocol
are still poorly defined, as the mypy type system does not know about structural subtyping yet. However, adding support for structural subtyping shouldn't be too difficult, once we figure out the semantics.
Here is an example of how Protocol
could be used:
class SupportsFileno(Protocol):
@abstractmethod
def fileno(self) -> int: pass
def f(file: SupportsFileno) -> None:
id = file.fileno() # Okay
...
f(open('foo')) # Okay, since file objects have fileno()!
There is no need to explicitly declare that file objects implement SupportsFileno
(the type checker would infer this automatically). This is unlike ABCs, where base classes have to be explicitly defined/registered.
The current implementation of Protocol
is just a proof of concept, as is the implementation of overload
. A production implementation would probably have to be optimized, and there are probably corner cases that aren't handled yet.
from typing.
Protocols look to me like a pretty reasonable way of implementing structural typing, which is definitely something that we're hoping to see in the PEP. This design is necessarily verbose; an in-line, anonymous definition using dictionaries, like
def f(file: {'fileno': Callable[[], int]}) -> None: ...
is a bit more concise when the protocol/structural type is small. This would conflict with the current proposal to use dictionaries in annotations to represent current non-type uses of annotations (https://github.com/ambv/typehinting/blob/master/pep-NNNN.txt#L194), however see #26 for thoughts on alternatives.
from typing.
I haven't seen any evidence for protocol types being used/useful all over the place, so my current working hypothesis is that having a heavy-weight syntax for protocols only adds a trivial amount of syntactic overhead for the vast majority of programs.
However, the absence of evidence doesn't prove anything -- if somebody finds a few counterexamples I'm happy to change my mind. :)
Maybe we could have a look at some Go code and see how many small, throwaway structural types are used there? Of course, having convenient syntax for a feature may nudge programmers into using the feature more often.
from typing.
ABCs have runtime instance checks so it's hard for the type checker to use them for structural sub-typing. If it could, that would be the simplest and most elegant solution. Otherwise, could we leave this out for now and look how we could make it work with existing protocol solutions like Zope interfaces?
from typing.
Jukka, your typing.py has a Protocol class that is more complex than most other infrastructure, and it is used for those generic ABCs (e.g. Sized, Hashable) whose collections.abc counterpart does a runtime structural check. Does this mean that you have now implemented this in mypy? I kind of like the resulting syntax for defining a generic ABC that is supposed to use structural testing:
class Sized(Protocol):
@abstractmethod
def __len__(self) -> int: pass
class Container(Protocol[T]):
@abstractmethod
def __contains__(self, x) -> bool: pass
(Note that Sized is not generic, but Container is.)
Łukasz: I have no experience with zope interfaces. But perhaps they are easy enough to parse for a hypothetical static type checker, and they feel close enough to types/classes that we should try to support them? Can you sketch a simple example?
from typing.
Quoting Łukasz in python/mypy#539 (comment):
"""
As for explicit protocols, there are some competing (as in: not easily composable) standards here:
- ABCs with @AbstractMethod
- Zope interfaces
- https://pypi.python.org/pypi/characteristic/
etc. etc.
As ABCs are built-in, it seems natural to suggest that they should be used for defining interfaces. However, I understand that static analysis (so, the type checker) might not be able to process every abstract class in general.
"""
from typing.
Guido, the machinery in mypy's typing.py has been there for a long time, but it was never fully implemented, and in particular, the type checker doesn't know anything about this. It shouldn't really be there -- everything should just use ABCs, until there is type system support for protocols.
I added a new task for removing Protocol
:
python/mypy#552
If protocols (or something similar) will be included in the PEP, I'll update the above issue.
from typing.
So we have multiple competing ways to spell protocols, but no way to type-check them statically. This feels like something we'll have to leave out of the PEP and come back to later in a separate PEP.
from typing.
OK, let's remove Protocol from typing.py for now. We should come back to it in a separate PEP, especially that PEP 245 has been rejected and there are ABCs now.
As for Zope interfaces, an interface definition is easy to parse, except for invariants (runnable checks, similar to instancechecks in ABCs). We have to bear in mind that existing interface definitions may sometimes be dynamic, it's Python. That's fine, I think. What the type checker doesn't know, it assumes it's correct.
I see two issues with Zope interfaces:
- arbitrary classes can be externally registered as implementing interfaces, just like ABCs can have external classes registered to them (so we'd have the same problems with this)
- adaptation (yes, Zope interfaces implement PEP 246) and runtime adapt hooks
from typing.
Closing as this is being left out for now.
from typing.
Reopening this, as duck typing is important according to the BDFL-Delegate (Mark Shannon).
from typing.
I'm writing here a bunch of related issues that came to mind. These are intended to start a discussion -- I try not to propose any course of action yet (though I have some opinions about some of these already).
I'm sure there are other issues not covered by these.
1) What to call these types?
We could call them at least duck types, protocols, interfaces or structural types. I'll call them protocols below, but I'm not specifically advocating any particular term.
2) How to define an interface?
Here are a few possible ways:
@protocol
class Sized: ...
class Size(Protocol): ...
class ISize(Protocol): ... # Zope interface naming convention
3) How to define a method in a protocol?
A few possible ways:
def __len__(self) -> int: pass
def __len__(self) -> int: ... # Literal ellipsis; semantically same as above
@abstractmethod
def __len__(self) -> int: pass
def __len__(self) -> int:
raise NotImplementedError # I'm not actually advocating this, but it's an option
4) How to define an attribute in a protocol?
Some ideas:
class Foo(...):
a = ??? # type: Foo # Annotation optional, not sure what the initializer should be
b = ... # type: Foo # As in stubs; literal ellipsis
@property
def c(self) -> 'Foo': pass
@abstractproperty
def d(self) -> 'Foo': pass
e = typing.attribute('''docstring''') # type: Foo # Similar to Zope interfaces
We could also disallow recursive types (see below).
5) How to explicitly declare that a class conforms to a protocol?
We may want the type checker to verify that a class actually conform to a protocol. Some ideas for this:
class A(Sized):
...
@implements(Sized)
class A:
...
class A:
implements(Sized) # Inspired by Zope interfaces
Alternatively, this can always be implicit. In that case we can do something like this
to force a check:
if False:
_dummy = A() # type: Sized # Complain if A doesn't implement Sized
We also need to decide whether the subclass will inherit the method implementations defined in the body of the protocol class in case we use regular inheritance (assuming they aren't abstract but regular methods). If we use a class decorator, we'd probably don't inherit anything.
6) Should we support protocols extending protocols?
We can probably use the same approach for defining a subprotocol as in (5).
7) Should we support optional methods and attributes?
I think TypeScript supports the concept of optional methods. If we declare a method as optional, subtypes don't have to implement it, but if they implement, the signature should be compatible. This could also work with attributes, but coming up with a syntax may be tricky.
8) Should some ABCs in typing be protocols instead?
For example, maybe typing.Sized
should be a protocol.
9) Should be support recursive protocols?
For example, when Foo
was used in the definition of Foo
in (4), it defined a recursive structural type. If we think that they are too tricky we can disallow them.
10) Should we support generic protocols?
For example, if typing.Iterable
is a protocol, it would have to be generic.
11) Should these be interoperable with other similar implementations?
See Guido's remark above. Maybe we should interoperate with Zope interfaces (or just borrow the implementation and include it in typing
), for example.
from typing.
I'm sorry I brought up Zope Interfaces. I'll respond to the rest when I
have a real keyboard.
On Monday, May 18, 2015, Jukka Lehtosalo [email protected] wrote:
I'm writing here a bunch of related issues that came to mind. These are
intended to start a discussion -- I try not to propose any course of action
yet (though I have some opinions about some of these already).I'm sure there are other issues not covered by these.
1) What to call these types?
We could call them at least duck types, protocols, interfaces or
structural types. I'll call them protocols below, but I'm not specifically
advocating any particular term.2) How to define an interface?
Here are a few possible ways:
@protocol
class Sized: ...class Size(Protocol): ...
class ISize(Protocol): ... # Zope interface naming convention
3) How to define a method in a protocol?
A few possible ways:
def len(self) -> int: pass
def len(self) -> int: ... # Literal ellipsis; semantically same as above
@AbstractMethod
def len(self) -> int: passdef len(self) -> int:
raise NotImplementedError # I'm not actually advocating this, but it's an option4) How to define an attribute in a protocol?
Some ideas:
class Foo(...):
a = ??? # type: Foo # Annotation optional, not sure what the initializer should beb = ... # type: Foo # As in stubs; literal ellipsis @property def c(self) -> 'Foo': pass @abstractproperty def d(self) -> 'Foo': pass e = typing.attribute('''docstring''') # type: Foo # Similar to Zope interfaces
We could also disallow recursive types (see below).
5) How to explicitly declare that a class conforms to a protocol?
We may want the type checker to verify that a class actually conform to a
protocol. Some ideas for this:class A(Sized):
...@implements(Sized)
class A:
...class A:
implements(Sized) # Inspired by Zope interfacesAlternatively, this can always be implicit. In that case we can do
something like this
to force a check:if False:
_dummy = A() # type: Sized # Complain if A doesn't implement SizedWe also need to decide whether the subclass will inherit the method
implementations defined in the body of the protocol class in case we use
regular inheritance (assuming they aren't abstract but regular methods). If
we use a class decorator, we'd probably don't inherit anything.6) Should we support protocols extending protocols?
We can probably use the same approach for defining a subprotocol as in (5).
7) Should we support optional methods and attributes?
I think TypeScript supports the concept of optional methods. If we declare
a method as optional, subtypes don't have to implement it, but if they
implement, the signature should be compatible. This could also work with
attributes, but coming up with a syntax may be tricky.8) Should some ABCs in typing be protocols instead?
For example, maybe typing.Sized should be a protocol.
9) Should be support recursive protocols?
For example, when Foo was used in the definition of Foo in (4), it
defined a recursive structural type. If we think that they are too tricky
we can disallow them.10) Should we support generic protocols?
For example, if typing.Iterable is a protocol, it would have to be
generic.11) Should these be interoperable with other similar implementations?
See Guido's remark above. Maybe we should interoperate with Zope
interfaces (or just borrow the implementation and include it in typing),
for example.—
Reply to this email directly or view it on GitHub
#11 (comment).
--Guido van Rossum (on iPad)
from typing.
Another open issue:
12) How should isinstance work?
We could disallow isinstance
or check for the presence of attributes in the protocol (similar to the now-removed mypy's Protocol
implementation).
from typing.
IMO, following the same conventions as stubs would be easier and less confusing.
from typing.
FYI, I posted a link to the latest proposal to python-ideas.
Crosslink: https://mail.python.org/pipermail/python-ideas/2015-September/035859.html
from typing.
Sorry, the description of isinstance
was unclear in the proposal. My idea was to preserve the isinstance
support for Sized
and all the other existing ABCs in typing
that would be turned into protocols, but new protocol types wouldn't automatically get any isinstance
machinery. This way the ABCs would remain compatible with existing code that uses them and might use isinstance
with them.
from typing.
This looks really cool and seems similar to traits. What if I want to extend library code and tack a protocol onto a class without monkey patching or subclassing? Can we have generic functions/multiple dispatch dispatch on a protocol? That would make for much more modular and slimmer code bases.
from typing.
from typing.
Scratch the traits comment.
It would be expensive, unless we would we be able to register protocols explicitly. What if I define a method outside of the class definition using an overloaded or multi dispatch function...would the protocol be ok with the needed method not residing as a class attribute?
from typing.
Drive-by comment: If PEP 526 (variable annotations) is accepted, maybe structural subtyping could be implemented using the following syntax:
class Point(<some magical base class):
x: float
y: float
z: float = 0
Without the magical base class this would just declare a class with instance variables. But with the proper base class it could mean to declare a type to be used with structural type checking, so that, after the above, the following would work:
class MyPoint: # unrelated to Point
def __init__(self, x: float, y: float):
self.x, self.y = x, y
p = MyPoint(1, 2)
assert isinstance(p, Point) # true, because of structural typing
from typing.
In the above example, for MyPoint
to be a subtype of Point
it should probably define z
as well, even though Point
has a default value for z
. The way I see it, the default value would only make a difference if a class explicitly subclasses Point
. Or maybe we'd even require MyPoint
to give z
a default value in the class body, so that MyPoint.z
is defined. The latter might be a bit overcomplicated, though.
from typing.
from typing.
@gvanrossum
Do I understand correctly that you want to say: Specifying types only for methods would be not enough to completely define a protocol, while with PEP 526 it will be possible to specify types for everything in a class and therefore to make a protocol well defined.
I think this is a good point. Moreover, it could be another argument why PEP 526 is important. I think the protocols are very in the spirit of Python duck typing, and everything that helps to introduce them is important.
from typing.
from typing.
Can a protocol be defined entirely though methods as well? (ie making it more abstract)
from typing.
In case anyone cares, the HN post on Zulip's use of mypy led to a thread all about protocols and typing in Python.
from typing.
from typing.
@ilevkivskyi would this be compatible with function overloading/multiple dispatch? There was some discussion about that and it may make sense to keep things open.
from typing.
@datnamer I think protocols will be not different from normal classes w.r.t. @overload
. There will be full static support, but limited runtime support (via @singledispatch
or third-party libs).
from typing.
Guido, I am aware about your situation, but just in case you have time/ability, I will be very glad if you also review/contribute to this PEP.
from typing.
Sorry for bringing this up again, but regarding the terminology, I think there's a difference between a "protocol" which implies runtime behaviour and temporal properties (as in "Iterator protocol") , and "structural typing" which is purely syntactic. Mypy (and most typecheckers) do not attempt to check whether a class implements a protocol, but whether it has certain syntactic structure. So why not stick to this term. When you ask whether a class implements the Iterator protocol, all of a sudden you have two possible answers, which may cause some confusion. If we call this thing "structure" or "interface" we say that the protocol requires such and such structure, with such and such runtime behaviour.
class R(Interface): ...
Will be obvious to everyone, including newcomers from some other language.
from typing.
Just finished reading the PEP by @ilevkivskyi ; LGTM and submitted a PR to tweak some grammar (and to let methods with just a docstring be acceptable as declaring a part of the protocol).
I did have some questions, though:
- When extending a protocol, does the inheritance order matter? (E.g. https://github.com/ilevkivskyi/peps/blob/2d89ba950431889746810f7a39ac8117b40fff81/pep-0544.txt#L483 has
Protocol
come first and I wasn't sure if that was on purpose since I'm used to seeing the more generic base class come last) - Would a
GenericProtocol
class be better than the@runtime
decorator? (otherwise I don't think the name communicates what the decorator does well; maybe@add_instancecheck
?)
from typing.
(and to let methods with just a docstring be acceptable as declaring a part of the protocol)
I like this addition, I merged your PR, thanks!
When extending a protocol, does the inheritance order matter?
The order of two protocols makes a difference in case they both define the same method, the one with a more narrow type for the method should come first (this just reflects Liskov principle w.r.t. multiple inheritance, i.e. what you normally see). But this rule does not need to be extended to the
Protocol
base itself (typecheckers and runtime implementation can easily sort this out).
So that these two are equivalent:
class SubSized(Protocol, Sized):
...
class SubSized(Sized, Protocol):
...
and purely "aesthetically" I like the first version a bit more.
Would a GenericProtocol class be better than the
@runtime
decorator? (otherwise I don't think the name communicates what the decorator does well; maybe@add_instancecheck
?)
This is a good question! I was struggling to choose a good name for this. I still like a decorator a bit more than a different base class. Actually your proposal @add_instancecheck
looks good. I was also thinking about something like @auto_instancecheck
or @auto_isinstance
. The problem here is that either the name is long or it is not very informative.
from typing.
Wow, a lot to read! I wish this was a PR for the upstream peps repo already, the review tools are a bit better I think.
Re: base class order, I prefer Protocol last, just as with Generic, and in some M.I. cases it will end up last anyway.
In general I like that at runtime this very close to ABCs, but with different behavior for static checking whether a type is compatible with another.
from typing.
I wish this was a PR for the upstream peps repo already, the review tools are a bit better I think.
OK, I will fix the order of Protocol
base (make it last) and will make a PR to upstream soon.
from typing.
Re: ellipsis (L737): I don't think it's necessary to mess with this in the metaclass. Let's just warn people that that is how it is. (The more magic a metaclass has the less people understand the code that depends on that magic.)
from typing.
Also I think that anything you support in the backport must be supported in the latest version, as people will need to write code that works in a range of Python versions, e.g. 3.5...3.7.
from typing.
@gvanrossum Thank you for review! It is very helpful. I agree more or less with practically all points. I will implement your comment later (could not do this today).
from typing.
It turns out that more than half of your points could be answered/fixed by just going with explicit marking of protocol members. I think this is a strong sign that we should really do it this way. It is a bit more verbose, but simplifies the logic a lot. I changed the PEP accordingly.
Given that a valid implementation doesn't have to inherit from the protocol(s) it implements, what's the use case for allowing implementations and non-protocol attributes in protocol classes at all?
We need to allow explicit implementation (subclassing) of protocols for two reasons. First, existing code should continue working. Second, protocols could provide useful "building blocks" that may be used to implement the abstract methods.
L547 "or recursive protocols, structural subtyping is decided positively for all situations that are type safe" sounds very tautological. The example leads me to think that this is just about empty lists?
Yes, this is something similar. This is a bit tricky point, following normal rules (protocol is implemented if all signatures match) here we find that Tree[float]
is a subtype of Traversable
if Tree[float]
is a subtype of Traversable
(because of signature of leafs
). I wanted to say that such (seemingly ambiguous) cases are decided positively. I tried to improve wording.
I know
Intersection
is a mouthful, butAll
as the dual ofUnion
feels terminologically murky. Unless you expect this to be very common I hope we can just writeIntersection
.
I dont't think it will be very common, but still quite common. We could hear from others, and then choose the name.
That's not how it currently works in mypy,
reveal_type(items)
showsIterator[Any]
Yes, but I think mypy could find the more precise type Iterator[int]
, for protocols this would be relatively easy to improve.
from typing.
@gvanrossum OK, I pushed the changes and made a PR to python/peps
python/peps#224 so that it will be convenient to review.
from typing.
@gvanrossum @JukkaL @ambv
There are two things that needs to be discussed. In the current state of the proposal, code like this will be flagged as error:
class MyIterable(Iterable): # Note no 'Protocol' base here
@abstractmethod
def additional_method(self) -> None: ...
# Error: '__iter__' not implemented
class Concrete(MyIterable):
def __iter__(self): ...
# etc.
I expect that there are relatively many examples of such code around. Now mypy
is fine with this, and it would be unfortunate if it will start complaining about all such cases. But on the other hand, I could imagine that people want to be warned if some class does not fully implements a certain protocol.
I think the best solution is to make a default semantics that the class MyIterable
is no more a protocol (i.e. could not be used with structural subtyping) and is "downgraded" to a normal ABC. We could have a flag for this in mypy (--strict-implementations
?) that will flag such cases. By running with this flag, one could find out whether it is a real mistake or one wants a subprotocol there:
class MyIterable(Iterable): # Note no 'Protocol' base here
@abstractmethod
def additional_method(self) -> None: ...
# Error here only shown with --strict-implementations
MyIterable() # Error: can't instantiate class with abstract methods ...
def fun(x: Iterable) -> None:
...
it: MyIterable
con: Concrete
fun(it) # Error: 'MyIterable' does not implement 'Iterable'
fun(con) # OK
I am going to update the PEP now. A random thought: the situation with classes <-> ABCs <-> protocols
somehow reminds me the situation with bytes <-> unicode <-> str
and/or Genrator <-> AwaitableGenerator <-> Coroutine
.
Second, we need to decide how to indicate in stubs that a method has a default implementation.
I will work on implementing this now (although the PEP is not finished), maybe it will be easier to make some decisions having something to look at.
from typing.
What would be the reasoning for someone specifically wanting to inherit a protocol?
Explicit inheritance would seem troublesome. As it stands, methods on the protocol that do have a default implementation would not be part of the protocol, right? Folks might expect the default implementations to "magically" become available. Inheriting a protocol seems bad in other ways, such as polluting the class with variable annotations (x = ...
) and any other properties that are not part of the protocol as the type checker understands it.
Also, because the of dual meaning of inheritance, Protocol
needs to be specified explicitly as a base class whenever an inheriting class is itself a protocol. This leads to situations like these:
class A(Protocol): ... # protocol
class B(A): ... # not a protocol
class C(B, Protocol): ... # is this a protocol?
class D: pass # is an A and a C but not a B?
What would happen here?
We need to allow explicit implementation (subclassing) of protocols for two reasons. First, existing code should continue working. Second, protocols could provide useful "building blocks" that may be used to implement the abstract methods.
The "building block" aspect is important, but does it require inheritance?
How about instead allowing something like:
class MyIterable(object, implements=Iterable):
...
The only downside I see here would be that ABCs could not themselves be protocols, so protocols matching the built-in ABCs would need to be provided separately.
from typing.
As it stands, methods on the protocol that do have a default implementation would not be part of the protocol, right?
No, not right. Where do you read this? Currently, PEP uses an idiom that one would use for normal ABCs:
def meth_with_default(self):
return super().method_with_default()
in examples, and explicitly states that:
In this case [empty body] type checkers will recognize that there is no default
implementation, i.e. it can't be used via ``super()`` in explicit
subclasses.
class C(B, Protocol): ... # is this a protocol?
This is an error. As PEP currently says:
A protocol can't extend a regular class.
Inheriting a protocol seems bad in other ways, such as polluting the class with variable annotations (
x = ...
) and any other properties that are not part of the protocol as the type checker understands it.
If you don't like it - don't use it. I don't think we need to break the currently working code (or unnecessarily complicate everything by duplicating all ABCs with protocols) only because some people will not use explicit inheritance.
from typing.
As it stands, methods on the protocol that do have a default implementation would not be part of the protocol, right?
No, not right. Where do you read this? Currently, PEP uses an idiom that one would use for normal ABCs:
Ok, I misread this. Every method that is not annotated with @abstractmethod
is not a protocol member, whether or not it has a default implementation. I believe folks will expect those to be defined because they are indeed defined in classes that directly inherit the protocol.
How will the type-checker interpret an ABC that is also a protocol and has non-protocol members? Will the type checker expect it to have the non-protocol members?
Also, somewhat related to this, say you transform collections.abc.Mapping
into a Protocol. Since its keys()
method is not abstract, it will not be part of the protocol but will be part of the ABC. For the keys()
method to again become "visible" to the type checker, it will need to be updated to an abstract method with a default implementation, correct?
from typing.
Every method that is not annotated with
@abstractmethod
is not a protocol member, whether or not it has a default implementation.
There is no such thing as "default implementation" for non-abstract methods.
How will the type-checker interpret an ABC that is also a protocol and has non-protocol members? Will the type checker expect it to have the non-protocol members?
No magic happens in explicit subclassing (except for few nuances explicitly mentioned in the PEP). As PEP currently says:
the semantics of inheritance is not changed
The point of protocol members vs non-protocol members is that former are used for structural subtyping. Again, quoting current PEP:
A concrete type or a protocol ``X`` is a subtype of another protocol ``P``
if and only if ``X`` implements all protocol members of ``P``. In other
words, subtyping with respect to a protocol is always structural.
Also, somewhat related to this, say you transform
collections.abc.Mapping
into a Protocol. Since itskeys()
method is not abstract, it will not be part of the protocol but will be part of the ABC. For thekeys()
method to again become "visible" to the type checker
I don't understand what do you mean by "visible". A class simply does not need to implement keys()
in order to be considered a subtype of Mapping
.
Also, if a class explicitly inherits Mapping
, then it gets keys()
for free.
from typing.
I don't understand what do you mean by "visible". A class simply does not need to implement
keys()
in order to be considered a subtype ofMapping
.
Sorry, I am not sure what words to use here.
My question relates to what happens when you transform an ABC
to a Protocol
. As I understand it, the type checker would allow the following if MyMapping
is an an ABC
but not if it is a Protocol
:
class MyMapping(Protocol): # vs MyMapping(ABC)
@abstractmethod
def __iter__(self) -> str: ...
def keys(self) -> List[str]:
return list(self)
def get_keys(x: MyMapping) -> List[str]:
return x.keys()
If MyMapping
is interpreted to be a concrete class stemming from a protocol, then x
must allow any instance of a class that implements __iter__(self) -> str
, i.e. all the protocol methods. Therefore, the type checker does not recognize x.keys()
.
from typing.
@lyschoening
Yes, the code snippet above will be flagged as error on return x.keys()
This is actually an interesting point, and I think this is reasonable. Maybe we need to add this example to the PEP to clarify the point.
from typing.
I still need to catch up, but skimming, this caught my eye:
There is no such thing as "default implementation" for non-abstract methods.
That seems odd. Surely a non-abstract method is always its own default implementation? Or is there a special meaning to default implementation here that I'm missing? Or are we talking about the default implementations for certain built-in operations, like str()
, or when e.g. iterating over something that doesn't have __iter__
but does have __getitem__
will just index it using the sequence 0, 1, 2, ... until it gets an IndexError? (But that concept is hard to generalize beyond a handful of ancient built-in operations.)
from typing.
I left some comments at python/peps#224.
from typing.
Due to the confusion shown by @lyschoening about what institutes the interface of a protocol, I'm starting to come around to @gvanrossum 's thinking that something needs to have @abstractmethod
or it isn't required.
As for the "There is no such thing as "default implementation" for non-abstract methods" confusion, I think this is just semantics that people are disagreeing with.
Lastly, I don't think I agree with Protocol
going into types
. Generic
isn't in types
, so what makes Protocol
so special? Why not put them into typing
as this is after all about a form of typing? (Yes, it's technically a different type of typing but most people won't care about the theoretical difference and thus I don't think will care that much that structural and nominal typing are both in the typing
module.)
from typing.
I'm starting to come around to @gvanrossum 's thinking that something needs to have
@abstractmethod
or it isn't required.
This is how it is written in the current version. I also agree with this.
Lastly, I don't think I agree with Protocol going into
types
I didn't want to put the Proyocol
in types
stdlib module. I propose to put it in typing
. Sorry for a confusion, there is also types.py
module in mypy
that I was talking about.
Surely a non-abstract method is always its own default implementation?
Yes, I meant exactly this. Reading how I phrased it, now I see that it could be understood as opposite to what I actually meant :-) sorry.
from typing.
My confusion aside, one point I want to make is that it would be easier to reason about protocols if they weren't generally used for ("default") method implementations.
In the PEP at the moment if there is a method implementation on the protocol and it's not marked by @abstractmethod
, it is not a protocol method. So, to behave as an ABC at runtime (combination of abstract and predefined methods), yet still be usable (all relevant methods are protocol methods), the interpreter would need to check whether the method body is an ellipsis and suppress the TypeError if it is not.
class MyMapping(Protocol):
@abstractmethod
def __iter__(self) -> str: ... # abstract method that is a protocol member
@abstractmethod
def keys(self) -> List[str]: # protocol member but not really an abstract method
return list(self)
Maybe there should be a decorator for protocol methods other than @abstractmethod
?
from typing.
I personally don't think that the body should represent anything special. People will inevitably call methods when they subclass a protocol they know they want to implement. And so that should do the proper thing (just like what ABCs are expected to do). So the method should raise NotImplementedError or provide a default implementation that when called through super()
makes sense.
from typing.
@lyschoening
I am not sure what do you mean by
the interpreter would need to check whether the method body is an ellipsis and suppress the TypeError if it is not.
there will be no any changes in the interpreter, most of the things we discuss are only for type checker. But I think I understand what you want, we are going to update the PEP draft, so that you will not need to put @abstractmethod
on keys()
. Briefly, we will probably have all methods as protocol members, but some of them will be abstract, i.e. required for implementation even in the case of explicit subclassing.
@brettcannon
I think the only thing that we still need to keep special for "empty" bodies is to suppress type errors in type checker on the definition:
class Proto(Protocol):
@abstractmethod
def method(self) -> Iterable[int]: # this should not be an error, although formally it is
raise NotImplementedError # since we don't return an 'Iterable'.
from typing.
What I meant was that all protocol members required @abstractmethod
, then members with an implementation, such as keys()
would have to be implemented (with super()
or otherwise), which would force them to be used differently than regular ABC
s even though the runtime behavior might be the same, unless the body of protocol members were checked at runtime and only those methods having an ellipsis were treated as abstract methods. If all methods are protocol members that solves the problem.
(I am not advocating that they should be usable in the same way as other ABCs, but that appeared to be the goal.)
from typing.
Yes on that last question. We also want in for Python 2.7.
from typing.
@vlasovskikh I just posted the latest version of the PEP on python-dev https://mail.python.org/pipermail/python-dev/2017-May/148005.html
from typing.
This is an incredible feature and I'm very eager to use it. Just curious when the current "pseudo-protocols" like Iterable
will be converted to typing_extensions.Protocols
(mentioned here in the pep) so that I can begin using them with mypy. Will they land in typing
or show up in typing_extensions
first?
from typing.
from typing.
@ilevkivskyi I was searching the wrong repos! Thanks.
from typing.
from typing.
@gvanrossum no changes needed in typing
(making Mapping
a protocol is optional). Many classes there, like typing.Iterable
already behave as protocols at runtime, as you can see, the PR I referenced is in typeshed.
from typing.
I'm not sure how you can say that Iterable
already behaves like a protocol. I tried this code:
class C:
def __iter__(self) -> Iterator[int]:
yield 0
for x in C(): # error: Iterable expected
print(x)
This complains that a C
instance is not an Iterable
.
from typing.
Iterable
behaves like a protocol at runtime (it has a custom isinstance
overload).
from typing.
from typing.
So the concerns are more about protocols that don't do that, e.g. Sequence or Mapping.
Yes, there are four classes Sequence
, Mapping
, MutableSequence
, and MutableMapping
.
Actually, I am thinking maybe we can leave them out from python/typeshed#1220 and consider them later. I have already heard several times that people want mypy to recognize existing runtime protocols (with Iterable
being an absolute winner) so that we could merge python/typeshed#1220 without these four classes soon.
from typing.
Hi, are there any updates on the remaining collections which are not yet protocols -- Sequence
, Mapping
, MutableSequence
, and MutableMapping
?
from typing.
from typing.
The plan is to keep them as they are.
What's the reasoning behind that? Even if the abc classes don't become protocols, it would be convenient to have Protocol
variants of these in typing or mypy_extensions, so that users who wish to treat these as protocols within their code don't have to write their own Protocol
classes for these common cases.
from typing.
What's the reasoning behind that?
We have no options now. The feature cut-off for Python 3.7 has passed long ago, and this festure would require a runtime change in collections.abc
.
Strong -1 on typing.Mapping
and collections.abc.Mapping
having different semantics, this will only confuse people.
We had a discussion over e-mail with @gvanrossum recently, we are both +0 on making these protocols (only +0 mostly because they are large, while protocols should be more compact), so we can consider this again in one year for Python 3.8.
from typing.
@JukkaL actually yes, I have seen someone on gitter recently confused by __contains__
.
from typing.
@jakebailey (MS Language Server representative)
from typing.
Also, currently
isinstance
works with them, so we'd have to support that in the future -- so these protocols would be "runtime" protocols. This brings the risk that the runtime and static views of subtyping become inconsistent because of minor signature differences.
@JukkaL I had the same concerns here.
from typing.
Related Issues (20)
- Out-of-date description of how to indicate positional-only parameters
- Typing spec should be clearer that type checkers are not expected to support PEP 3141 HOT 22
- Incorporate PEP 483 into the spec HOT 5
- Conformance tests: Add mechanism to allow errors on one of multiple lines HOT 2
- Conformance tests: Add mechanism to ignore errors
- Conformance tests: Align automated and manual scoring HOT 1
- Introduce a `Language` type to provide consistent language information of strings. HOT 5
- Spec: Version and platform checking underspecified HOT 6
- Support list comprehension for `TypeVarTuple` HOT 2
- Clarify the float/int/complex special case HOT 5
- Clarify the implications of subclassing Any HOT 1
- Allow multiple `TypeVarTuple` in Generic Classes when they are wrapped. HOT 5
- pytype conformance tests crash if no `python3.11` executable is in `PATH` HOT 5
- Conformance test: make dataclass_hash.py not rely on `typing.Hashable`? HOT 1
- Proposal: Add coerced type narrowing similar to 'cast' HOT 1
- Idenity Type HOT 2
- [spec] clarify assert_type behavior as asserting type equivalence
- [spec] better clarify the difference between a runtime type object and a static type HOT 1
- [spec] a protocol type should be assignable to object? HOT 1
- [spec] define or replace the term "concrete type" HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from typing.