PythonUtil.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. import types
  2. import string
  3. import re
  4. import math
  5. import operator
  6. # NOTE: ifAbsentPut has been replaced with Python's dictionary's builtin setdefault
  7. # before:
  8. # ifAbsentPut(dict, key, defaultValue)
  9. # after:
  10. # dict.setdefault(key, defaultValue)
  11. # Please use setdefault instead -- Joe
  12. def unique(L1, L2):
  13. """Return a list containing all items in 'L1' that are not in 'L2'"""
  14. L2 = dict([(k,None) for k in L2])
  15. return [item for item in L1 if item not in L2]
  16. def indent(stream, numIndents, str):
  17. """
  18. Write str to stream with numIndents in front it it
  19. """
  20. # To match emacs, instead of a tab character we will use 4 spaces
  21. stream.write(' ' * numIndents + str)
  22. def apropos(obj, *args):
  23. """
  24. Obsolete, use pdir
  25. """
  26. print 'Use pdir instead'
  27. def getClassLineage(obj):
  28. """ getClassLineage(obj): print object inheritance list """
  29. # Just a dictionary, return dictionary
  30. if type(obj) == types.DictionaryType:
  31. return [obj]
  32. # Instance, make a list with the instance and its class interitance
  33. elif type(obj) == types.InstanceType:
  34. return [obj] + getClassLineage(obj.__class__)
  35. # Class, see what it derives from
  36. elif type(obj) == types.ClassType:
  37. lineage = [obj]
  38. for c in obj.__bases__:
  39. lineage = lineage + getClassLineage(c)
  40. return lineage
  41. # Not what I'm looking for
  42. else:
  43. return []
  44. def pdir(obj, str = None, fOverloaded = 0, width = None,
  45. fTruncate = 1, lineWidth = 75, wantPrivate = 0):
  46. # Remove redundant class entries
  47. uniqueLineage = []
  48. for l in getClassLineage(obj):
  49. if type(l) == types.ClassType:
  50. if l in uniqueLineage:
  51. break
  52. uniqueLineage.append(l)
  53. # Pretty print out directory info
  54. uniqueLineage.reverse()
  55. for obj in uniqueLineage:
  56. _pdir(obj, str, fOverloaded, width, fTruncate, lineWidth, wantPrivate)
  57. print
  58. def _pdir(obj, str = None, fOverloaded = 0, width = None,
  59. fTruncate = 1, lineWidth = 75, wantPrivate = 0):
  60. """
  61. Print out a formatted list of members and methods of an instance or class
  62. """
  63. def printHeader(name):
  64. name = ' ' + name + ' '
  65. length = len(name)
  66. if length < 70:
  67. padBefore = int((70 - length)/2.0)
  68. padAfter = max(0,70 - length - padBefore)
  69. header = '*' * padBefore + name + '*' * padAfter
  70. print header
  71. print
  72. def printInstanceHeader(i, printHeader = printHeader):
  73. printHeader(i.__class__.__name__ + ' INSTANCE INFO')
  74. def printClassHeader(c, printHeader = printHeader):
  75. printHeader(c.__name__ + ' CLASS INFO')
  76. def printDictionaryHeader(d, printHeader = printHeader):
  77. printHeader('DICTIONARY INFO')
  78. # Print Header
  79. if type(obj) == types.InstanceType:
  80. printInstanceHeader(obj)
  81. elif type(obj) == types.ClassType:
  82. printClassHeader(obj)
  83. elif type (obj) == types.DictionaryType:
  84. printDictionaryHeader(obj)
  85. # Get dict
  86. if type(obj) == types.DictionaryType:
  87. dict = obj
  88. else:
  89. dict = obj.__dict__
  90. # Adjust width
  91. if width:
  92. maxWidth = width
  93. else:
  94. maxWidth = 10
  95. keyWidth = 0
  96. aproposKeys = []
  97. privateKeys = []
  98. remainingKeys = []
  99. for key in dict.keys():
  100. if not width:
  101. keyWidth = len(key)
  102. if str:
  103. if re.search(str, key, re.I):
  104. aproposKeys.append(key)
  105. if (not width) and (keyWidth > maxWidth):
  106. maxWidth = keyWidth
  107. else:
  108. if key[:1] == '_':
  109. if wantPrivate:
  110. privateKeys.append(key)
  111. if (not width) and (keyWidth > maxWidth):
  112. maxWidth = keyWidth
  113. else:
  114. remainingKeys.append(key)
  115. if (not width) and (keyWidth > maxWidth):
  116. maxWidth = keyWidth
  117. # Sort appropriate keys
  118. if str:
  119. aproposKeys.sort()
  120. else:
  121. privateKeys.sort()
  122. remainingKeys.sort()
  123. # Print out results
  124. if wantPrivate:
  125. keys = aproposKeys + privateKeys + remainingKeys
  126. else:
  127. keys = aproposKeys + remainingKeys
  128. format = '%-' + `maxWidth` + 's'
  129. for key in keys:
  130. value = dict[key]
  131. if callable(value):
  132. strvalue = `Signature(value)`
  133. else:
  134. strvalue = `value`
  135. if fTruncate:
  136. # Cut off line (keeping at least 1 char)
  137. strvalue = strvalue[:max(1,lineWidth - maxWidth)]
  138. print (format % key)[:maxWidth] + '\t' + strvalue
  139. # Magic numbers: These are the bit masks in func_code.co_flags that
  140. # reveal whether or not the function has a *arg or **kw argument.
  141. _POS_LIST = 4
  142. _KEY_DICT = 8
  143. def _is_variadic(function):
  144. return function.func_code.co_flags & _POS_LIST
  145. def _has_keywordargs(function):
  146. return function.func_code.co_flags & _KEY_DICT
  147. def _varnames(function):
  148. return function.func_code.co_varnames
  149. def _getcode(f):
  150. """
  151. _getcode(f)
  152. This function returns the name and function object of a callable
  153. object.
  154. """
  155. def method_get(f):
  156. return f.__name__, f.im_func
  157. def function_get(f):
  158. return f.__name__, f
  159. def instance_get(f):
  160. if hasattr(f, '__call__'):
  161. method = f.__call__
  162. if (type(method) == types.MethodType):
  163. func = method.im_func
  164. else:
  165. func = method
  166. return ("%s%s" % (f.__class__.__name__, '__call__'), func)
  167. else:
  168. s = ("Instance %s of class %s does not have a __call__ method" %
  169. (f, f.__class__.__name__))
  170. raise TypeError, s
  171. def class_get(f):
  172. if hasattr(f, '__init__'):
  173. return f.__name__, f.__init__.im_func
  174. else:
  175. return f.__name__, lambda: None
  176. codedict = { types.UnboundMethodType: method_get,
  177. types.MethodType : method_get,
  178. types.FunctionType : function_get,
  179. types.InstanceType : instance_get,
  180. types.ClassType : class_get,
  181. }
  182. try:
  183. return codedict[type(f)](f)
  184. except KeyError:
  185. if callable(f): # eg, built-in functions and methods
  186. # raise ValueError, "type %s not supported yet." % type(f)
  187. return f.__name__, None
  188. else:
  189. raise TypeError, ("object %s of type %s is not callable." %
  190. (f, type(f)))
  191. class Signature:
  192. def __init__(self, func):
  193. self.type = type(func)
  194. self.name, self.func = _getcode(func)
  195. def ordinary_args(self):
  196. n = self.func.func_code.co_argcount
  197. return _varnames(self.func)[0:n]
  198. def special_args(self):
  199. n = self.func.func_code.co_argcount
  200. x = {}
  201. #
  202. if _is_variadic(self.func):
  203. x['positional'] = _varnames(self.func)[n]
  204. if _has_keywordargs(self.func):
  205. x['keyword'] = _varnames(self.func)[n+1]
  206. elif _has_keywordargs(self.func):
  207. x['keyword'] = _varnames(self.func)[n]
  208. else:
  209. pass
  210. return x
  211. def full_arglist(self):
  212. base = list(self.ordinary_args())
  213. x = self.special_args()
  214. if x.has_key('positional'):
  215. base.append(x['positional'])
  216. if x.has_key('keyword'):
  217. base.append(x['keyword'])
  218. return base
  219. def defaults(self):
  220. defargs = self.func.func_defaults
  221. args = self.ordinary_args()
  222. mapping = {}
  223. if defargs is not None:
  224. for i in range(-1, -(len(defargs)+1), -1):
  225. mapping[args[i]] = defargs[i]
  226. else:
  227. pass
  228. return mapping
  229. def __repr__(self):
  230. if self.func:
  231. defaults = self.defaults()
  232. specials = self.special_args()
  233. l = []
  234. for arg in self.ordinary_args():
  235. if defaults.has_key(arg):
  236. l.append( arg + '=' + str(defaults[arg]) )
  237. else:
  238. l.append( arg )
  239. if specials.has_key('positional'):
  240. l.append( '*' + specials['positional'] )
  241. if specials.has_key('keyword'):
  242. l.append( '**' + specials['keyword'] )
  243. return "%s(%s)" % (self.name, string.join(l, ', '))
  244. else:
  245. return "%s(?)" % self.name
  246. def aproposAll(obj):
  247. """
  248. Print out a list of all members and methods (including overloaded methods)
  249. of an instance or class
  250. """
  251. apropos(obj, fOverloaded = 1, fTruncate = 0)
  252. def doc(obj):
  253. if (isinstance(obj, types.MethodType)) or \
  254. (isinstance(obj, types.FunctionType)):
  255. print obj.__doc__
  256. def adjust(command = None, dim = 1, parent = None, **kw):
  257. """
  258. adjust(command = None, parent = None, **kw)
  259. Popup and entry scale to adjust a parameter
  260. Accepts any Slider keyword argument. Typical arguments include:
  261. command: The one argument command to execute
  262. min: The min value of the slider
  263. max: The max value of the slider
  264. resolution: The resolution of the slider
  265. text: The label on the slider
  266. These values can be accessed and/or changed after the fact
  267. >>> vg = adjust()
  268. >>> vg['min']
  269. 0.0
  270. >>> vg['min'] = 10.0
  271. >>> vg['min']
  272. 10.0
  273. """
  274. # Make sure we enable Tk
  275. import Valuator
  276. # Set command if specified
  277. if command:
  278. kw['command'] = lambda x: apply(command, x)
  279. if parent is None:
  280. kw['title'] = command.__name__
  281. kw['dim'] = dim
  282. # Create toplevel if needed
  283. if not parent:
  284. vg = apply(Valuator.ValuatorGroupPanel, (parent,), kw)
  285. else:
  286. vg = apply(Valuator.ValuatorGroup,(parent,), kw)
  287. vg.pack(expand = 1, fill = 'x')
  288. return vg
  289. def intersection(a, b):
  290. """
  291. intersection(list, list):
  292. """
  293. if not a: return []
  294. if not b: return []
  295. d = []
  296. for i in a:
  297. if (i in b) and (i not in d):
  298. d.append(i)
  299. for i in b:
  300. if (i in a) and (i not in d):
  301. d.append(i)
  302. return d
  303. def union(a, b):
  304. """
  305. union(list, list):
  306. """
  307. # Copy a
  308. c = a[:]
  309. for i in b:
  310. if (i not in c):
  311. c.append(i)
  312. return c
  313. def sameElements(a, b):
  314. if len(a) != len(b):
  315. return 0
  316. for elem in a:
  317. if elem not in b:
  318. return 0
  319. for elem in b:
  320. if elem not in a:
  321. return 0
  322. return 1
  323. def contains(whole, sub):
  324. """
  325. Return 1 if whole contains sub, 0 otherwise
  326. """
  327. if (whole == sub):
  328. return 1
  329. for elem in sub:
  330. # The first item you find not in whole, return 0
  331. if elem not in whole:
  332. return 0
  333. # If you got here, whole must contain sub
  334. return 1
  335. def replace(list, old, new, all=0):
  336. """
  337. replace 'old' with 'new' in 'list'
  338. if all == 0, replace first occurrence
  339. otherwise replace all occurrences
  340. returns the number of items replaced
  341. """
  342. if old not in list:
  343. return 0
  344. if not all:
  345. i = list.index(old)
  346. list[i] = new
  347. return 1
  348. else:
  349. numReplaced = 0
  350. for i in xrange(len(list)):
  351. if list[i] == old:
  352. numReplaced += 1
  353. list[i] = new
  354. return numReplaced
  355. def reduceAngle(deg):
  356. """
  357. Reduces an angle (in degrees) to a value in [-180..180)
  358. """
  359. return (((deg + 180.) % 360.) - 180.)
  360. def fitSrcAngle2Dest(src, dest):
  361. """
  362. given a src and destination angle, returns an equivalent src angle
  363. that is within [-180..180) of dest
  364. examples:
  365. fitSrcAngle2Dest(30,60) == 30
  366. fitSrcAngle2Dest(60,30) == 60
  367. fitSrcAngle2Dest(0,180) == 0
  368. fitSrcAngle2Dest(-1,180) == 359
  369. fitSrcAngle2Dest(-180,180) == 180
  370. """
  371. return dest + reduceAngle(src - dest)
  372. def fitDestAngle2Src(src, dest):
  373. """
  374. given a src and destination angle, returns an equivalent dest angle
  375. that is within [-180..180) of src
  376. examples:
  377. fitDestAngle2Src(30,60) == 60
  378. fitDestAngle2Src(60,30) == 30
  379. fitDestAngle2Src(0,180) == -180
  380. fitDestAngle2Src(1,180) == 180
  381. """
  382. return src + (reduceAngle(dest - src))
  383. def closestDestAngle2(src, dest):
  384. # The function above didn't seem to do what I wanted. So I hacked
  385. # this one together. I can't really say I understand it. It's more
  386. # from impirical observation... GRW
  387. diff = src - dest
  388. # if the difference is greater that 180 it's shorter to go the other way
  389. if diff > 180:
  390. return dest - 360
  391. # or perhaps the OTHER other way...
  392. elif diff < -180:
  393. return dest + 360
  394. # otherwise just go to the original destination
  395. else:
  396. return dest
  397. def closestDestAngle(src, dest):
  398. # The function above didn't seem to do what I wanted. So I hacked
  399. # this one together. I can't really say I understand it. It's more
  400. # from impirical observation... GRW
  401. diff = src - dest
  402. # if the difference is greater that 180 it's shorter to go the other way
  403. if diff > 180:
  404. return src - (diff - 360)
  405. # or perhaps the OTHER other way...
  406. elif diff < -180:
  407. return src - (360 + diff)
  408. # otherwise just go to the original destination
  409. else:
  410. return dest
  411. def binaryRepr(number, max_length = 32):
  412. # This will only work reliably for relatively small numbers.
  413. # Increase the value of max_length if you think you're going
  414. # to use long integers
  415. assert number < 2L << max_length
  416. shifts = map (operator.rshift, max_length * [number], \
  417. range (max_length - 1, -1, -1))
  418. digits = map (operator.mod, shifts, max_length * [2])
  419. if not digits.count (1): return 0
  420. digits = digits [digits.index (1):]
  421. return string.join (map (repr, digits), '')
  422. # constant profile defaults
  423. PyUtilProfileDefaultFilename = 'profiledata'
  424. PyUtilProfileDefaultLines = 80
  425. PyUtilProfileDefaultSorts = ['cumulative', 'time', 'calls']
  426. # call this from the prompt, and break back out to the prompt
  427. # to stop profiling
  428. #
  429. # OR to do inline profiling, you must make a globally-visible
  430. # function to be profiled, i.e. to profile 'self.load()', do
  431. # something like this:
  432. #
  433. # def func(self=self):
  434. # self.load()
  435. # import __builtin__
  436. # __builtin__.func = func
  437. # PythonUtil.startProfile(cmd='func()', filename='loadProfile')
  438. # del __builtin__.func
  439. #
  440. def startProfile(filename=PyUtilProfileDefaultFilename,
  441. lines=PyUtilProfileDefaultLines,
  442. sorts=PyUtilProfileDefaultSorts,
  443. silent=0,
  444. cmd='run()'):
  445. import profile
  446. profile.run(cmd, filename)
  447. if not silent:
  448. printProfile(filename, lines, sorts)
  449. # call this to see the results again
  450. def printProfile(filename=PyUtilProfileDefaultFilename,
  451. lines=PyUtilProfileDefaultLines,
  452. sorts=PyUtilProfileDefaultSorts,):
  453. import pstats
  454. s = pstats.Stats(filename)
  455. s.strip_dirs()
  456. for sort in sorts:
  457. s.sort_stats(sort)
  458. s.print_stats(lines)
  459. class Functor:
  460. def __init__(self, function, *args, **kargs):
  461. assert callable(function), "function should be a callable obj"
  462. self._function = function
  463. self._args = args
  464. self._kargs = kargs
  465. self.__name__ = 'Functor: %s' % self._function.__name__
  466. self.__doc__ = self._function.__doc__
  467. def __call__(self, *args, **kargs):
  468. """call function"""
  469. _args = list(self._args)
  470. _args.extend(args)
  471. _kargs = self._kargs.copy()
  472. _kargs.update(kargs)
  473. return apply(self._function,_args,_kargs)
  474. def bound(value, bound1, bound2):
  475. """
  476. returns value if value is between bound1 and bound2
  477. otherwise returns bound that is closer to value
  478. """
  479. if bound1 > bound2:
  480. return min(max(value, bound2), bound1)
  481. else:
  482. return min(max(value, bound1), bound2)
  483. def lerp(v0, v1, t):
  484. """
  485. returns a value lerped between v0 and v1, according to t
  486. t == 0 maps to v0, t == 1 maps to v1
  487. """
  488. return v0 + (t * (v1 - v0))
  489. def lineupPos(i, num, spacing):
  490. """
  491. use to line up a series of 'num' objects, in one dimension,
  492. centered around zero
  493. 'i' is the index of the object in the lineup
  494. 'spacing' is the amount of space between objects in the lineup
  495. """
  496. assert num >= 1
  497. assert i >= 0 and i < num
  498. pos = float(i) * spacing
  499. return pos - ((float(spacing) * (num-1))/2.)
  500. def formatElapsedSeconds(seconds):
  501. """
  502. Returns a string of the form "mm:ss" or "hh:mm:ss" or "n days",
  503. representing the indicated elapsed time in seconds.
  504. """
  505. sign = ''
  506. if seconds < 0:
  507. seconds = -seconds
  508. sign = '-'
  509. seconds = (int)(seconds)
  510. hours = (int)(seconds / (60 * 60))
  511. if hours > 36:
  512. days = (int)((hours + 12) / 24)
  513. return "%s%d days" % (sign, days)
  514. seconds -= hours * (60 * 60)
  515. minutes = (int)(seconds / 60)
  516. seconds -= minutes * 60
  517. if hours != 0:
  518. return "%s%d:%02d:%02d" % (sign, hours, minutes, seconds)
  519. else:
  520. return "%s%d:%02d" % (sign, minutes, seconds)
  521. def solveQuadratic(a, b, c):
  522. # quadratic equation: ax^2 + bx + c = 0
  523. # quadratic formula: x = [-b +/- sqrt(b^2 - 4ac)] / 2a
  524. # returns None, root, or [root1, root2]
  525. # a cannot be zero.
  526. if a == 0.:
  527. return None
  528. # calculate the determinant (b^2 - 4ac)
  529. D = (b * b) - (4. * a * c)
  530. if D < 0:
  531. # there are no solutions (sqrt(negative number) is undefined)
  532. return None
  533. elif D == 0:
  534. # only one root
  535. return (-b) / (2. * a)
  536. else:
  537. # OK, there are two roots
  538. sqrtD = math.sqrt(D)
  539. twoA = 2. * a
  540. root1 = ((-b) - sqrtD) / twoA
  541. root2 = ((-b) + sqrtD) / twoA
  542. return [root1, root2]