diff --git a/pyqtgraph/ordereddict.py b/pyqtgraph/ordereddict.py new file mode 100644 index 00000000..5b0303f5 --- /dev/null +++ b/pyqtgraph/ordereddict.py @@ -0,0 +1,127 @@ +# Copyright (c) 2009 Raymond Hettinger +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. + +from UserDict import DictMixin + +class OrderedDict(dict, DictMixin): + + def __init__(self, *args, **kwds): + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + try: + self.__end + except AttributeError: + self.clear() + self.update(*args, **kwds) + + def clear(self): + self.__end = end = [] + end += [None, end, end] # sentinel node for doubly linked list + self.__map = {} # key --> [key, prev, next] + dict.clear(self) + + def __setitem__(self, key, value): + if key not in self: + end = self.__end + curr = end[1] + curr[2] = end[1] = self.__map[key] = [key, curr, end] + dict.__setitem__(self, key, value) + + def __delitem__(self, key): + dict.__delitem__(self, key) + key, prev, next = self.__map.pop(key) + prev[2] = next + next[1] = prev + + def __iter__(self): + end = self.__end + curr = end[2] + while curr is not end: + yield curr[0] + curr = curr[2] + + def __reversed__(self): + end = self.__end + curr = end[1] + while curr is not end: + yield curr[0] + curr = curr[1] + + def popitem(self, last=True): + if not self: + raise KeyError('dictionary is empty') + if last: + key = reversed(self).next() + else: + key = iter(self).next() + value = self.pop(key) + return key, value + + def __reduce__(self): + items = [[k, self[k]] for k in self] + tmp = self.__map, self.__end + del self.__map, self.__end + inst_dict = vars(self).copy() + self.__map, self.__end = tmp + if inst_dict: + return (self.__class__, (items,), inst_dict) + return self.__class__, (items,) + + def keys(self): + return list(self) + + setdefault = DictMixin.setdefault + update = DictMixin.update + pop = DictMixin.pop + values = DictMixin.values + items = DictMixin.items + iterkeys = DictMixin.iterkeys + itervalues = DictMixin.itervalues + iteritems = DictMixin.iteritems + + def __repr__(self): + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, self.items()) + + def copy(self): + return self.__class__(self) + + @classmethod + def fromkeys(cls, iterable, value=None): + d = cls() + for key in iterable: + d[key] = value + return d + + def __eq__(self, other): + if isinstance(other, OrderedDict): + if len(self) != len(other): + return False + for p, q in zip(self.items(), other.items()): + if p != q: + return False + return True + return dict.__eq__(self, other) + + def __ne__(self, other): + return not self == other diff --git a/pyqtgraph/pgcollections.py b/pyqtgraph/pgcollections.py index b0198526..4a225915 100644 --- a/pyqtgraph/pgcollections.py +++ b/pyqtgraph/pgcollections.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """ -advancedTypes.py - Basic data structures not included with python +advancedTypes.py - Basic data structures not included with python Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more infomation. @@ -15,76 +15,10 @@ import threading, sys, copy, collections try: from collections import OrderedDict -except: - # Deprecated; this class is now present in Python 2.7 as collections.OrderedDict +except ImportError: # Only keeping this around for python2.6 support. - class OrderedDict(dict): - """extends dict so that elements are iterated in the order that they were added. - Since this class can not be instantiated with regular dict notation, it instead uses - a list of tuples: - od = OrderedDict([(key1, value1), (key2, value2), ...]) - items set using __setattr__ are added to the end of the key list. - """ - - def __init__(self, data=None): - self.order = [] - if data is not None: - for i in data: - self[i[0]] = i[1] - - def __setitem__(self, k, v): - if not self.has_key(k): - self.order.append(k) - dict.__setitem__(self, k, v) - - def __delitem__(self, k): - self.order.remove(k) - dict.__delitem__(self, k) + from ordereddict import OrderedDict - def keys(self): - return self.order[:] - - def items(self): - it = [] - for k in self.keys(): - it.append((k, self[k])) - return it - - def values(self): - return [self[k] for k in self.order] - - def remove(self, key): - del self[key] - #self.order.remove(key) - - def __iter__(self): - for k in self.order: - yield k - - def update(self, data): - """Works like dict.update, but accepts list-of-tuples as well as dict.""" - if isinstance(data, dict): - for k, v in data.iteritems(): - self[k] = v - else: - for k,v in data: - self[k] = v - - def copy(self): - return OrderedDict(self.items()) - - def itervalues(self): - for k in self.order: - yield self[k] - - def iteritems(self): - for k in self.order: - yield (k, self[k]) - - def __deepcopy__(self, memo): - return OrderedDict([(k, copy.deepcopy(v, memo)) for k, v in self.iteritems()]) - - class ReverseDict(dict): """extends dict so that reverse lookups are possible by requesting the key as a list of length 1: @@ -101,7 +35,7 @@ class ReverseDict(dict): for k in data: self.reverse[data[k]] = k dict.__init__(self, data) - + def __getitem__(self, item): if type(item) is list: return self.reverse[item[0]] @@ -114,8 +48,8 @@ class ReverseDict(dict): def __deepcopy__(self, memo): raise Exception("deepcopy not implemented") - - + + class BiDict(dict): """extends dict so that reverse lookups are possible by adding each reverse combination to the dict. This only works if all values and keys are unique.""" @@ -125,11 +59,11 @@ class BiDict(dict): dict.__init__(self) for k in data: self[data[k]] = k - + def __setitem__(self, item, value): dict.__setitem__(self, item, value) dict.__setitem__(self, value, item) - + def __deepcopy__(self, memo): raise Exception("deepcopy not implemented") @@ -138,7 +72,7 @@ class ThreadsafeDict(dict): Also adds lock/unlock functions for extended exclusive operations Converts all sub-dicts and lists to threadsafe as well. """ - + def __init__(self, *args, **kwargs): self.mutex = threading.RLock() dict.__init__(self, *args, **kwargs) @@ -162,7 +96,7 @@ class ThreadsafeDict(dict): dict.__setitem__(self, attr, val) finally: self.unlock() - + def __contains__(self, attr): self.lock() try: @@ -188,19 +122,19 @@ class ThreadsafeDict(dict): def lock(self): self.mutex.acquire() - + def unlock(self): self.mutex.release() def __deepcopy__(self, memo): raise Exception("deepcopy not implemented") - + class ThreadsafeList(list): """Extends list so that getitem, setitem, and contains are all thread-safe. Also adds lock/unlock functions for extended exclusive operations Converts all sub-lists and dicts to threadsafe as well. """ - + def __init__(self, *args, **kwargs): self.mutex = threading.RLock() list.__init__(self, *args, **kwargs) @@ -222,7 +156,7 @@ class ThreadsafeList(list): list.__setitem__(self, attr, val) finally: self.unlock() - + def __contains__(self, attr): self.lock() try: @@ -238,17 +172,17 @@ class ThreadsafeList(list): finally: self.unlock() return val - + def lock(self): self.mutex.acquire() - + def unlock(self): self.mutex.release() def __deepcopy__(self, memo): raise Exception("deepcopy not implemented") - - + + def makeThreadsafe(obj): if type(obj) is dict: return ThreadsafeDict(obj) @@ -258,8 +192,8 @@ def makeThreadsafe(obj): return obj else: raise Exception("Not sure how to make object of type %s thread-safe" % str(type(obj))) - - + + class Locker(object): def __init__(self, lock): self.lock = lock @@ -283,10 +217,10 @@ class CaselessDict(OrderedDict): self[k] = args[0][k] else: raise Exception("CaselessDict may only be instantiated with a single dict.") - + #def keys(self): #return self.keyMap.values() - + def __setitem__(self, key, val): kl = key.lower() if kl in self.keyMap: @@ -294,30 +228,30 @@ class CaselessDict(OrderedDict): else: OrderedDict.__setitem__(self, key, val) self.keyMap[kl] = key - + def __getitem__(self, key): kl = key.lower() if kl not in self.keyMap: raise KeyError(key) return OrderedDict.__getitem__(self, self.keyMap[kl]) - + def __contains__(self, key): return key.lower() in self.keyMap - + def update(self, d): for k, v in d.iteritems(): self[k] = v - + def copy(self): return CaselessDict(OrderedDict.copy(self)) - + def __delitem__(self, key): kl = key.lower() if kl not in self.keyMap: raise KeyError(key) OrderedDict.__delitem__(self, self.keyMap[kl]) del self.keyMap[kl] - + def __deepcopy__(self, memo): raise Exception("deepcopy not implemented") @@ -329,34 +263,34 @@ class CaselessDict(OrderedDict): class ProtectedDict(dict): """ - A class allowing read-only 'view' of a dict. + A class allowing read-only 'view' of a dict. The object can be treated like a normal dict, but will never modify the original dict it points to. Any values accessed from the dict will also be read-only. """ def __init__(self, data): self._data_ = data - + ## List of methods to directly wrap from _data_ wrapMethods = ['_cmp_', '__contains__', '__eq__', '__format__', '__ge__', '__gt__', '__le__', '__len__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__repr__', '__str__', 'count', 'has_key', 'iterkeys', 'keys', ] - + ## List of methods which wrap from _data_ but return protected results protectMethods = ['__getitem__', '__iter__', 'get', 'items', 'values'] - + ## List of methods to disable disableMethods = ['__delitem__', '__setitem__', 'clear', 'pop', 'popitem', 'setdefault', 'update'] - - - ## Template methods + + + # # Template methods def wrapMethod(methodName): return lambda self, *a, **k: getattr(self._data_, methodName)(*a, **k) - + def protectMethod(methodName): return lambda self, *a, **k: protect(getattr(self._data_, methodName)(*a, **k)) - + def error(self, *args, **kargs): raise Exception("Can not modify read-only list.") - - + + ## Directly (and explicitly) wrap some methods from _data_ ## Many of these methods can not be intercepted using __getattribute__, so they ## must be implemented explicitly @@ -371,33 +305,33 @@ class ProtectedDict(dict): for methodName in disableMethods: locals()[methodName] = error - + ## Add a few extra methods. def copy(self): raise Exception("It is not safe to copy protected dicts! (instead try deepcopy, but be careful.)") - + def itervalues(self): for v in self._data_.itervalues(): yield protect(v) - + def iteritems(self): for k, v in self._data_.iteritems(): yield (k, protect(v)) - + def deepcopy(self): return copy.deepcopy(self._data_) - + def __deepcopy__(self, memo): return copy.deepcopy(self._data_, memo) - + class ProtectedList(collections.Sequence): """ - A class allowing read-only 'view' of a list or dict. + A class allowing read-only 'view' of a list or dict. The object can be treated like a normal list, but will never modify the original list it points to. Any values accessed from the list will also be read-only. - + Note: It would be nice if we could inherit from list or tuple so that isinstance checks would work. However, doing this causes tuple(obj) to return unprotected results (importantly, this means unpacking into function arguments will also fail) @@ -405,28 +339,28 @@ class ProtectedList(collections.Sequence): def __init__(self, data): self._data_ = data #self.__mro__ = (ProtectedList, object) - + ## List of methods to directly wrap from _data_ wrapMethods = ['__contains__', '__eq__', '__format__', '__ge__', '__gt__', '__le__', '__len__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__repr__', '__str__', 'count', 'index'] - + ## List of methods which wrap from _data_ but return protected results protectMethods = ['__getitem__', '__getslice__', '__mul__', '__reversed__', '__rmul__'] - + ## List of methods to disable disableMethods = ['__delitem__', '__delslice__', '__iadd__', '__imul__', '__setitem__', '__setslice__', 'append', 'extend', 'insert', 'pop', 'remove', 'reverse', 'sort'] - - - ## Template methods + + + # # Template methods def wrapMethod(methodName): return lambda self, *a, **k: getattr(self._data_, methodName)(*a, **k) - + def protectMethod(methodName): return lambda self, *a, **k: protect(getattr(self._data_, methodName)(*a, **k)) - + def error(self, *args, **kargs): raise Exception("Can not modify read-only list.") - - + + ## Directly (and explicitly) wrap some methods from _data_ ## Many of these methods can not be intercepted using __getattribute__, so they ## must be implemented explicitly @@ -441,13 +375,13 @@ class ProtectedList(collections.Sequence): for methodName in disableMethods: locals()[methodName] = error - + ## Add a few extra methods. def __iter__(self): for item in self._data_: yield protect(item) - - + + def __add__(self, op): if isinstance(op, ProtectedList): return protect(self._data_.__add__(op._data_)) @@ -455,7 +389,7 @@ class ProtectedList(collections.Sequence): return protect(self._data_.__add__(op)) else: raise TypeError("Argument must be a list.") - + def __radd__(self, op): if isinstance(op, ProtectedList): return protect(op._data_.__add__(self._data_)) @@ -463,13 +397,13 @@ class ProtectedList(collections.Sequence): return protect(op.__add__(self._data_)) else: raise TypeError("Argument must be a list.") - + def deepcopy(self): return copy.deepcopy(self._data_) - + def __deepcopy__(self, memo): return copy.deepcopy(self._data_, memo) - + def poop(self): raise Exception("This is a list. It does not poop.") @@ -478,29 +412,29 @@ class ProtectedTuple(collections.Sequence): """ A class allowing read-only 'view' of a tuple. The object can be treated like a normal tuple, but its contents will be returned as protected objects. - + Note: It would be nice if we could inherit from list or tuple so that isinstance checks would work. However, doing this causes tuple(obj) to return unprotected results (importantly, this means unpacking into function arguments will also fail) """ def __init__(self, data): self._data_ = data - + ## List of methods to directly wrap from _data_ wrapMethods = ['__contains__', '__eq__', '__format__', '__ge__', '__getnewargs__', '__gt__', '__hash__', '__le__', '__len__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__repr__', '__str__', 'count', 'index'] - + ## List of methods which wrap from _data_ but return protected results protectMethods = ['__getitem__', '__getslice__', '__iter__', '__add__', '__mul__', '__reversed__', '__rmul__'] - - - ## Template methods + + + # # Template methods def wrapMethod(methodName): return lambda self, *a, **k: getattr(self._data_, methodName)(*a, **k) - + def protectMethod(methodName): return lambda self, *a, **k: protect(getattr(self._data_, methodName)(*a, **k)) - - + + ## Directly (and explicitly) wrap some methods from _data_ ## Many of these methods can not be intercepted using __getattribute__, so they ## must be implemented explicitly @@ -511,14 +445,14 @@ class ProtectedTuple(collections.Sequence): for methodName in protectMethods: locals()[methodName] = protectMethod(methodName) - + ## Add a few extra methods. def deepcopy(self): return copy.deepcopy(self._data_) - + def __deepcopy__(self, memo): return copy.deepcopy(self._data_, memo) - + def protect(obj): @@ -530,14 +464,14 @@ def protect(obj): return ProtectedTuple(obj) else: return obj - - + + if __name__ == '__main__': d = {'x': 1, 'y': [1,2], 'z': ({'a': 2, 'b': [3,4], 'c': (5,6)}, 1, 2)} dp = protect(d) - + l = [1, 'x', ['a', 'b'], ('c', 'd'), {'x': 1, 'y': 2}] lp = protect(l) - + t = (1, 'x', ['a', 'b'], ('c', 'd'), {'x': 1, 'y': 2}) - tp = protect(t) \ No newline at end of file + tp = protect(t)