Skip to content

Instantly share code, notes, and snippets.

@antonagestam
Created March 26, 2019 18:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save antonagestam/8412de81e3814167c853f28795dd6e0b to your computer and use it in GitHub Desktop.
Save antonagestam/8412de81e3814167c853f28795dd6e0b to your computer and use it in GitHub Desktop.
Memory efficient and immutable DegreeAngle implementation
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