00001 """
00002 Utilities for rootplot including histogram classes.
00003 """
00004
00005 __license__ = '''\
00006 Copyright (c) 2009-2010 Jeff Klukas <klukas@wisc.edu>
00007
00008 Permission is hereby granted, free of charge, to any person obtaining a copy
00009 of this software and associated documentation files (the "Software"), to deal
00010 in the Software without restriction, including without limitation the rights
00011 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
00012 copies of the Software, and to permit persons to whom the Software is
00013 furnished to do so, subject to the following conditions:
00014
00015 The above copyright notice and this permission notice shall be included in
00016 all copies or substantial portions of the Software.
00017
00018 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00019 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00020 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
00021 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
00022 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
00023 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
00024 THE SOFTWARE.
00025 '''
00026
00027
00028
00029 import math
00030 import ROOT
00031 import re
00032 import copy
00033 import array
00034 import os.path
00035 import sys
00036 import fnmatch
00037 from random import gauss
00038
00039
00040
00041 class Hist2D(object):
00042 """A container to hold the parameters from a 2D ROOT histogram."""
00043 def __init__(self, hist, label="__nolabel__", title=None,
00044 xlabel=None, ylabel=None):
00045 try:
00046 if not hist.InheritsFrom("TH2"):
00047 raise TypeError("%s does not inherit from TH2" % hist)
00048 except:
00049 raise TypeError("%s is not a ROOT object" % hist)
00050 self.rootclass = hist.ClassName()
00051 self.name = hist.GetName()
00052 self.nbinsx = nx = hist.GetNbinsX()
00053 self.nbinsy = ny = hist.GetNbinsY()
00054 self.binlabelsx = process_bin_labels([hist.GetXaxis().GetBinLabel(i)
00055 for i in range(1, nx + 1)])
00056 if self.binlabelsx:
00057 self.nbinsx = nx = self.binlabelsx.index('')
00058 self.binlabelsx = self.binlabelsx[:ny]
00059 self.binlabelsy = process_bin_labels([hist.GetYaxis().GetBinLabel(i)
00060 for i in range(1, ny + 1)])
00061 if self.binlabelsy:
00062 self.nbinsy = ny = self.binlabelsy.index('')
00063 self.binlabelsy = self.binlabelsy[:ny]
00064 self.entries = hist.GetEntries()
00065 self.content = [[hist.GetBinContent(i, j) for i in range(1, nx + 1)]
00066 for j in range(1, ny + 1)]
00067 self.xedges = [hist.GetXaxis().GetBinLowEdge(i)
00068 for i in range(1, nx + 2)]
00069 self.yedges = [hist.GetYaxis().GetBinLowEdge(i)
00070 for i in range(1, ny + 2)]
00071 self.x = [(self.xedges[i+1] + self.xedges[i])/2
00072 for i in range(nx)]
00073 self.y = [(self.yedges[i+1] + self.yedges[i])/2
00074 for i in range(ny)]
00075 self.title = title or hist.GetTitle()
00076 self.xlabel = xlabel or hist.GetXaxis().GetTitle()
00077 self.ylabel = ylabel or hist.GetYaxis().GetTitle()
00078 self.label = label
00079 def _flat_content(self):
00080 flatcontent = []
00081 for row in self.content:
00082 flatcontent += row
00083 return flatcontent
00084 def __getitem__(self, index):
00085 """Return contents of indexth bin: x.__getitem__(y) <==> x[y]"""
00086 return self._flat_content()[index]
00087 def __len__(self):
00088 """Return the number of bins: x.__len__() <==> len(x)"""
00089 return len(self._flat_content())
00090 def __iter__(self):
00091 """Iterate through bins: x.__iter__() <==> iter(x)"""
00092 return iter(self._flat_content())
00093 def TH2F(self, name=""):
00094 """Return a ROOT.TH2F object with contents of this Hist2D."""
00095 th2f = ROOT.TH2F(name, "",
00096 self.nbinsx, array.array('f', self.xedges),
00097 self.nbinsy, array.array('f', self.yedges))
00098 th2f.SetTitle("%s;%s;%s" % (self.title, self.xlabel, self.ylabel))
00099 for ix in range(self.nbinsx):
00100 for iy in range(self.nbinsy):
00101 th2f.SetBinContent(ix + 1, iy + 1, self.content[iy][ix])
00102 return th2f
00103
00104 class Hist(object):
00105 """A container to hold the parameters from a ROOT histogram."""
00106 def __init__(self, hist, label="__nolabel__",
00107 name=None, title=None, xlabel=None, ylabel=None):
00108 try:
00109 hist.GetNbinsX()
00110 self.__init_TH1(hist)
00111 except AttributeError:
00112 try:
00113 hist.GetN()
00114 self.__init_TGraph(hist)
00115 except AttributeError:
00116 raise TypeError("%s is not a 1D histogram or TGraph" % hist)
00117 self.rootclass = hist.ClassName()
00118 self.name = name or hist.GetName()
00119 self.title = title or hist.GetTitle().split(';')[0]
00120 self.xlabel = xlabel or hist.GetXaxis().GetTitle()
00121 self.ylabel = ylabel or hist.GetYaxis().GetTitle()
00122 self.label = label
00123 def __init_TH1(self, hist):
00124 self.nbins = n = hist.GetNbinsX()
00125 self.binlabels = process_bin_labels([hist.GetXaxis().GetBinLabel(i)
00126 for i in range(1, n + 1)])
00127 if self.binlabels and '' in self.binlabels:
00128
00129 self.nbins = n = self.binlabels.index('')
00130 self.binlabels = self.binlabels[:n]
00131 self.entries = hist.GetEntries()
00132 self.xedges = [hist.GetBinLowEdge(i) for i in range(1, n + 2)]
00133 self.x = [(self.xedges[i+1] + self.xedges[i])/2 for i in range(n)]
00134 self.xerr = [(self.xedges[i+1] - self.xedges[i])/2 for i in range(n)]
00135 self.xerr = [self.xerr[:], self.xerr[:]]
00136 self.width = [(self.xedges[i+1] - self.xedges[i]) for i in range(n)]
00137 self.y = [hist.GetBinContent(i) for i in range(1, n + 1)]
00138 self.yerr = [hist.GetBinError( i) for i in range(1, n + 1)]
00139 self.yerr = [self.yerr[:], self.yerr[:]]
00140 self.underflow = hist.GetBinContent(0)
00141 self.overflow = hist.GetBinContent(self.nbins + 1)
00142 def __init_TGraph(self, hist):
00143 self.nbins = n = hist.GetN()
00144 self.x, self.y = [], []
00145 x, y = ROOT.Double(0), ROOT.Double(0)
00146 for i in range(n):
00147 hist.GetPoint(i, x, y)
00148 self.x.append(copy.copy(x))
00149 self.y.append(copy.copy(y))
00150 lower = [max(0, hist.GetErrorXlow(i)) for i in xrange(n)]
00151 upper = [max(0, hist.GetErrorXhigh(i)) for i in xrange(n)]
00152 self.xerr = [lower[:], upper[:]]
00153 lower = [max(0, hist.GetErrorYlow(i)) for i in xrange(n)]
00154 upper = [max(0, hist.GetErrorYhigh(i)) for i in xrange(n)]
00155 self.yerr = [lower[:], upper[:]]
00156 self.xedges = [self.x[i] - self.xerr[0][i] for i in xrange(n)]
00157 self.xedges.append(self.x[n - 1] + self.xerr[1][n - 1])
00158 self.width = [self.xedges[i + 1] - self.xedges[i] for i in range(n)]
00159 self.underflow, self.overflow = 0, 0
00160 self.binlabels = None
00161 self.entries = n
00162 def __add__(self, b):
00163 """Return the sum of self and b: x.__add__(y) <==> x + y"""
00164 c = copy.copy(self)
00165 for i in range(len(self)):
00166 c.y[i] += b.y[i]
00167 c.yerr[0][i] += b.yerr[0][i]
00168 c.yerr[1][i] += b.yerr[1][i]
00169 c.overflow += b.overflow
00170 c.underflow += b.underflow
00171 return c
00172 def __sub__(self, b):
00173 """Return the difference of self and b: x.__sub__(y) <==> x - y"""
00174 c = copy.copy(self)
00175 for i in range(len(self)):
00176 c.y[i] -= b.y[i]
00177 c.yerr[0][i] -= b.yerr[0][i]
00178 c.yerr[1][i] -= b.yerr[1][i]
00179 c.overflow -= b.overflow
00180 c.underflow -= b.underflow
00181 return c
00182 def __div__(self, denominator):
00183 return self.divide(denominator)
00184 def __getitem__(self, index):
00185 """Return contents of indexth bin: x.__getitem__(y) <==> x[y]"""
00186 return self.y[index]
00187 def __setitem__(self, index, value):
00188 """Set contents of indexth bin: x.__setitem__(i, y) <==> x[i]=y"""
00189 self.y[index] = value
00190 def __len__(self):
00191 """Return the number of bins: x.__len__() <==> len(x)"""
00192 return self.nbins
00193 def __iter__(self):
00194 """Iterate through bins: x.__iter__() <==> iter(x)"""
00195 return iter(self.y)
00196 def min(self, threshold=None):
00197 """Return the y-value of the bottom tip of the lowest errorbar."""
00198 vals = [(yval - yerr) for yval, yerr in zip(self.y, self.yerr[0])
00199 if (yval - yerr) > threshold]
00200 if vals:
00201 return min(vals)
00202 else:
00203 return threshold
00204 def av_xerr(self):
00205 """Return average between the upper and lower xerr."""
00206 return [(self.xerr[0][i] + self.xerr[1][i]) / 2
00207 for i in range(self.nbins)]
00208 def av_yerr(self):
00209 """Return average between the upper and lower yerr."""
00210 return [(self.yerr[0][i] + self.yerr[1][i]) / 2
00211 for i in range(self.nbins)]
00212 def scale(self, factor):
00213 """
00214 Scale contents, errors, and over/underflow by the given scale factor.
00215 """
00216 self.y = [x * factor for x in self.y]
00217 self.yerr[0] = [x * factor for x in self.yerr[0]]
00218 self.yerr[1] = [x * factor for x in self.yerr[1]]
00219 self.overflow *= factor
00220 self.underflow *= factor
00221 def delete_bin(self, index):
00222 """
00223 Delete a the contents of a bin, sliding all the other data one bin to
00224 the left. This can be useful for histograms with labeled bins.
00225 """
00226 self.nbins -= 1
00227 self.xedges.pop()
00228 self.x.pop()
00229 self.width.pop()
00230 self.y.pop(index)
00231 self.xerr[0].pop(index)
00232 self.xerr[1].pop(index)
00233 self.yerr[0].pop(index)
00234 self.yerr[1].pop(index)
00235 if self.binlabels:
00236 self.binlabels.pop(index)
00237 def TH1F(self, name=None):
00238 """Return a ROOT.TH1F object with contents of this Hist"""
00239 th1f = ROOT.TH1F(name or self.name, "", self.nbins,
00240 array.array('f', self.xedges))
00241 th1f.Sumw2()
00242 th1f.SetTitle("%s;%s;%s" % (self.title, self.xlabel, self.ylabel))
00243 for i in range(self.nbins):
00244 th1f.SetBinContent(i + 1, self.y[i])
00245 try:
00246 th1f.SetBinError(i + 1, (self.yerr[0][i] + self.yerr[1][i]) / 2)
00247 except TypeError:
00248 th1f.SetBinError(i + 1, self.yerr[i])
00249 if self.binlabels:
00250 th1f.GetXaxis().SetBinLabel(i + 1, self.binlabels[i])
00251 th1f.SetBinContent(0, self.underflow)
00252 th1f.SetBinContent(self.nbins + 2, self.overflow)
00253 th1f.SetEntries(self.entries)
00254 return th1f
00255 def TGraph(self, name=None):
00256 """Return a ROOT.TGraphAsymmErrors object with contents of this Hist"""
00257 x = array.array('f', self.x)
00258 y = array.array('f', self.y)
00259 xl = array.array('f', self.xerr[0])
00260 xh = array.array('f', self.xerr[1])
00261 yl = array.array('f', self.yerr[0])
00262 yh = array.array('f', self.yerr[1])
00263 tgraph = ROOT.TGraphAsymmErrors(self.nbins, x, y, xl, xh, yl, yh)
00264 tgraph.SetName(name or self.name)
00265 tgraph.SetTitle('%s;%s;%s' % (self.title, self.xlabel, self.ylabel))
00266 return tgraph
00267 def divide(self, denominator):
00268 """
00269 Return the simple quotient with errors added in quadrature.
00270
00271 This function is called by the division operator:
00272 hist3 = hist1.divide_wilson(hist2) <--> hist3 = hist1 / hist2
00273 """
00274 if len(self) != len(denominator):
00275 raise TypeError("Cannot divide %s with %i bins by "
00276 "%s with %i bins." %
00277 (denominator.name, len(denominator),
00278 self.name, len(self)))
00279 quotient = copy.deepcopy(self)
00280 num_yerr = self.av_yerr()
00281 den_yerr = denominator.av_yerr()
00282 quotient.yerr = [0. for i in range(self.nbins)]
00283 for i in range(self.nbins):
00284 if denominator[i] == 0 or self[i] == 0:
00285 quotient.y[i] = 0.
00286 else:
00287 quotient.y[i] = self[i] / denominator[i]
00288 quotient.yerr[i] = quotient[i]
00289 quotient.yerr[i] *= math.sqrt((num_yerr[i] / self[i]) ** 2 +
00290 (den_yerr[i] / denominator[i]) ** 2)
00291 if quotient.yerr[i] > quotient[i]:
00292 quotient.yerr[i] = quotient[i]
00293 quotient.yerr = [quotient.yerr, quotient.yerr]
00294 return quotient
00295 def divide_wilson(self, denominator):
00296 """Return an efficiency plot with Wilson score interval errors."""
00297 if len(self) != len(denominator):
00298 raise TypeError("Cannot divide %s with %i bins by "
00299 "%s with %i bins." %
00300 (denominator.name, len(denominator),
00301 self.name, len(self)))
00302 eff, upper_err, lower_err = wilson_interval(self.y, denominator.y)
00303 quotient = copy.deepcopy(self)
00304 quotient.y = eff
00305 quotient.yerr = [lower_err, upper_err]
00306 return quotient
00307
00308 class HistStack(object):
00309 """
00310 A container to hold Hist objects for plotting together.
00311
00312 When plotting, the title and the x and y labels of the last Hist added
00313 will be used unless specified otherwise in the constructor.
00314 """
00315 def __init__(self, hists=None, title=None, xlabel=None, ylabel=None):
00316 self.hists = []
00317 self.kwargs = []
00318 self.title = title
00319 self.xlabel = xlabel
00320 self.ylabel = ylabel
00321 if hists:
00322 for hist in hists:
00323 self.add(hist)
00324 def __getitem__(self, index):
00325 """Return indexth hist: x.__getitem__(y) <==> x[y]"""
00326 return self.hists[index]
00327 def __setitem__(self, index, value):
00328 """Replace indexth hist with value: x.__setitem__(i, y) <==> x[i]=y"""
00329 self.hists[index] = value
00330 def __len__(self):
00331 """Return the number of hists in the stack: x.__len__() <==> len(x)"""
00332 return len(self.hists)
00333 def __iter__(self):
00334 """Iterate through hists in the stack: x.__iter__() <==> iter(x)"""
00335 return iter(self.hists)
00336 def max(self):
00337 """Return the value of the highest bin of all hists in the stack."""
00338 maxes = [max(x) for x in self.hists]
00339 try:
00340 return max(maxes)
00341 except ValueError:
00342 return 0
00343 def stackmax(self):
00344 """Return the value of the highest bin in the addition of all hists."""
00345 try:
00346 return max([sum([h[i] for h in self.hists])
00347 for i in range(self.hists[0].nbins)])
00348 except:
00349 print [h.nbins for h in self.hists]
00350 def scale(self, factor):
00351 """Scale all Hists by factor."""
00352 for hist in self.hists:
00353 hist.scale(factor)
00354 def min(self, threshold=None):
00355 """
00356 Return the value of the lowest bin of all hists in the stack.
00357
00358 If threshold is specified, only values above the threshold will be
00359 considered.
00360 """
00361 mins = [x.min(threshold) for x in self.hists]
00362 return min(mins)
00363 def add(self, hist, **kwargs):
00364 """
00365 Add a Hist object to this stack.
00366
00367 Any additional keyword arguments will be added to just this Hist
00368 when the stack is plotted.
00369 """
00370 if "label" in kwargs:
00371 hist.label = kwargs['label']
00372 del kwargs['label']
00373 if len(self) > 0:
00374 if hist.xedges != self.hists[0].xedges:
00375 raise ValueError("Cannot add %s to stack; all Hists must "
00376 "have the same binning." % hist.name)
00377 self.hists.append(hist)
00378 self.kwargs.append(kwargs)
00379
00380
00381
00382
00383 class RootFile(object):
00384 """A wrapper for TFiles, allowing easier access to methods."""
00385 def __init__(self, filename, name=None):
00386 self.filename = filename
00387 self.name = name or filename[:-5]
00388 self.file = ROOT.TFile(filename, 'read')
00389 if self.file.IsZombie():
00390 raise ValueError("Error opening %s" % filename)
00391 def cd(self, directory=''):
00392 """Make directory the current directory."""
00393 self.file.cd(directory)
00394 def get(self, object_name, path=None, type1D=Hist, type2D=Hist2D):
00395 """Return a Hist object from the given path within this file."""
00396 if not path:
00397 path = os.path.dirname(object_name)
00398 object_name = os.path.basename(object_name)
00399 try:
00400 roothist = self.file.GetDirectory(path).Get(object_name)
00401 except ReferenceError, e:
00402 raise ReferenceError(e)
00403 try:
00404 return type2D(roothist)
00405 except TypeError:
00406 return type1D(roothist)
00407
00408 def ls(directory=None):
00409 """Return a python list of ROOT object names from the given directory."""
00410 if directory == None:
00411 keys = ROOT.gDirectory.GetListOfKeys()
00412 else:
00413 keys = ROOT.gDirectory.GetDirectory(directory).GetListOfKeys()
00414 key = keys[0]
00415 names = []
00416 while key:
00417 obj = key.ReadObj()
00418 key = keys.After(key)
00419 names.append(obj.GetName())
00420 return names
00421
00422 def pwd():
00423 """Return ROOT's present working directory."""
00424 return ROOT.gDirectory.GetPath()
00425
00426 def get(object_name):
00427 """Return a Hist object with the given name."""
00428 return Hist(ROOT.gDirectory.Get(object_name))
00429
00430
00431
00432
00433 def loadROOT(batch=True):
00434
00435
00436 saved_argv = sys.argv[:]
00437 argstring = ' '.join(sys.argv)
00438 sys.argv = [sys.argv[0]]
00439 try:
00440 import ROOT
00441 except ImportError:
00442 print """\
00443 The program was unable to access PyROOT. Usually, this just requires switching
00444 to the same major version of python that used when compiling ROOT. To
00445 determine which version that is, try the following command:
00446 root -config 2>&1 | tr ' ' '\\n' | egrep 'python|PYTHON'
00447 If this is different from the python version you are currently using, try
00448 changing your PATH to point to the new one."""
00449 sys.exit(1)
00450
00451
00452 if batch:
00453 ROOT.gROOT.SetBatch()
00454 ROOT.gErrorIgnoreLevel = ROOT.kWarning
00455
00456 if os.path.exists('rootlogon.C'):
00457 ROOT.gROOT.Macro('rootlogon.C')
00458 sys.argv = saved_argv[:]
00459 return ROOT
00460
00461 def replace(string, replacements):
00462 """
00463 Modify a string based on a list of patterns and substitutions.
00464
00465 replacements should be a list of two-entry tuples, the first entry giving
00466 a string to search for and the second entry giving the string with which
00467 to replace it. If replacements includes a pattern entry containing
00468 'use_regexp', then all patterns will be treated as regular expressions
00469 using re.sub.
00470 """
00471 if not replacements:
00472 return string
00473 if 'use_regexp' in [x for x,y in replacements]:
00474 for pattern, repl in [x for x in replacements
00475 if x[0] != 'use_regexp']:
00476 string = re.sub(pattern, repl, string)
00477 else:
00478 for pattern, repl in replacements:
00479 string = string.replace(pattern, repl)
00480 if re.match(_all_whitespace_string, string):
00481 return ""
00482 return string
00483
00484 def process_bin_labels(binlabels):
00485 has_labels = False
00486 for binlabel in binlabels:
00487 if binlabel:
00488 has_labels = True
00489 if has_labels:
00490 return binlabels
00491 else:
00492 return None
00493
00494 def wilson_interval(numerator_array, denominator_array):
00495 eff, upper_err, lower_err = [], [], []
00496 for n, d in zip(numerator_array, denominator_array):
00497 try:
00498 p = float(n) / d
00499 s = math.sqrt(p * (1 - p) / d + 1 / (4 * d * d))
00500 t = p + 1 / (2 * d)
00501 eff.append(p)
00502 upper_err.append(-p + 1/(1 + 1/d) * (t + s))
00503 lower_err.append(+p - 1/(1 + 1/d) * (t - s))
00504 except ZeroDivisionError:
00505 eff.append(0)
00506 upper_err.append(0)
00507 lower_err.append(0)
00508 return eff, upper_err, lower_err
00509
00510 def find_num_processors():
00511 import os
00512 try:
00513 num_processors = os.sysconf('SC_NPROCESSORS_ONLN')
00514 except:
00515 try:
00516 num_processors = os.environ['NUMBER_OF_PROCESSORS']
00517 except:
00518 num_processors = 1
00519 return num_processors
00520
00521 def testfile():
00522 outfile = ROOT.TFile("test.root", "recreate")
00523 for i in range(4):
00524 d = outfile.mkdir("dir%i" % (i + 1))
00525 d.cd()
00526 for j in range(4):
00527 hist = ROOT.TH1F("hist%i" % (j + 1), "A Histogram", 10, 0, 10)
00528 hist.Fill(j)
00529 hist.Write()
00530 outfile.Write()
00531 return outfile
00532
00533
00534
00535 glob_magic_check = re.compile('[*?[]')
00536
00537 def has_glob_magic(s):
00538 return glob_magic_check.search(s) is not None
00539
00540
00541
00542
00543
00544
00545 def _rootglob1(tdirectory, dirname, pattern):
00546 if not tdirectory.GetDirectory(dirname):
00547 return []
00548 names = [key.GetName() for key in
00549 tdirectory.GetDirectory(dirname).GetListOfKeys()]
00550 return fnmatch.filter(names, pattern)
00551
00552 def _rootglob0(tdirectory, dirname, basename):
00553 if tdirectory.Get(os.path.join(dirname, basename)):
00554 return [basename]
00555 return []
00556
00557 def rootglob(tdirectory, pathname):
00558 """Return a list of paths matching a pathname pattern.
00559
00560 The pattern may contain simple shell-style wildcards a la fnmatch.
00561
00562 >>> import rootplot.utilities
00563 >>> f = rootplot.utilities.testfile()
00564 >>> rootglob(f, '*')
00565 ['dir1', 'dir2', 'dir3', 'dir4']
00566 >>> rootglob(f, 'dir1/*')
00567 ['dir1/hist1', 'dir1/hist2', 'dir1/hist3', 'dir1/hist4']
00568 >>> rootglob(f, '*/hist1')
00569 ['dir1/hist1', 'dir2/hist1', 'dir3/hist1', 'dir4/hist1']
00570 >>> rootglob(f, 'dir1/hist[1-2]')
00571 ['dir1/hist1', 'dir1/hist2']
00572 """
00573 return list(irootglob(tdirectory, pathname))
00574
00575 def irootglob(tdirectory, pathname):
00576 """Return an iterator which yields the paths matching a pathname pattern.
00577
00578 The pattern may contain simple shell-style wildcards a la fnmatch.
00579
00580 """
00581 if not has_glob_magic(pathname):
00582 if tdirectory.Get(pathname):
00583 yield pathname
00584 return
00585 dirname, basename = os.path.split(pathname)
00586 if has_glob_magic(dirname):
00587 dirs = irootglob(tdirectory, dirname)
00588 else:
00589 dirs = [dirname]
00590 if has_glob_magic(basename):
00591 glob_in_dir = _rootglob1
00592 else:
00593 glob_in_dir = _rootglob0
00594 for dirname in dirs:
00595 for name in glob_in_dir(tdirectory, dirname, basename):
00596 yield os.path.join(dirname, name)
00597
00598 if __name__ == '__main__':
00599 import doctest
00600 doctest.testmod()