Standard Protocol Classes¶
Introduction¶
As static type analysis in Python evolved, structural subtyping (aka., static
duck-typing) was introduced by PEP 544. This PEP defined
typing.Protocol
classes. In addition to being a special form
recognized by type checkers to support structural subtyping, protocol classes
are also instances of abc.ABCMeta
, which means that they can be
used as abstract bases for classes which implement their protocols via nominal
subtyping. Thus, it makes sense to support them within the context of the
standard behaviors: concealment and immutability.
Because protocol classes are produced by a different metaclass than
type
, they cannot be produced by Class
or Dataclass
.
Instead, ProtocolClass
and ProtocolDataclass
are provided as
metaclasses which are compatible with typing.Protocol
and its
descendants. Similarly, the Protocol
and DataclassProtocol
classes can
be used as bases, which are produced from metaclasses that are compatible with
type( Protocol )
.
>>> import abc
>>> import urllib.parse
>>> import typing_extensions as typx
>>> import classcore.standard as ccstd
For example, one can make a protocol class via inheritance:
>>> class FileAccessor( ccstd.Protocol, typx.Protocol ):
... urlparts: urllib.parse.ParseResult
... @abc.abstractmethod
... async def acquire( self ) -> bytes: raise NotImplementedError
... @abc.abstractmethod
... async def update( self, content: bytes ) -> None: raise NotImplementedError
This protocol cannot be instantiated or mutated:
>>> accessor = FileAccessor( )
Traceback (most recent call last):
...
TypeError: Can't instantiate abstract class FileAccessor with abstract methods acquire, update
>>> FileAccessor.urlparts = urllib.parse.urlparse( '' )
Traceback (most recent call last):
...
classcore.exceptions.AttributeImmutability: Could not assign or delete attribute 'urlparts' on class ...
However, a concrete subtype of it can be instantiated but is still concealed and immutable:
>>> from os import PathLike
>>> class AiofilesFileAccessor( FileAccessor ):
... def __init__( self, location: bytes | str | PathLike ) -> None:
... if isinstance( location, bytes ):
... location_ = location.decode( )
... elif isinstance( location, PathLike ):
... location_ = str( location )
... else: location_ = location
... self.location = urllib.parse.urlparse( location_ )
... async def acquire( self ) -> bytes: return b'' # TODO: implement
... async def update( self, content: bytes ) -> None: pass # TODO: implement
>>> AiofilesFileAccessor.location = urllib.parse.urlparse( '' )
Traceback (most recent call last):
...
classcore.exceptions.AttributeImmutability: Could not assign or delete attribute 'location' on class ...
>>> dir( AiofilesFileAccessor )
['acquire', 'update']
Likewise, instances thereof are also concealed and immutable:
>>> accessor = AiofilesFileAccessor( 'file:///foo.txt' )
>>> accessor.location
ParseResult(scheme='file', netloc='', path='/foo.txt', params='', query='', fragment='')
>>> accessor.location = urllib.parse.urlparse( '/bar.txt' )
Traceback (most recent call last):
...
classcore.exceptions.AttributeImmutability: Could not assign or delete attribute 'location' on instance of class ...
>>> dir( accessor )
['acquire', 'location', 'update']
Protocol Dataclasses¶
Todo
Contents