1
|
|
|
"""Implementation of the Memoize Software Design Pattern. |
2
|
|
|
|
3
|
|
|
Memoize is implemented using an Object Pool which is queried by a key which is |
4
|
|
|
the result of computing a hash given runtime arguments. |
5
|
|
|
|
6
|
|
|
""" |
7
|
|
|
import types |
8
|
|
|
from typing import Any, Callable, Dict, Generic, Optional, TypeVar, Union |
9
|
|
|
|
10
|
|
|
__all__ = ['ObjectsPool'] |
11
|
|
|
|
12
|
|
|
|
13
|
|
|
DictKey = Union[int, str] |
14
|
|
|
ObjectType = TypeVar('ObjectType') |
15
|
|
|
|
16
|
|
|
RuntimeBuildHashCallable = Callable[..., Union[int, str]] |
17
|
|
|
|
18
|
|
|
|
19
|
|
|
def adapt_build_hash(a_callable: RuntimeBuildHashCallable): |
20
|
|
|
def build_hash(_self: ObjectType, *args, **kwargs): |
21
|
|
|
return a_callable(*args, **kwargs) |
22
|
|
|
|
23
|
|
|
return build_hash |
24
|
|
|
|
25
|
|
|
|
26
|
|
|
class ObjectsPool(Generic[ObjectType]): |
27
|
|
|
"""Cache objects and allow to query (the pool) using runtime arguments. |
28
|
|
|
|
29
|
|
|
Instances of the ObjectsPool class implement the Object Pool Software Design |
30
|
|
|
Creational Pattern. |
31
|
|
|
|
32
|
|
|
Whenever an object is requested, it is checked whether it exists in the |
33
|
|
|
pool by using the runtimetime arguments to query a python dictionary. |
34
|
|
|
If it exists, a reference is returned, otherwise a new object is |
35
|
|
|
constructed (given the provided callback) and its reference is returned. |
36
|
|
|
|
37
|
|
|
Example: |
38
|
|
|
>>> from software_patterns import ObjectsPool |
39
|
|
|
>>> class ClientClass: |
40
|
|
|
... def __init__(self, a: int, b: int): |
41
|
|
|
... pass |
42
|
|
|
|
43
|
|
|
>>> object_pool = ObjectsPool[ClientClass](ClientClass) |
44
|
|
|
|
45
|
|
|
>>> obj1 = object_pool.get_object(1, 2) |
46
|
|
|
>>> obj2 = object_pool.get_object(1, 3) |
47
|
|
|
>>> obj3 = object_pool.get_object(1, 2) |
48
|
|
|
|
49
|
|
|
>>> id(obj1) == id(obj3) |
50
|
|
|
True |
51
|
|
|
|
52
|
|
|
>>> len(object_pool._objects) |
53
|
|
|
2 |
54
|
|
|
|
55
|
|
|
Args: |
56
|
|
|
callback (Callable[..., ObjectType]): constructs objects given arguments |
57
|
|
|
hash_callback (Optional[RuntimeBuildHashCallable], optional): option to |
58
|
|
|
overide the default hash key computer. Defaults to None. |
59
|
|
|
Returns: |
60
|
|
|
[type]: [description] |
61
|
|
|
""" |
62
|
|
|
|
63
|
|
|
_objects: Dict[DictKey, ObjectType] |
64
|
|
|
|
65
|
|
|
user_supplied_callback: Dict[bool, Callable] = { |
66
|
|
|
True: lambda callback: callback, |
67
|
|
|
False: lambda callback: ObjectsPool.__build_hash, |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
def __init__( |
71
|
|
|
self, |
72
|
|
|
callback: Callable[..., ObjectType], |
73
|
|
|
hash_callback: Optional[RuntimeBuildHashCallable] = None, |
74
|
|
|
): |
75
|
|
|
self.constructor = callback |
76
|
|
|
build_hash_callback = self.user_supplied_callback[callable(hash_callback)]( |
77
|
|
|
hash_callback |
78
|
|
|
) |
79
|
|
|
self._build_hash = types.MethodType(adapt_build_hash(build_hash_callback), self) |
80
|
|
|
self._objects = {} |
81
|
|
|
|
82
|
|
|
@staticmethod |
83
|
|
|
def __build_hash(*args: Any, **kwargs: Any) -> int: |
84
|
|
|
r"""Construct a hash out of the input \*args and \*\*kwargs.""" |
85
|
|
|
return hash( |
86
|
|
|
'-'.join( |
87
|
|
|
[str(_) for _ in args] |
88
|
|
|
+ [f'{key}={str(value)}' for key, value in kwargs.items()] |
89
|
|
|
) |
90
|
|
|
) |
91
|
|
|
|
92
|
|
|
def get_object(self, *args: Any, **kwargs: Any) -> ObjectType: |
93
|
|
|
r"""Request an object from the pool. |
94
|
|
|
|
95
|
|
|
Get or create an object given the input arguments, which are used to |
96
|
|
|
create a unique hash key. The key is used to query a python dictionary |
97
|
|
|
and determine whether the object request refers to a cached object. |
98
|
|
|
|
99
|
|
|
Returns: |
100
|
|
|
object (ObjectType): the reference to the object that corresponds to the input |
101
|
|
|
arguments, regardless of whether it was found in the pool or not |
102
|
|
|
""" |
103
|
|
|
key = self._build_hash(*args, **kwargs) |
104
|
|
|
if key not in self._objects: |
105
|
|
|
self._objects[key] = self.constructor(*args, **kwargs) |
106
|
|
|
return self._objects[key] |
107
|
|
|
|