Created
March 26, 2019 18:51
-
-
Save antonagestam/8412de81e3814167c853f28795dd6e0b to your computer and use it in GitHub Desktop.
Memory efficient and immutable DegreeAngle implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from __future__ import annotations | |
from collections import OrderedDict | |
from typing import Any | |
import weakref | |
class InstanceCache(type): | |
""" | |
Custom metaclass for DegreeAngle that reuses cached objects to optimize | |
memory consumption by storing weak references to them in an ordered dict. | |
""" | |
__cache: OrderedDict[float, weakref.ReferenceType] = OrderedDict() | |
__max_size = 100000 | |
def __call__(cls, *args: Any, **kwargs: Any) -> Any: | |
# make room for one more item in the cache | |
if len(InstanceCache.__cache) + 1 > InstanceCache.__max_size: | |
InstanceCache.__cache.popitem(last=False) | |
# update the angle argument with a normalized value | |
normalized = [DegreeAngle.normalize(args[0])] + list(args[1:]) | |
# evict cache if weakref is dead | |
if normalized[0] in InstanceCache.__cache: | |
obj = InstanceCache.__cache[normalized[0]]() | |
if obj is None: | |
del InstanceCache.__cache[normalized[0]] | |
# create a new object and a store a weakref to it in the cache | |
if normalized[0] not in InstanceCache.__cache: | |
obj = super(InstanceCache, cls).__call__(*normalized, **kwargs) | |
InstanceCache.__cache[normalized[0]] = weakref.ref(obj) | |
return InstanceCache.__cache[normalized[0]]() | |
class DegreeAngle(metaclass=InstanceCache): | |
""" | |
Normalizes angles to the 0-360 degree range and allows circle aware | |
subtraction. | |
""" | |
__slots__ = ('__value', '__weakref__', ) | |
__metaclass__ = InstanceCache | |
def __init__(self, value: float): | |
self.__value = value | |
@staticmethod | |
def normalize(value: float) -> float: | |
"""Normalize angular value to between 0 and 360 degrees.""" | |
normalized = value % 360 | |
if normalized < 0: | |
normalized += 360 | |
return normalized | |
@property | |
def value(self): | |
return self.__value | |
def __repr__(self): | |
return f'DegreeAngle({self.value})' | |
def __sub__(self, other: DegreeAngle) -> DegreeAngle: | |
d = abs(self.value - other.value) | |
if d >= 180: | |
return DegreeAngle(360 - d) | |
return DegreeAngle(d) | |
def __eq__(self, other: object) -> bool: | |
if not isinstance(other, DegreeAngle): | |
return NotImplemented | |
return self.value == other.value | |
def __hash__(self): | |
return hash(self.value) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment