mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-04 19:08:55 -04:00
770 lines
24 KiB
Python
770 lines
24 KiB
Python
import types
|
|
import string
|
|
import re
|
|
import math
|
|
import operator
|
|
import inspect
|
|
import os
|
|
import sys
|
|
|
|
|
|
# NOTE: ifAbsentPut has been replaced with Python's dictionary's builtin setdefault
|
|
# before:
|
|
# ifAbsentPut(dict, key, defaultValue)
|
|
# after:
|
|
# dict.setdefault(key, defaultValue)
|
|
# Please use setdefault instead -- Joe
|
|
|
|
|
|
def unique(L1, L2):
|
|
"""Return a list containing all items in 'L1' that are not in 'L2'"""
|
|
L2 = dict([(k,None) for k in L2])
|
|
return [item for item in L1 if item not in L2]
|
|
|
|
def indent(stream, numIndents, str):
|
|
"""
|
|
Write str to stream with numIndents in front of it
|
|
"""
|
|
# To match emacs, instead of a tab character we will use 4 spaces
|
|
stream.write(' ' * numIndents + str)
|
|
|
|
def apropos(obj, *args):
|
|
"""
|
|
Obsolete, use pdir
|
|
"""
|
|
print 'Use pdir instead'
|
|
|
|
def getClassLineage(obj):
|
|
""" getClassLineage(obj): print object inheritance list """
|
|
# Just a dictionary, return dictionary
|
|
if type(obj) == types.DictionaryType:
|
|
return [obj]
|
|
# Instance, make a list with the instance and its class interitance
|
|
elif type(obj) == types.InstanceType:
|
|
return [obj] + getClassLineage(obj.__class__)
|
|
# Class, see what it derives from
|
|
elif type(obj) == types.ClassType:
|
|
lineage = [obj]
|
|
for c in obj.__bases__:
|
|
lineage = lineage + getClassLineage(c)
|
|
return lineage
|
|
# Not what I'm looking for
|
|
else:
|
|
return []
|
|
|
|
def pdir(obj, str = None, fOverloaded = 0, width = None,
|
|
fTruncate = 1, lineWidth = 75, wantPrivate = 0):
|
|
# Remove redundant class entries
|
|
uniqueLineage = []
|
|
for l in getClassLineage(obj):
|
|
if type(l) == types.ClassType:
|
|
if l in uniqueLineage:
|
|
break
|
|
uniqueLineage.append(l)
|
|
# Pretty print out directory info
|
|
uniqueLineage.reverse()
|
|
for obj in uniqueLineage:
|
|
_pdir(obj, str, fOverloaded, width, fTruncate, lineWidth, wantPrivate)
|
|
print
|
|
|
|
def _pdir(obj, str = None, fOverloaded = 0, width = None,
|
|
fTruncate = 1, lineWidth = 75, wantPrivate = 0):
|
|
"""
|
|
Print out a formatted list of members and methods of an instance or class
|
|
"""
|
|
def printHeader(name):
|
|
name = ' ' + name + ' '
|
|
length = len(name)
|
|
if length < 70:
|
|
padBefore = int((70 - length)/2.0)
|
|
padAfter = max(0,70 - length - padBefore)
|
|
header = '*' * padBefore + name + '*' * padAfter
|
|
print header
|
|
print
|
|
def printInstanceHeader(i, printHeader = printHeader):
|
|
printHeader(i.__class__.__name__ + ' INSTANCE INFO')
|
|
def printClassHeader(c, printHeader = printHeader):
|
|
printHeader(c.__name__ + ' CLASS INFO')
|
|
def printDictionaryHeader(d, printHeader = printHeader):
|
|
printHeader('DICTIONARY INFO')
|
|
# Print Header
|
|
if type(obj) == types.InstanceType:
|
|
printInstanceHeader(obj)
|
|
elif type(obj) == types.ClassType:
|
|
printClassHeader(obj)
|
|
elif type (obj) == types.DictionaryType:
|
|
printDictionaryHeader(obj)
|
|
# Get dict
|
|
if type(obj) == types.DictionaryType:
|
|
dict = obj
|
|
else:
|
|
dict = obj.__dict__
|
|
# Adjust width
|
|
if width:
|
|
maxWidth = width
|
|
else:
|
|
maxWidth = 10
|
|
keyWidth = 0
|
|
aproposKeys = []
|
|
privateKeys = []
|
|
remainingKeys = []
|
|
for key in dict.keys():
|
|
if not width:
|
|
keyWidth = len(key)
|
|
if str:
|
|
if re.search(str, key, re.I):
|
|
aproposKeys.append(key)
|
|
if (not width) and (keyWidth > maxWidth):
|
|
maxWidth = keyWidth
|
|
else:
|
|
if key[:1] == '_':
|
|
if wantPrivate:
|
|
privateKeys.append(key)
|
|
if (not width) and (keyWidth > maxWidth):
|
|
maxWidth = keyWidth
|
|
else:
|
|
remainingKeys.append(key)
|
|
if (not width) and (keyWidth > maxWidth):
|
|
maxWidth = keyWidth
|
|
# Sort appropriate keys
|
|
if str:
|
|
aproposKeys.sort()
|
|
else:
|
|
privateKeys.sort()
|
|
remainingKeys.sort()
|
|
# Print out results
|
|
if wantPrivate:
|
|
keys = aproposKeys + privateKeys + remainingKeys
|
|
else:
|
|
keys = aproposKeys + remainingKeys
|
|
format = '%-' + `maxWidth` + 's'
|
|
for key in keys:
|
|
value = dict[key]
|
|
if callable(value):
|
|
strvalue = `Signature(value)`
|
|
else:
|
|
strvalue = `value`
|
|
if fTruncate:
|
|
# Cut off line (keeping at least 1 char)
|
|
strvalue = strvalue[:max(1,lineWidth - maxWidth)]
|
|
print (format % key)[:maxWidth] + '\t' + strvalue
|
|
|
|
# Magic numbers: These are the bit masks in func_code.co_flags that
|
|
# reveal whether or not the function has a *arg or **kw argument.
|
|
_POS_LIST = 4
|
|
_KEY_DICT = 8
|
|
|
|
def _is_variadic(function):
|
|
return function.func_code.co_flags & _POS_LIST
|
|
|
|
def _has_keywordargs(function):
|
|
return function.func_code.co_flags & _KEY_DICT
|
|
|
|
def _varnames(function):
|
|
return function.func_code.co_varnames
|
|
|
|
def _getcode(f):
|
|
"""
|
|
_getcode(f)
|
|
This function returns the name and function object of a callable
|
|
object.
|
|
"""
|
|
def method_get(f):
|
|
return f.__name__, f.im_func
|
|
def function_get(f):
|
|
return f.__name__, f
|
|
def instance_get(f):
|
|
if hasattr(f, '__call__'):
|
|
method = f.__call__
|
|
if (type(method) == types.MethodType):
|
|
func = method.im_func
|
|
else:
|
|
func = method
|
|
return ("%s%s" % (f.__class__.__name__, '__call__'), func)
|
|
else:
|
|
s = ("Instance %s of class %s does not have a __call__ method" %
|
|
(f, f.__class__.__name__))
|
|
raise TypeError, s
|
|
def class_get(f):
|
|
if hasattr(f, '__init__'):
|
|
return f.__name__, f.__init__.im_func
|
|
else:
|
|
return f.__name__, lambda: None
|
|
codedict = { types.UnboundMethodType: method_get,
|
|
types.MethodType : method_get,
|
|
types.FunctionType : function_get,
|
|
types.InstanceType : instance_get,
|
|
types.ClassType : class_get,
|
|
}
|
|
try:
|
|
return codedict[type(f)](f)
|
|
except KeyError:
|
|
if callable(f): # eg, built-in functions and methods
|
|
# raise ValueError, "type %s not supported yet." % type(f)
|
|
return f.__name__, None
|
|
else:
|
|
raise TypeError, ("object %s of type %s is not callable." %
|
|
(f, type(f)))
|
|
|
|
class Signature:
|
|
def __init__(self, func):
|
|
self.type = type(func)
|
|
self.name, self.func = _getcode(func)
|
|
def ordinary_args(self):
|
|
n = self.func.func_code.co_argcount
|
|
return _varnames(self.func)[0:n]
|
|
def special_args(self):
|
|
n = self.func.func_code.co_argcount
|
|
x = {}
|
|
#
|
|
if _is_variadic(self.func):
|
|
x['positional'] = _varnames(self.func)[n]
|
|
if _has_keywordargs(self.func):
|
|
x['keyword'] = _varnames(self.func)[n+1]
|
|
elif _has_keywordargs(self.func):
|
|
x['keyword'] = _varnames(self.func)[n]
|
|
else:
|
|
pass
|
|
return x
|
|
def full_arglist(self):
|
|
base = list(self.ordinary_args())
|
|
x = self.special_args()
|
|
if x.has_key('positional'):
|
|
base.append(x['positional'])
|
|
if x.has_key('keyword'):
|
|
base.append(x['keyword'])
|
|
return base
|
|
def defaults(self):
|
|
defargs = self.func.func_defaults
|
|
args = self.ordinary_args()
|
|
mapping = {}
|
|
if defargs is not None:
|
|
for i in range(-1, -(len(defargs)+1), -1):
|
|
mapping[args[i]] = defargs[i]
|
|
else:
|
|
pass
|
|
return mapping
|
|
def __repr__(self):
|
|
if self.func:
|
|
defaults = self.defaults()
|
|
specials = self.special_args()
|
|
l = []
|
|
for arg in self.ordinary_args():
|
|
if defaults.has_key(arg):
|
|
l.append( arg + '=' + str(defaults[arg]) )
|
|
else:
|
|
l.append( arg )
|
|
if specials.has_key('positional'):
|
|
l.append( '*' + specials['positional'] )
|
|
if specials.has_key('keyword'):
|
|
l.append( '**' + specials['keyword'] )
|
|
return "%s(%s)" % (self.name, string.join(l, ', '))
|
|
else:
|
|
return "%s(?)" % self.name
|
|
|
|
|
|
def aproposAll(obj):
|
|
"""
|
|
Print out a list of all members and methods (including overloaded methods)
|
|
of an instance or class
|
|
"""
|
|
apropos(obj, fOverloaded = 1, fTruncate = 0)
|
|
|
|
def doc(obj):
|
|
if (isinstance(obj, types.MethodType)) or \
|
|
(isinstance(obj, types.FunctionType)):
|
|
print obj.__doc__
|
|
|
|
def adjust(command = None, dim = 1, parent = None, **kw):
|
|
"""
|
|
adjust(command = None, parent = None, **kw)
|
|
Popup and entry scale to adjust a parameter
|
|
|
|
Accepts any Slider keyword argument. Typical arguments include:
|
|
command: The one argument command to execute
|
|
min: The min value of the slider
|
|
max: The max value of the slider
|
|
resolution: The resolution of the slider
|
|
text: The label on the slider
|
|
|
|
These values can be accessed and/or changed after the fact
|
|
>>> vg = adjust()
|
|
>>> vg['min']
|
|
0.0
|
|
>>> vg['min'] = 10.0
|
|
>>> vg['min']
|
|
10.0
|
|
"""
|
|
# Make sure we enable Tk
|
|
import Valuator
|
|
# Set command if specified
|
|
if command:
|
|
kw['command'] = lambda x: apply(command, x)
|
|
if parent is None:
|
|
kw['title'] = command.__name__
|
|
kw['dim'] = dim
|
|
# Create toplevel if needed
|
|
if not parent:
|
|
vg = apply(Valuator.ValuatorGroupPanel, (parent,), kw)
|
|
else:
|
|
vg = apply(Valuator.ValuatorGroup,(parent,), kw)
|
|
vg.pack(expand = 1, fill = 'x')
|
|
return vg
|
|
|
|
def intersection(a, b):
|
|
"""
|
|
intersection(list, list):
|
|
"""
|
|
if not a: return []
|
|
if not b: return []
|
|
d = []
|
|
for i in a:
|
|
if (i in b) and (i not in d):
|
|
d.append(i)
|
|
for i in b:
|
|
if (i in a) and (i not in d):
|
|
d.append(i)
|
|
return d
|
|
|
|
def union(a, b):
|
|
"""
|
|
union(list, list):
|
|
"""
|
|
# Copy a
|
|
c = a[:]
|
|
for i in b:
|
|
if (i not in c):
|
|
c.append(i)
|
|
return c
|
|
|
|
def sameElements(a, b):
|
|
if len(a) != len(b):
|
|
return 0
|
|
for elem in a:
|
|
if elem not in b:
|
|
return 0
|
|
for elem in b:
|
|
if elem not in a:
|
|
return 0
|
|
return 1
|
|
|
|
def list2dict(L, value=None):
|
|
"""creates dict using elements of list, all assigned to same value"""
|
|
return dict([(k,value) for k in L])
|
|
|
|
def uniqueElements(L):
|
|
"""are all elements of list unique?"""
|
|
return len(L) == len(list2dict(L))
|
|
|
|
def contains(whole, sub):
|
|
"""
|
|
Return 1 if whole contains sub, 0 otherwise
|
|
"""
|
|
if (whole == sub):
|
|
return 1
|
|
for elem in sub:
|
|
# The first item you find not in whole, return 0
|
|
if elem not in whole:
|
|
return 0
|
|
# If you got here, whole must contain sub
|
|
return 1
|
|
|
|
def replace(list, old, new, all=0):
|
|
"""
|
|
replace 'old' with 'new' in 'list'
|
|
if all == 0, replace first occurrence
|
|
otherwise replace all occurrences
|
|
returns the number of items replaced
|
|
"""
|
|
if old not in list:
|
|
return 0
|
|
|
|
if not all:
|
|
i = list.index(old)
|
|
list[i] = new
|
|
return 1
|
|
else:
|
|
numReplaced = 0
|
|
for i in xrange(len(list)):
|
|
if list[i] == old:
|
|
numReplaced += 1
|
|
list[i] = new
|
|
return numReplaced
|
|
|
|
def reduceAngle(deg):
|
|
"""
|
|
Reduces an angle (in degrees) to a value in [-180..180)
|
|
"""
|
|
return (((deg + 180.) % 360.) - 180.)
|
|
|
|
def fitSrcAngle2Dest(src, dest):
|
|
"""
|
|
given a src and destination angle, returns an equivalent src angle
|
|
that is within [-180..180) of dest
|
|
examples:
|
|
fitSrcAngle2Dest(30,60) == 30
|
|
fitSrcAngle2Dest(60,30) == 60
|
|
fitSrcAngle2Dest(0,180) == 0
|
|
fitSrcAngle2Dest(-1,180) == 359
|
|
fitSrcAngle2Dest(-180,180) == 180
|
|
"""
|
|
return dest + reduceAngle(src - dest)
|
|
|
|
def fitDestAngle2Src(src, dest):
|
|
"""
|
|
given a src and destination angle, returns an equivalent dest angle
|
|
that is within [-180..180) of src
|
|
examples:
|
|
fitDestAngle2Src(30,60) == 60
|
|
fitDestAngle2Src(60,30) == 30
|
|
fitDestAngle2Src(0,180) == -180
|
|
fitDestAngle2Src(1,180) == 180
|
|
"""
|
|
return src + (reduceAngle(dest - src))
|
|
|
|
def closestDestAngle2(src, dest):
|
|
# The function above didn't seem to do what I wanted. So I hacked
|
|
# this one together. I can't really say I understand it. It's more
|
|
# from impirical observation... GRW
|
|
diff = src - dest
|
|
# if the difference is greater that 180 it's shorter to go the other way
|
|
if diff > 180:
|
|
return dest - 360
|
|
# or perhaps the OTHER other way...
|
|
elif diff < -180:
|
|
return dest + 360
|
|
# otherwise just go to the original destination
|
|
else:
|
|
return dest
|
|
|
|
def closestDestAngle(src, dest):
|
|
# The function above didn't seem to do what I wanted. So I hacked
|
|
# this one together. I can't really say I understand it. It's more
|
|
# from impirical observation... GRW
|
|
diff = src - dest
|
|
# if the difference is greater that 180 it's shorter to go the other way
|
|
if diff > 180:
|
|
return src - (diff - 360)
|
|
# or perhaps the OTHER other way...
|
|
elif diff < -180:
|
|
return src - (360 + diff)
|
|
# otherwise just go to the original destination
|
|
else:
|
|
return dest
|
|
|
|
|
|
def binaryRepr(number, max_length = 32):
|
|
# This will only work reliably for relatively small numbers.
|
|
# Increase the value of max_length if you think you're going
|
|
# to use long integers
|
|
assert number < 2L << max_length
|
|
shifts = map (operator.rshift, max_length * [number], \
|
|
range (max_length - 1, -1, -1))
|
|
digits = map (operator.mod, shifts, max_length * [2])
|
|
if not digits.count (1): return 0
|
|
digits = digits [digits.index (1):]
|
|
return string.join (map (repr, digits), '')
|
|
|
|
# constant profile defaults
|
|
PyUtilProfileDefaultFilename = 'profiledata'
|
|
PyUtilProfileDefaultLines = 80
|
|
PyUtilProfileDefaultSorts = ['cumulative', 'time', 'calls']
|
|
|
|
# call this from the prompt, and break back out to the prompt
|
|
# to stop profiling
|
|
#
|
|
# OR to do inline profiling, you must make a globally-visible
|
|
# function to be profiled, i.e. to profile 'self.load()', do
|
|
# something like this:
|
|
#
|
|
# def func(self=self):
|
|
# self.load()
|
|
# import __builtin__
|
|
# __builtin__.func = func
|
|
# PythonUtil.startProfile(cmd='func()', filename='loadProfile')
|
|
# del __builtin__.func
|
|
#
|
|
def startProfile(filename=PyUtilProfileDefaultFilename,
|
|
lines=PyUtilProfileDefaultLines,
|
|
sorts=PyUtilProfileDefaultSorts,
|
|
silent=0,
|
|
cmd='run()'):
|
|
import profile
|
|
profile.run(cmd, filename)
|
|
if not silent:
|
|
printProfile(filename, lines, sorts)
|
|
|
|
# call this to see the results again
|
|
def printProfile(filename=PyUtilProfileDefaultFilename,
|
|
lines=PyUtilProfileDefaultLines,
|
|
sorts=PyUtilProfileDefaultSorts,):
|
|
import pstats
|
|
s = pstats.Stats(filename)
|
|
s.strip_dirs()
|
|
for sort in sorts:
|
|
s.sort_stats(sort)
|
|
s.print_stats(lines)
|
|
|
|
class Functor:
|
|
def __init__(self, function, *args, **kargs):
|
|
assert callable(function), "function should be a callable obj"
|
|
self._function = function
|
|
self._args = args
|
|
self._kargs = kargs
|
|
self.__name__ = 'Functor: %s' % self._function.__name__
|
|
self.__doc__ = self._function.__doc__
|
|
|
|
def __call__(self, *args, **kargs):
|
|
"""call function"""
|
|
_args = list(self._args)
|
|
_args.extend(args)
|
|
_kargs = self._kargs.copy()
|
|
_kargs.update(kargs)
|
|
return apply(self._function,_args,_kargs)
|
|
|
|
def bound(value, bound1, bound2):
|
|
"""
|
|
returns value if value is between bound1 and bound2
|
|
otherwise returns bound that is closer to value
|
|
"""
|
|
if bound1 > bound2:
|
|
return min(max(value, bound2), bound1)
|
|
else:
|
|
return min(max(value, bound1), bound2)
|
|
|
|
def lerp(v0, v1, t):
|
|
"""
|
|
returns a value lerped between v0 and v1, according to t
|
|
t == 0 maps to v0, t == 1 maps to v1
|
|
"""
|
|
return v0 + (t * (v1 - v0))
|
|
|
|
def boolEqual(a, b):
|
|
"""
|
|
returns true if a and b are both true or both false.
|
|
returns false otherwise
|
|
"""
|
|
return (a and b) or not (a or b)
|
|
|
|
def lineupPos(i, num, spacing):
|
|
"""
|
|
use to line up a series of 'num' objects, in one dimension,
|
|
centered around zero
|
|
'i' is the index of the object in the lineup
|
|
'spacing' is the amount of space between objects in the lineup
|
|
"""
|
|
assert num >= 1
|
|
assert i >= 0 and i < num
|
|
pos = float(i) * spacing
|
|
return pos - ((float(spacing) * (num-1))/2.)
|
|
|
|
def formatElapsedSeconds(seconds):
|
|
"""
|
|
Returns a string of the form "mm:ss" or "hh:mm:ss" or "n days",
|
|
representing the indicated elapsed time in seconds.
|
|
"""
|
|
sign = ''
|
|
if seconds < 0:
|
|
seconds = -seconds
|
|
sign = '-'
|
|
|
|
# We use math.floor() instead of casting to an int, so we avoid
|
|
# problems with numbers that are too large to represent as
|
|
# type int.
|
|
seconds = math.floor(seconds)
|
|
hours = math.floor(seconds / (60 * 60))
|
|
if hours > 36:
|
|
days = math.floor((hours + 12) / 24)
|
|
return "%s%d days" % (sign, days)
|
|
|
|
seconds -= hours * (60 * 60)
|
|
minutes = (int)(seconds / 60)
|
|
seconds -= minutes * 60
|
|
if hours != 0:
|
|
return "%s%d:%02d:%02d" % (sign, hours, minutes, seconds)
|
|
else:
|
|
return "%s%d:%02d" % (sign, minutes, seconds)
|
|
|
|
def solveQuadratic(a, b, c):
|
|
# quadratic equation: ax^2 + bx + c = 0
|
|
# quadratic formula: x = [-b +/- sqrt(b^2 - 4ac)] / 2a
|
|
# returns None, root, or [root1, root2]
|
|
|
|
# a cannot be zero.
|
|
if a == 0.:
|
|
return None
|
|
|
|
# calculate the determinant (b^2 - 4ac)
|
|
D = (b * b) - (4. * a * c)
|
|
|
|
if D < 0:
|
|
# there are no solutions (sqrt(negative number) is undefined)
|
|
return None
|
|
elif D == 0:
|
|
# only one root
|
|
return (-b) / (2. * a)
|
|
else:
|
|
# OK, there are two roots
|
|
sqrtD = math.sqrt(D)
|
|
twoA = 2. * a
|
|
root1 = ((-b) - sqrtD) / twoA
|
|
root2 = ((-b) + sqrtD) / twoA
|
|
return [root1, root2]
|
|
|
|
def stackEntryInfo(depth=0, baseFileName=1):
|
|
"""
|
|
returns the sourcefilename, line number, and function name of
|
|
an entry in the stack.
|
|
'depth' is how far back to go in the stack; 0 is the caller of this
|
|
function, 1 is the function that called the caller of this function, etc.
|
|
by default, strips off the path of the filename; override with baseFileName
|
|
returns (fileName, lineNum, funcName) --> (string, int, string)
|
|
returns (None, None, None) on error
|
|
"""
|
|
try:
|
|
stack = None
|
|
frame = None
|
|
try:
|
|
stack = inspect.stack()
|
|
# add one to skip the frame associated with this function
|
|
frame = stack[depth+1]
|
|
filename = frame[1]
|
|
if baseFileName:
|
|
filename = os.path.basename(filename)
|
|
lineNum = frame[2]
|
|
funcName = frame[3]
|
|
result = (filename, lineNum, funcName)
|
|
finally:
|
|
del stack
|
|
del frame
|
|
except:
|
|
result = (None, None, None)
|
|
|
|
return result
|
|
|
|
def lineInfo(baseFileName=1):
|
|
"""
|
|
returns the sourcefilename, line number, and function name of the
|
|
code that called this function
|
|
(answers the question: 'hey lineInfo, where am I in the codebase?')
|
|
see stackEntryInfo, above, for info on 'baseFileName' and return types
|
|
"""
|
|
return stackEntryInfo(1)
|
|
|
|
def callerInfo(baseFileName=1):
|
|
"""
|
|
returns the sourcefilename, line number, and function name of the
|
|
caller of the function that called this function
|
|
(answers the question: 'hey callerInfo, who called me?')
|
|
see stackEntryInfo, above, for info on 'baseFileName' and return types
|
|
"""
|
|
return stackEntryInfo(2)
|
|
|
|
def lineTag(baseFileName=1, verbose=0, separator=':'):
|
|
"""
|
|
returns a string containing the sourcefilename and line number
|
|
of the code that called this function
|
|
(equivalent to lineInfo, above, with different return type)
|
|
see stackEntryInfo, above, for info on 'baseFileName'
|
|
|
|
if 'verbose' is false, returns a compact string of the form
|
|
'fileName:lineNum:funcName'
|
|
if 'verbose' is true, returns a longer string that matches the
|
|
format of Python stack trace dumps
|
|
|
|
returns empty string on error
|
|
"""
|
|
fileName, lineNum, funcName = callerInfo()
|
|
if fileName is None:
|
|
return ''
|
|
if verbose:
|
|
return 'File "%s", line %s, in %s' % (fileName, lineNum, funcName)
|
|
else:
|
|
return '%s%s%s%s%s' % (fileName, separator, lineNum, separator,
|
|
funcName)
|
|
|
|
def findPythonModule(module):
|
|
# Look along the python load path for the indicated filename.
|
|
# Returns the located pathname, or None if the filename is not
|
|
# found.
|
|
filename = module + '.py'
|
|
for dir in sys.path:
|
|
pathname = os.path.join(dir, filename)
|
|
if os.path.exists(pathname):
|
|
return pathname
|
|
|
|
return None
|
|
|
|
def describeException(backTrace = 4):
|
|
# When called in an exception handler, returns a string describing
|
|
# the current exception.
|
|
|
|
def byteOffsetToLineno(code, byte):
|
|
# Returns the source line number corresponding to the given byte
|
|
# offset into the indicated Python code module.
|
|
|
|
import array
|
|
lnotab = array.array('B', code.co_lnotab)
|
|
|
|
line = code.co_firstlineno
|
|
for i in range(0, len(lnotab),2):
|
|
byte -= lnotab[i]
|
|
if byte <= 0:
|
|
return line
|
|
line += lnotab[i+1]
|
|
|
|
return line
|
|
|
|
infoArr = sys.exc_info()
|
|
exception = infoArr[0]
|
|
exceptionName = getattr(exception, '__name__', None)
|
|
extraInfo = infoArr[1]
|
|
trace = infoArr[2]
|
|
|
|
stack = []
|
|
while trace.tb_next:
|
|
# We need to call byteOffsetToLineno to determine the true
|
|
# line number at which the exception occurred, even though we
|
|
# have both trace.tb_lineno and frame.f_lineno, which return
|
|
# the correct line number only in non-optimized mode.
|
|
frame = trace.tb_frame
|
|
module = frame.f_globals.get('__name__', None)
|
|
lineno = byteOffsetToLineno(frame.f_code, frame.f_lasti)
|
|
stack.append("%s:%s, " % (module, lineno))
|
|
trace = trace.tb_next
|
|
|
|
frame = trace.tb_frame
|
|
module = frame.f_globals.get('__name__', None)
|
|
lineno = byteOffsetToLineno(frame.f_code, frame.f_lasti)
|
|
stack.append("%s:%s, " % (module, lineno))
|
|
|
|
description = ""
|
|
for i in range(len(stack) - 1, max(len(stack) - backTrace, 0) - 1, -1):
|
|
description += stack[i]
|
|
|
|
description += "%s: %s" % (exceptionName, extraInfo)
|
|
return description
|
|
|
|
class PureVirtual:
|
|
""" Python classes that want to have C++-style pure-virtual functions
|
|
can derive from this class and call 'derivedMustOverride' from their
|
|
'pure virtual' methods. Such Python classes can still be instantiated,
|
|
but they will give an informative error when a pure-virtual method
|
|
is invoked. """
|
|
def derivedMustOverride(self):
|
|
""" call this function from methods that must be overriden
|
|
and are not meant to be chained down to. This simulates C++
|
|
pure-virtual methods. """
|
|
raise 'error: derived class must implement %s' % callerInfo()[2]
|
|
|
|
def mostDerivedLast(classList):
|
|
"""pass in list of classes. sorts list in-place, with derived classes
|
|
appearing after their bases"""
|
|
def compare(a,b):
|
|
if a is b:
|
|
return 0
|
|
if issubclass(a,b):
|
|
return 1
|
|
return -1
|
|
classList.sort(compare)
|