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