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 conrete 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
@@ -541,35 +566,21 @@ def instance(
541
566
isinstance (object (), typ )
542
567
543
568
def decorator (implementation ):
544
- 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
+ )
545
577
container [typ ] = implementation
546
578
547
- if isinstance (getattr (typ , '__instancecheck__' , None ), MethodType ):
548
- # This means that this type has `__instancecheck__` defined,
549
- # which allows dynamic checks of what `isinstance` of this type.
550
- # That's why we also treat this type as a protocol.
551
- self ._protocols [typ ] = implementation
552
-
553
- if self ._cache_token is None : # pragma: no cover
554
- if getattr (typ , '__abstractmethods__' , None ):
555
- self ._cache_token = get_cache_token ()
556
579
self ._dispatch_cache .clear ()
557
580
return implementation
558
581
559
582
return decorator
560
583
561
- def _control_abc_cache (self ) -> None :
562
- """
563
- Required to drop cache if ``abc`` type got new subtypes in runtime.
564
-
565
- Copied from ``cpython``.
566
- """
567
- if self ._cache_token is not None :
568
- current_token = get_cache_token ()
569
- if self ._cache_token != current_token :
570
- self ._dispatch_cache .clear ()
571
- self ._cache_token = current_token
572
-
573
584
def _dispatch (self , instance , instance_type : type ) -> Optional [Callable ]:
574
585
"""
575
586
Dispatches a function by its type.
@@ -589,13 +600,11 @@ def _dispatch(self, instance, instance_type: type) -> Optional[Callable]:
589
600
590
601
return _find_impl (instance_type , self ._instances )
591
602
592
- def _default_implementation (self , instance , * args , ** kwargs ) -> NoReturn :
593
- """By default raises an exception."""
594
- raise NotImplementedError (
595
- 'Missing matched typeclass instance for type: {0}' .format (
596
- type (instance ).__qualname__ ,
597
- ),
598
- )
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
599
608
600
609
601
610
if TYPE_CHECKING :
0 commit comments