2727
2828from random import shuffle
2929from threading import RLock
30+ from typing import Generic , List , Optional , Set , TypeVar , cast
3031
3132from nussschale .util .locks import mutex
3233
3334
34- class MultiDeck :
35+ T = TypeVar ("T" )
36+ U = TypeVar ("U" )
37+
38+
39+ class MultiDeck (Generic [T , U ]):
3540 """One (refilling) deck used to make selection seem more 'random'."""
3641
37- def __init__ (self , deck ) :
42+ def __init__ (self , deck : List [ T ]) -> None :
3843 """Constructor.
3944
4045 Args:
41- deck (list) : A list of objects having an 'id' property. The deck
46+ deck: A list of objects having an 'id' property. The deck
4247 should be safe for random access at any given time.
4348 """
4449 self ._lock = RLock ()
45- self ._backing = deck
46- self ._queue = []
47- self ._contained = set ()
50+ self ._backing = deck # type: List[T]
51+ self ._queue = [] # type: List[T]
52+ self ._contained = set () # type: Set[U]
53+
54+ @staticmethod
55+ def _id_of (o : T ) -> U :
56+ """Fetches the ID of the given object.
57+
58+ This method only exists to enable type checking to work while
59+ structural subtyping is not supported.
60+
61+ Args:
62+ o: The object to get the ID from.
63+
64+ Returns:
65+ The ID of the object.
66+ """
67+ id = o .id # type: ignore
68+ return cast (U , id )
4869
4970 @mutex
50- def request (self , banned_ids ) :
71+ def request (self , banned_ids : Set [ U ]) -> Optional [ T ] :
5172 """Requests a card from the multideck.
5273
5374 Args:
54- banned_ids (set) : A set of IDs that may not be chosen.
75+ banned_ids: A set of IDs that may not be chosen.
5576
5677 Returns:
57- obj: The object that was selected. This might be None if the
58- request can't be fulfilled.
78+ The object that was selected. This might be None if the
79+ request can't be fulfilled.
5980
6081 Contract:
6182 This method locks the deck's lock.
@@ -65,8 +86,8 @@ def request(self, banned_ids):
6586 # Try to find a viable object
6687 while ptr < len (self ._queue ):
6788 obj = self ._queue [ptr ]
68- if obj . id not in banned_ids :
69- self ._contained .remove (obj . id )
89+ if MultiDeck . _id_of ( obj ) not in banned_ids :
90+ self ._contained .remove (MultiDeck . _id_of ( obj ) )
7091 del self ._queue [ptr ]
7192 return obj
7293 ptr += 1
@@ -78,18 +99,18 @@ def request(self, banned_ids):
7899 # by at least factor 2.
79100 pool = []
80101 for obj in self ._backing :
81- if obj . id not in self ._contained :
102+ if MultiDeck . _id_of ( obj ) not in self ._contained :
82103 pool .append (obj )
83104 shuffle (pool )
84105 for obj in pool :
85- self ._contained .add (obj . id )
106+ self ._contained .add (MultiDeck . _id_of ( obj ) )
86107 self ._queue .append (obj )
87108
88109 # Try to find a viable object again
89110 while ptr < len (self ._queue ):
90111 obj = self ._queue [ptr ]
91- if obj . id not in banned_ids :
92- self ._contained .remove (obj . id )
112+ if MultiDeck . _id_of ( obj ) not in banned_ids :
113+ self ._contained .remove (MultiDeck . _id_of ( obj ) )
93114 del self ._queue [ptr ]
94115 return obj
95116 ptr += 1
0 commit comments