114
114
115
115
See our `official docs <https://classes.readthedocs.io>`_ to learn more!
116
116
"""
117
-
118
- from abc import get_cache_token
119
117
from functools import _find_impl # type: ignore # noqa: WPS450
120
- from types import MethodType
121
118
from typing import ( # noqa: WPS235
122
119
TYPE_CHECKING ,
123
120
Callable ,
124
121
Dict ,
125
122
Generic ,
126
- NoReturn ,
127
123
Optional ,
128
124
Type ,
129
125
TypeVar ,
134
130
135
131
from typing_extensions import TypeGuard , final
136
132
133
+ from classes ._registry import (
134
+ TypeRegistry ,
135
+ choose_registry ,
136
+ default_implementation ,
137
+ )
138
+
137
139
_InstanceType = TypeVar ('_InstanceType' )
138
140
_SignatureType = TypeVar ('_SignatureType' , bound = Callable )
139
141
_AssociatedType = TypeVar ('_AssociatedType' )
@@ -305,12 +307,17 @@ class _TypeClass( # noqa: WPS214
305
307
"""
306
308
307
309
__slots__ = (
310
+ # Str:
308
311
'_signature' ,
309
312
'_associated_type' ,
313
+
314
+ # Registry:
315
+ '_concretes' ,
310
316
'_instances' ,
311
317
'_protocols' ,
318
+
319
+ # Cache:
312
320
'_dispatch_cache' ,
313
- '_cache_token' ,
314
321
)
315
322
316
323
_dispatch_cache : Dict [type , Callable ]
@@ -349,16 +356,17 @@ def __init__(
349
356
The only exception is the first argument: it is polymorfic.
350
357
351
358
"""
352
- self ._instances : Dict [type , Callable ] = {}
353
- self ._protocols : Dict [type , Callable ] = {}
354
-
355
359
# We need this for `repr`:
356
360
self ._signature = signature
357
361
self ._associated_type = associated_type
358
362
363
+ # Registries:
364
+ self ._concretes : TypeRegistry = {}
365
+ self ._instances : TypeRegistry = {}
366
+ self ._protocols : TypeRegistry = {}
367
+
359
368
# Cache parts:
360
369
self ._dispatch_cache = WeakKeyDictionary () # type: ignore
361
- self ._cache_token = None
362
370
363
371
def __call__ (
364
372
self ,
@@ -410,7 +418,16 @@ def __call__(
410
418
And all typeclasses that match ``Callable[[int, int], int]`` signature
411
419
will typecheck.
412
420
"""
413
- self ._control_abc_cache ()
421
+ # At first, we try all our concrete types,
422
+ # we don't cache it, because we cannot.
423
+ # We only have runtime type info: `type([1]) == type(['a'])`.
424
+ # It might be slow!
425
+ # Don't add concrete types unless
426
+ # you are absolutely know what you are doing.
427
+ impl = self ._dispatch_concrete (instance )
428
+ if impl is not None :
429
+ return impl (instance , * args , ** kwargs )
430
+
414
431
instance_type = type (instance )
415
432
416
433
try :
@@ -419,7 +436,7 @@ def __call__(
419
436
impl = self ._dispatch (
420
437
instance ,
421
438
instance_type ,
422
- ) or self . _default_implementation
439
+ ) or default_implementation
423
440
self ._dispatch_cache [instance_type ] = impl
424
441
return impl (instance , * args , ** kwargs )
425
442
@@ -481,16 +498,24 @@ def supports(
481
498
482
499
See also: https://www.python.org/dev/peps/pep-0647
483
500
"""
484
- self ._control_abc_cache ()
485
-
501
+ # Here we first check that instance is already in the cache
502
+ # and only then we check concrete types.
503
+ # Why?
504
+ # Because if some type is already in the cache,
505
+ # it means that it is not concrete.
506
+ # So, this is simply faster.
486
507
instance_type = type (instance )
487
508
if instance_type in self ._dispatch_cache :
488
509
return True
489
510
490
- # This only happens when we don't have a cache in place:
511
+ # We never cache concrete types.
512
+ if self ._dispatch_concrete (instance ) is not None :
513
+ return True
514
+
515
+ # This only happens when we don't have a cache in place
516
+ # and this is not a concrete generic:
491
517
impl = self ._dispatch (instance , instance_type )
492
518
if impl is None :
493
- self ._dispatch_cache [instance_type ] = self ._default_implementation
494
519
return False
495
520
496
521
self ._dispatch_cache [instance_type ] = impl
@@ -503,14 +528,36 @@ def instance(
503
528
# TODO: at one point I would like to remove `is_protocol`
504
529
# and make this function decide whether this type is protocol or not.
505
530
is_protocol : bool = False ,
531
+ delegate : Optional [type ] = None ,
506
532
) -> '_TypeClassInstanceDef[_NewInstanceType, _TypeClassType]' :
507
533
"""
508
534
We use this method to store implementation for each specific type.
509
535
510
- The only setting we provide is ``is_protocol`` which is required
511
- when passing protocols. See our ``mypy`` plugin for that.
536
+ Args:
537
+ is_protocol - required when passing protocols.
538
+ delegate - required when using concrete generics like ``List[str]``.
539
+
540
+ Returns:
541
+ Decorator for instance handler.
542
+
543
+ .. note::
544
+
545
+ ``is_protocol`` and ``delegate`` are mutually exclusive.
546
+
547
+ We don't use ``@overload`` decorator here
548
+ (which makes our ``mypy`` plugin even more complex)
549
+ because ``@overload`` functions do not
550
+ work well with ``ctx.api.fail`` inside the plugin.
551
+ They start to try other overloads, which produces wrong results.
512
552
"""
513
- typ = type_argument or type (None ) # `None` is a special case
553
+ # This might seem like a strange line at first, let's dig into it:
554
+ #
555
+ # First, if `delegate` is passed, then we use delegate, not a real type.
556
+ # We use delegates for concrete generics.
557
+ # Then, we have a regular `type_argument`. It is used for most types.
558
+ # Lastly, we have `type(None)` to handle cases
559
+ # when we want to register `None` as a type / singleton value.
560
+ typ = delegate or type_argument or type (None )
514
561
515
562
# That's how we check for generics,
516
563
# generics that look like `List[int]` or `set[T]` will fail this check,
@@ -519,34 +566,20 @@ def instance(
519
566
isinstance (object (), typ )
520
567
521
568
def decorator (implementation ):
522
- container = self ._protocols if is_protocol else self ._instances
569
+ container = choose_registry (
570
+ typ = typ ,
571
+ is_protocol = is_protocol ,
572
+ delegate = delegate ,
573
+ concretes = self ._concretes ,
574
+ instances = self ._instances ,
575
+ protocols = self ._protocols ,
576
+ )
523
577
container [typ ] = implementation
524
578
525
- if isinstance (getattr (typ , '__instancecheck__' , None ), MethodType ):
526
- # This means that this type has `__instancecheck__` defined,
527
- # which allows dynamic checks of what `isinstance` of this type.
528
- # That's why we also treat this type as a protocol.
529
- self ._protocols [typ ] = implementation
530
-
531
- if self ._cache_token is None : # pragma: no cover
532
- if getattr (typ , '__abstractmethods__' , None ):
533
- self ._cache_token = get_cache_token ()
534
-
535
579
self ._dispatch_cache .clear ()
536
580
return implementation
537
- return decorator
538
581
539
- def _control_abc_cache (self ) -> None :
540
- """
541
- Required to drop cache if ``abc`` type got new subtypes in runtime.
542
-
543
- Copied from ``cpython``.
544
- """
545
- if self ._cache_token is not None :
546
- current_token = get_cache_token ()
547
- if self ._cache_token != current_token :
548
- self ._dispatch_cache .clear ()
549
- self ._cache_token = current_token
582
+ return decorator
550
583
551
584
def _dispatch (self , instance , instance_type : type ) -> Optional [Callable ]:
552
585
"""
@@ -567,13 +600,11 @@ def _dispatch(self, instance, instance_type: type) -> Optional[Callable]:
567
600
568
601
return _find_impl (instance_type , self ._instances )
569
602
570
- def _default_implementation (self , instance , * args , ** kwargs ) -> NoReturn :
571
- """By default raises an exception."""
572
- raise NotImplementedError (
573
- 'Missing matched typeclass instance for type: {0}' .format (
574
- type (instance ).__qualname__ ,
575
- ),
576
- )
603
+ def _dispatch_concrete (self , instance ) -> Optional [Callable ]:
604
+ for concrete , callback in self ._concretes .items ():
605
+ if isinstance (instance , concrete ):
606
+ return callback
607
+ return None
577
608
578
609
579
610
if TYPE_CHECKING :
0 commit comments