diff --git a/gpiozero/compat.py b/gpiozero/compat.py index 8387038..31e5f35 100644 --- a/gpiozero/compat.py +++ b/gpiozero/compat.py @@ -9,6 +9,9 @@ from __future__ import ( str = type('') import cmath +import collections +import operator +import functools # Back-ported from python 3.5; see @@ -51,3 +54,30 @@ def median(data): i = n // 2 return (data[i - 1] + data[i]) / 2 + +# Copied from the MIT-licensed https://github.com/slezica/python-frozendict +class frozendict(collections.Mapping): + def __init__(self, *args, **kwargs): + self.__dict = dict(*args, **kwargs) + self.__hash = None + + def __getitem__(self, key): + return self.__dict[key] + + def copy(self, **add_or_replace): + return frozendict(self, **add_or_replace) + + def __iter__(self): + return iter(self.__dict) + + def __len__(self): + return len(self.__dict) + + def __repr__(self): + return '' % repr(self.__dict) + + def __hash__(self): + if self.__hash is None: + hashes = map(hash, self.items()) + self.__hash = functools.reduce(operator.xor, hashes, 0) + return self.__hash diff --git a/gpiozero/devices.py b/gpiozero/devices.py index a56ab89..df78557 100644 --- a/gpiozero/devices.py +++ b/gpiozero/devices.py @@ -28,6 +28,7 @@ from .exc import ( GPIOPinInUse, GPIODeviceClosed, ) +from .compat import frozendict # Get a pin implementation to use as the default; we prefer RPi.GPIO's here # as it supports PWM, and all Pi revisions. If no third-party libraries are @@ -263,7 +264,7 @@ class CompositeDevice(Device): """ def __init__(self, *args, **kwargs): self._all = () - self._named = {} + self._named = frozendict({}) self._namedtuple = None self._order = kwargs.pop('_order', None) if self._order is None: @@ -279,7 +280,7 @@ class CompositeDevice(Device): for dev in self._all: if not isinstance(dev, Device): raise CompositeDeviceBadDevice("%s doesn't inherit from Device" % dev) - self._named = kwargs + self._named = frozendict(kwargs) self._namedtuple = namedtuple('%sValue' % self.__class__.__name__, chain( (str(i) for i in range(len(args))), self._order), rename=True) @@ -287,7 +288,7 @@ class CompositeDevice(Device): def __getattr__(self, name): # if _named doesn't exist yet, pretend it's an empty dict if name == '_named': - return {} + return frozendict({}) try: return self._named[name] except KeyError: