panda3d/direct/src/showbase/PythonUtil.py
Darren Ranalli 4e36337007 added bound
2002-11-27 19:11:41 +00:00

509 lines
15 KiB
Python

import types
import string
import re
import math
import operator
def ifAbsentPut(dict, key, newValue):
"""
If dict has key, return the value, otherwise insert the newValue and return it
"""
if dict.has_key(key):
return dict[key]
else:
dict[key] = newValue
return newValue
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 it 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 []
c = a + b
d = []
for i in c:
if (i in a) and (i in b):
# make it unique, like a set
if (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 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
def startProfile(filename=PyUtilProfileDefaultFilename,
lines=PyUtilProfileDefaultLines,
sorts=PyUtilProfileDefaultSorts,
silent=0):
import profile
profile.run('run()', 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
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)