Package qubx :: Module types
[hide private]
[frames] | no frames]

Source Code for Module qubx.types

  1  """General-purpose types and utilities. 
  2   
  3  Copyright 2007-2012 Research Foundation State University of New York  
  4  This file is part of QUB Express.                                           
  5   
  6  QUB Express is free software; you can redistribute it and/or modify           
  7  it under the terms of the GNU General Public License as published by  
  8  the Free Software Foundation, either version 3 of the License, or     
  9  (at your option) any later version.                                   
 10   
 11  QUB Express is distributed in the hope that it will be useful,                
 12  but WITHOUT ANY WARRANTY; without even the implied warranty of        
 13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         
 14  GNU General Public License for more details.                          
 15   
 16  You should have received a copy of the GNU General Public License,    
 17  named LICENSE.txt, in the QUB Express program directory.  If not, see         
 18  <http://www.gnu.org/licenses/>.                                       
 19   
 20  """ 
 21   
 22  import collections 
 23  import heapq 
 24  import pprint 
 25  import traceback 
 26  import weakref 
 27  from math import * 
 28  from string import join 
 29  import re 
 30  import os 
 31  import sys 
 32   
 33  from contextlib import contextmanager 
 34   
 35  VERSION = "1.3.9" 
 36   
 37  ShowDropped = False 
 38   
 39  UNSET_VALUE = 86738.82583 
40 41 -class Event(object):
42 """ 43 Usage: 44 45 >>> myEvent = Event() # a, b 46 >>> callback1 = lambda a, b: dostuff(a, b) 47 >>> callback2 = lambda a, b: otherstuff(a, b) 48 >>> myEvent += callback1 49 >>> myEvent += callback2 50 >>> myEvent(a, b) # calls both callbacks 51 >>> myEvent -= callback1 52 >>> myEvent -= callback2 53 >>> myEvent(a, b) # no callbacks; does nothing 54 55 Note: this is a great source of circular references; see gc.collect() 56 """ 57
58 - def __init__(self):
59 self._listeners = [] 60 self.enabled = True
61 - def __call__(self, *args, **kw):
62 if self.enabled: 63 for l in self._listeners: 64 l(*args, **kw)
65 - def __iadd__(self, l):
66 if not l in self._listeners: 67 self._listeners.append(l) 68 return self
69 - def __isub__(self, l):
70 if l in self._listeners: 71 self._listeners.remove(l) 72 return self
73 - def __nonzero__(self):
74 return len(self._listeners)
75
76 -def printer(*xx):
77 """Prints and returns its argument(s). Handy for testing callbacks.""" 78 if len(xx) == 1: 79 pprint.pprint(xx[0]) 80 # print xx[0] 81 return xx[0] 82 else: 83 for x in xx: 84 print x, 85 print 86 return xx
87
88 -class WeakEvent(object):
89 """ 90 Usage: 91 92 >>> myEvent = Event() # a, b 93 >>> callback1 = lambda a, b: dostuff(a, b) 94 >>> callback2 = lambda a, b: otherstuff(a, b) 95 >>> callback3 = self.callback3 96 >>> myEvent += callback1 97 >>> myEvent += callback2 98 >>> myEvent(a, b) # calls both callbacks 99 >>> myEvent -= callback1 100 >>> myEvent -= callback2 101 >>> myEvent(a, b) # no callbacks; does nothing 102 103 note: keep a ref somewhere, even to self.whatever, or it will be unlinked; see L{Reffer} 104 """ 105
106 - def __init__(self, name=""):
107 self._listeners = [] 108 self._names = [] 109 self._stacks = [] 110 self.name = name 111 self.enabled = True
112 - def __call__(self, *args, **kw):
113 dead = [] 114 if self.enabled: 115 for l in self._listeners: 116 listener = l() 117 if not (listener is None): 118 try: 119 listener(*args, **kw) 120 except: 121 traceback.print_exc() 122 else: 123 dead.append(l) 124 for l in dead: 125 i = self._listeners.index(l) 126 n = self._names[i] 127 if ShowDropped: 128 print "%s%sdropped callback%s%s" % (self.name, self.name and ' ', n and ': ', n) 129 for line in self._stacks[i]: 130 sys.stdout.write(" > %s"%line) 131 self._listeners.pop(i) 132 self._names.pop(i) 133 self._stacks.pop(i)
134 - def __iadd__(self, ln):
135 try: 136 l, n = ln 137 except: 138 l, n = ln, "" 139 if not l in self._listeners: 140 self._listeners.append(weakref.ref(l)) # , lambda l:printer("released "+n))) 141 self._names.append(n) 142 if ShowDropped: 143 self._stacks.append(traceback.format_stack()[:-1]) 144 else: 145 self._stacks.append("") 146 return self
147 - def __isub__(self, lsub):
148 dead = [] 149 found = None 150 for l in self._listeners: 151 listener = l() 152 if listener == lsub: 153 found = l 154 dead.append(l) 155 elif not listener: 156 dead.append(l) 157 for l in dead: 158 i = self._listeners.index(l) 159 if ShowDropped and (l != found): 160 n = self._names[i] 161 print "%s%sdropped callback%s%s" % (self.name, self.name and ' ', n and ': ', n) 162 for line in self._stacks[i]: 163 sys.stdout.write(" > %s"%line) 164 self._listeners.pop(i) 165 self._names.pop(i) 166 self._stacks.pop(i) 167 if not found: # no matching event found 168 raise Exception('-=!~') 169 return self
170 - def __nonzero__(self):
171 return len(self._listeners)
172
173 174 175 -class WeakCall(object):
176 """Like WeakEvent, except there's at most one listener, and it can return a value.""" 177
178 - def __init__(self, name=""):
179 self.name = name 180 self._listener = None 181 self._listener_name = "" 182 self.enabled = True
183 - def __call__(self, *args, **kw):
184 if self.enabled and not (self._listener is None): 185 listener = self._listener() 186 if not (listener is None): 187 try: 188 return listener(*args, **kw) 189 except: 190 traceback.print_exc() 191 else: 192 if ShowDropped: 193 n = self._listener_name 194 print "%s%sdropped callback%s%s" % (self.name, self.name and ' ', n and ': ', n) 195 self._listener = None 196 self._listener_name = ""
197 - def assign(self, listener, name=""):
198 self._listener = None if (listener is None) else weakref.ref(listener) 199 self._listener_name = name
200 - def __nonzero__(self):
201 return not (self._listener is None)
202
203 204 # for WeakEvent and WeakCall, we need to keep bound-method references somewhere: 205 # self.r = Reffer() 206 # self.foo.OnFoo += self.r(self._onFoo) 207 208 -class Reffer(object):
209 """ 210 Holds references to keep objects alive. 211 Intended in combo with WeakEvent for bound method references e.g. self.onEvent, 212 since self.onEvent creates a temporary instance that would otherwise be destroyed. 213 214 Usage: 215 216 >>> self.ref = Reffer() 217 >>> self.evt = WeakEvent() 218 >>> self.evt += self.ref(self.onEvt) 219 >>> ... 220 >>> self.evt -= self.ref(self.onEvt) # hashes the same each time 221 """
222 - def __init__(self):
223 self.r = {}
224 - def __call__(self, ref):
225 if not self.r.has_key(ref): 226 self.r[ref] = ref 227 return self.r[ref]
228 - def clear(self):
229 self.r.clear()
230
231 -class DeathRattle(object):
232 """Prints to stdout when it is finally freed, presumably at the same time as its owner. 233 234 Usage: 235 236 >>> self.death_rattle = DeathRattle('Free at last') 237 """
238 - def __init__(self, rattle):
239 self.rattle = rattle
240 - def __del__(self):
241 print self.rattle
242
243 -class Anon(object):
244 """ 245 An object whose fields are the constructor keyword arguments. 246 Requesting any other field returns None rather than raise NameError. 247 248 Example: 249 250 >>> x = Anon(stuff='x',n=5) 251 >>> print x.stuff, x.n, x.whatever 252 x, 5, None 253 """
254 - def __init__(self, *args, **kw):
255 for d in self._find_arg_dicts(*args, **kw): 256 for k,v in d.iteritems(): 257 self.__dict__[k] = v
258 - def _find_arg_dicts(self, *args, **kw):
259 dicts = [] 260 for arg in args: 261 if isinstance(arg, str): 262 try: 263 dicts.append( dict(eval(arg)) ) 264 except: 265 print '%s(): ignoring non-dict string argument: %s' % (self.__class__, arg) 266 elif isinstance(arg, dict): 267 dicts.append( arg ) 268 else: 269 try: 270 dicts.append( arg.__dict__ ) 271 except: 272 print '%s(): ignoring non-mapping argument: %s (%s)' % (self.__class__, arg, arg.__class__) 273 dicts.append(kw) 274 return dicts
275 # access missing field returns None instead of raising NameError
276 - def __getattr__(self, name):
277 try: 278 return self.__dict__[name] 279 except: 280 return None
281 - def __repr__(self):
282 return repr(self.__dict__)
283 - def __str__(self):
284 return self._str_indent()
285 - def _str_indent(self, indent=""):
286 def val2str(v): 287 if isinstance(v, Anon): 288 return v._str_indent(indent+" | ") 289 else: 290 return str(v)
291 return '\n'.join([str(self.__class__)] + 292 ['%s%16s: %s' % (indent,k,val2str(v)) for k,v in self.__dict__.iteritems()])
293 - def clone(self, **kw):
294 return self.__class__(self, **kw)
295
296 -class JsonAnon(Anon):
297 - def __init__(self, *args, **kw):
298 for d in self._find_arg_dicts(*args, **kw): 299 for k,v in d.iteritems(): 300 if isinstance(v, dict): 301 self.__dict__[k] = self.__class__(v) 302 print v 303 print 304 print self.__dict__[k] 305 print 306 else: 307 self.__dict__[k] = v
308
309 310 -def breduce(func, seq):
311 """like builtin reduce(), but recursing by splitting in half. Doesn't seem to make a difference.""" 312 if len(seq) < 1: 313 return None 314 elif len(seq) == 1: 315 return seq[0] 316 elif len(seq) == 2: 317 return func(seq[0], seq[1]) 318 else: 319 half = len(seq)/2 320 return func(breduce(func, seq[:half]), breduce(func, seq[half:]))
321
322 -def memoize(func, lbl=""):
323 """ 324 Returns wrapped func that caches return values for specific args. 325 All args must be immutable (e.g. tuples not lists). 326 327 Example: 328 329 >>> def factorial(n): 330 >>> if n > 1: 331 >>> return n * fast_fac(n-1) 332 >>> else: 333 >>> return 1 334 >>> fast_fac = memoize(factorial) 335 """ 336 results = {} 337 def wrapper(*args): 338 if args in results: 339 return results[args] 340 else: 341 result = func(*args) 342 results[args] = result 343 # print lbl, args, ':', result 344 return result
345 wrapper.results = results 346 wrapper.reset = results.clear 347 return wrapper 348
349 -def memoize_heapq(func, max_weight=1000000, weigh=lambda x: 1, lbl=""):
350 """ 351 Returns wrapped func that caches return values for specific args. 352 All args must be immutable (e.g. tuples not lists). 353 354 Example: 355 356 >>> def factorial(n): 357 >>> if n > 1: 358 >>> return n * fast_fac(n-1) 359 >>> else: 360 >>> return 1 361 >>> fast_fac = memoize(factorial) 362 """ 363 results = {} 364 order = [] 365 next = [-1000000000] # counting up 366 total_weight = [0] 367 def wrapper(*args): 368 if args in results: 369 index, result = results[args] 370 for i in reversed(xrange(len(order))): 371 if order[i][0] == index: 372 results[args] = (next[0], result) 373 order[i] = (next[0], args) 374 next[0] += 1 375 heapq_sink(order, i) 376 break 377 return result 378 else: 379 result = func(*args) 380 weight = total_weight[0] + weigh(result) 381 while weight > max_weight: 382 rem_args = heapq.heappop(order)[1] 383 weight -= weigh(results[rem_args]) 384 del results[rem_args] 385 total_weight[0] = weight 386 results[args] = (next[0], result) 387 heapq.heappush(order, (next[0], args)) 388 next[0] += 1 389 # print lbl, args, ':', result 390 return result
391 return wrapper 392
393 -def heapq_swim(heap, k):
394 while (k > 0) and (heap[(k+1)/2-1] > heap[k]): 395 heap[k], heap[(k+1)/2-1] = heap[(k+1)/2-1], heap[k]
396
397 -def heapq_sink(heap, k):
398 k2, N = (k+1)*2 - 1, len(heap) 399 if k2 >= N: return 400 if ((k2+1) < N) and (heap[k2] > heap[k2+1]): 401 k2 += 1 402 if heap[k] > heap[k2]: 403 heap[k], heap[k2] = heap[k2], heap[k] 404 heapq_sink(heap, k2)
405
406 407 -def bind(callback, *args, **kw):
408 """ 409 Returns a function f which, when called, discards its own args and returns callback(*args, **kw). 410 411 Lambda can be useful to share one message handler among many sources, but there's a limitation: 412 >>> for i in xrange(N): 413 chk[i].OnToggle += self__ref(lambda chk: self.__onToggle(i)) 414 415 Here, the lambda binds not the value of i, but the stack location whose contents are repeatedly changed by the for loop. 416 So all the lambdas are effectively doing self.__onToggle(N-1). 417 418 Instead: 419 >>> for i in xrange(N): 420 chk[i].OnToggle += self.__ref(bind(self.__onToggle, i)) 421 422 This creates a unique stack frame for your unique i, so it does what you expect. 423 """ 424 425 def act(*act_args, **act_kw): 426 return callback(*args, **kw)
427 return act 428
429 -def bind_with_args(callback, *args, **kw):
430 """Like bind, but it appends the callback's positional args after the bound positional args; returns callback(*(args+cb_args), **kw).""" 431 def act(*act_args, **act_kw): 432 use_kw = kw.copy() 433 for k,v in act_kw.iteritems(): 434 use_kw[k] = v 435 return callback(*(args+act_args), **use_kw)
436 return act 437
438 -def bind_with_args_before(callback, *args, **kw):
439 """Like bind, but it prepends the callback's positional args before the bound positional args; returns callback(*(args+cb_args), **kw).""" 440 def act(*act_args, **act_kw): 441 use_kw = kw.copy() 442 for k,v in act_kw.iteritems(): 443 use_kw[k] = v 444 return callback(*(act_args+args), **use_kw)
445 return act 446
447 448 # all coords in [0,1] 449 -def HSVtoRGB(h,s,v):
450 """Returns (r,g,b) color coordinates corresponding to the hue, saturation and value. All coordinates are between 0 and 1.""" 451 h, s, v = map(float, (h, s, v)) 452 if h == 1.0: h = 0.0 453 h *= 360 454 455 if s == 0: 456 return v,v,v 457 458 sectorPos = h/60.0 459 sectorNumber = int(floor(sectorPos)) 460 fractionalSector = sectorPos - sectorNumber 461 462 p = v * (1 - s) 463 q = v * (1 - (s * fractionalSector)) 464 t = v * (1 - (s * (1 - fractionalSector))) 465 466 return {0 : (v, t, p), 467 1 : (q, v, p), 468 2 : (p, v, t), 469 3 : (p, q, v), 470 4 : (t, p, v), 471 5 : (v, p, q)} [sectorNumber]
472
473 -def RGBtoHSV(r,g,b):
474 """Returns the (hue, saturation, value) color coordinates corresponding to the RGB color. All coordinates are between 0 and 1.""" 475 r, g, b = map(float, (r, g, b)) 476 mn = min(r, g, b) 477 mx = max(r, g, b) 478 v = mx 479 delta = mx - mn 480 if (mx == 0.0) or (delta == 0.0): 481 return 0.0, 0.0, v 482 s = delta / mx 483 if r == mx: 484 h = ((g - b) / delta) % 6.0 485 elif g == mx: 486 h = 2.0 + (b - r) / delta 487 else: 488 h = 4.0 + (r - g) / delta 489 h *= 60.0 490 if h < 0: h += 360 491 return h/360.0, s, v
492
493 -def ScaleToColor(x, saturation=1.0): # x in [-1..1]
494 """ 495 Returns r,g,b corresponding to x in [-1..1]. 496 The scale moves smoothly around the color wheel from pink (-) to red (+), 497 with brightness proportional to abs(x). 498 """ 499 y = sin(x*pi/2) 500 h = 0.44 * (1.0 - y) # leave off the last 12% of the wheel so 0 is distinguishable from 1 501 v = abs(x) 502 return HSVtoRGB(h, saturation, v*(2.0 - v)) 503 504 505 # toolspace/modelGTK constants useful everywhere 506 WITHALPHA = lambda tup: (len(tup) == 3) and tuple(list(tup)+[1]) or tup 507 NOALPHA = lambda tup: (len(tup) == 4) and tup[:3] or tup 508 SETALPHA = lambda tup, a: tuple(list(NOALPHA(tup)) + [a]) 509 ADDALPHA = lambda tup, a: tuple(list(tup) + [a])
510 511 -def INVERT_COLOR(tup):
512 out = list(tup[:]) 513 r,g,b = tup[:3] 514 h,s,v = RGBtoHSV(r, g, b) 515 out[:3] = HSVtoRGB((h+0.5) % 1.0, s, v) 516 return tuple(out)
517 518 519 COLOR = collections.defaultdict(lambda:(.5, .5, .5)) 520 for i,c in enumerate([(0,0,0), (1,0,0), (0,0,1), (0,1,0), (0,1,1), (1,1,0), (1,0,1)]): 521 COLOR[i] = c 522 offset = len(COLOR) 523 for i in xrange(20): 524 COLOR[offset+i] = HSVtoRGB((i*2.0/20)%1.0, (40.0-i)/40, (20.0+i)/40) 525 COLOR_CLASS = lambda c: ('modelGTK.class[%i]' % c, COLOR[c])
526 527 528 529 -def path_splits(s):
530 """Returns the list of all path components '/a/b/c' -> ['/', 'a', 'b', 'c']""" 531 l,r = os.path.split(s) 532 if (l == ''): 533 return [r] 534 elif (l == s): 535 return [s] 536 else: 537 lst = path_splits(l) 538 lst.append(r) 539 return lst
540
541 -def add_ext_if_none(path, ext):
542 """Returns path unmodified if it already has an extension, otherwise appends ext.""" 543 if os.path.splitext(path)[1]: 544 return path 545 else: 546 return path + ext
547
548 549 -class Product(object):
550 - def __init__(self, get=None):
551 self.serial = 0 552 self.__get = get or self.get 553 self.__last_serial = -1 554 self.__last_args = None 555 self.__last_val = None 556 self.OnInvalidate = WeakEvent() # (Product)
557 - def invalidate(self):
558 self.serial += 1 559 self.OnInvalidate(self)
560 - def get_val(self, *args):
561 if (self.serial != self.__last_serial) or (args != self.__last_args): 562 self.__last_val = self.__get(*args) 563 self.__last_serial = self.serial 564 self.__last_args = args 565 return self.__last_val
566 val = property(get_val)
567 - def get(self):
568 raise Exception('abstract')
569
570 571 -class OverridingDict(dict):
572 """"Overrides" the base dict so keys are looked up first here, then if not found, in base dict."""
573 - def __init__(self, base=None, *args, **kw):
574 dict.__init__(self, *args, **kw) 575 self.base = base or {}
576 - def has_key(self, key):
577 return (dict.has_key(self, key)) or (key in self.base)
578 - def keys(self):
579 return list(set(dict.keys(self) + self.base.keys()))
580 - def __contains__(self, key):
581 return (dict.__contains__(self, key)) or self.base.__contains__(key)
582 - def __getitem__(self, key):
583 if dict.__contains__(self, key): 584 return dict.__getitem__(self, key) 585 else: 586 return self.base[key]
587 - def get(self, key):
588 if dict.__contains__(self, key): 589 return dict.__getitem__(self, key) 590 else: 591 return self.base.get(key)
592
593 594 -def scrolled_int(val, offset, fine=False, coarse=False, lo=None, hi=None):
595 if fine: 596 offset /= 10.0 597 elif coarse: 598 offset *= 10.0 599 if val < 100: 600 offset = int(round(offset)) or ((offset >= 0.0) and 1 or -1) 601 result = val + offset 602 else: 603 sgn = 1 if val >= 0 else -1 604 result = int(round(val * pow(1.01, sgn*offset))) 605 if not (lo is None): 606 result = max(lo, result) 607 if not (hi is None): 608 result = min(hi, result) 609 return result
610
611 -def scrolled_float(val, offset, fine=False, coarse=False, lo=None, hi=None):
612 """Returns val scrolled according to offset direction and modifiers.""" 613 if fine: 614 offset /= 10.0 615 elif coarse: 616 offset *= 10.0 617 sgn = 1 if val >= 0 else -1 618 result = val * pow(1.01, sgn*offset) 619 if not (lo is None): 620 result = max(lo, result) 621 if not (hi is None): 622 result = min(hi, result) 623 return result
624
625 -def scrolled_digits(val, offset, fine=False, coarse=False, lo=None, hi=None):
626 incr = 1e15 if val else 1.0 627 # incr will be most significant digit (abs) changed to 1 628 while val and (abs(val) < incr): 629 incr *= 1e-1 630 if (offset < 0) and (abs(val) < 1.1*incr): 631 incr *= 1e-1 632 if coarse: 633 pass # most significant digit 634 elif fine: 635 incr *= 1e-2 636 else: 637 incr *= 1e-1 638 result = val + offset * incr 639 if not (lo is None): 640 result = max(lo, result) 641 if not (hi is None): 642 result = min(hi, result) 643 return result
644
645 646 @contextmanager 647 -def nested_break():
648 # from Mark Dickinson (via stackoverflow.com) 649 class NestedBreakException(Exception): 650 pass
651 try: 652 yield NestedBreakException 653 except NestedBreakException: 654 pass 655 656 #with nested_break() as mylabel: 657 # while True: 658 # print "current state" 659 # while True: 660 # ok = raw_input("Is this ok? (y/n)") 661 # if ok == "y" or ok == "Y": raise mylabel 662 # if ok == "n" or ok == "N": break 663 # print "more processing" 664