diff --git a/pyext/yoda/include/AnalysisObject.pyx b/pyext/yoda/include/AnalysisObject.pyx --- a/pyext/yoda/include/AnalysisObject.pyx +++ b/pyext/yoda/include/AnalysisObject.pyx @@ -1,163 +1,157 @@ cimport util cdef class AnalysisObject(util.Base): """ AnalysisObject is the base class of the main user-facing objects, such as the Histo, Profile and Scatter classes. """ # Pointer upcasting mechanism cdef inline c.AnalysisObject* aoptr(self) except NULL: return self.ptr() # Pointer upcasting mechanism # DEPRECATED cdef inline c.AnalysisObject* _AnalysisObject(self) except NULL: return self.ptr() # Deallocator (only needed as a base class) def __dealloc__(self): p = self.aoptr() if self._deallocate: del p #@property def type(self): "String identifier for this type" return self.aoptr().type().decode('utf-8') #@property def dim(self): "Fill dimension or plot dimension of this object, for fillables and scatters respectively" return self.aoptr().dim() #@property def annotations(self): """() -> list[str] A list of all annotation/metadata keys.""" return [ a.decode('utf-8') for a in self.aoptr().annotations() ] #@property def annotationsDict(self): """() -> dict[str->str] A dict of all annotations/metadata entries.""" # TODO: add a map equivalent to C++? return dict((k.lower(), self.annotation(k)) for k in self.annotations()) def annotation(self, k, default=None): """Get annotation k from this object (falling back to default if not set). The annotation string will be automatically converted to Python native types as far as possible -- more complex types are possible via the ast and yaml modules.""" - rtn = default try: rtn = self.aoptr().annotation(k.encode('utf-8')) try: - import ast - rtn = ast.literal_eval(rtn) + import yaml + rtn = yaml.full_load(rtn) except: - try: - import yaml - rtn = yaml.full_load(rtn) - except: - pass - #rtn = util._autotype(rtn) + rtn = util._autotype(rtn, True) except: - pass + rtn = default return rtn def setAnnotation(self, k, v): """Set annotation k on this object.""" self.aoptr().setAnnotation(k.encode('utf-8'), util._autostr(v).encode('utf-8')) def hasAnnotation(self, k): """Check if this object has annotation k.""" return self.aoptr().hasAnnotation(k.encode('utf-8')) def rmAnnotation(self, k): """Remove annotation k from this object.""" self.aoptr().rmAnnotation(k.encode('utf-8')) def clearAnnotations(self): """Clear the annotations dictionary.""" self.aoptr().clearAnnotations() def dump(self): """A human readable representation of this object.""" try: from cStringIO import StringIO except ImportError: from io import StringIO f = StringIO() writeFLAT([self], f) f.seek(0) return f.read().strip() #@property def name(self): """ Return the histogram name, i.e. the last part of the path (which may be empty). """ return self.aoptr().name().decode('utf-8') def path(self): """ Used for persistence and as a unique identifier. Must begin with a '/' if not the empty string. """ return self.aoptr().path().decode('utf-8') def setPath(self, path): """ Used for persistence and as a unique identifier. Must begin with a '/' if not the empty string. """ self.aoptr().setPath(path.encode('utf-8')) # property path: # """ # Used for persistence and as a unique identifier. Must begin with # a '/' if not the empty string. # """ # def __get__(self): # return self.aoptr().path().decode('utf-8') # def __set__(self, path): # self.aoptr().setPath(path.encode('utf-8')) # def title(self): # """ # Histogram title # """ # return self.aoptr().title().decode('utf-8') # def setTitle(self, title): # """ # Set the histogram title (optional) # """ # self.aoptr().setTitle(title.encode('utf-8')) # property title: # """ # Convenient access to the histogram title (optional). # """ # def __get__(self): # return self.aoptr().title().decode('utf-8') # def __set__(self, title): # self.aoptr().setTitle(title.encode('utf-8')) def __repr__(self): return "<%s '%s'>" % (self.__class__.__name__, self.path) ## Convenience alias AO = AnalysisObject diff --git a/pyext/yoda/util.pyx b/pyext/yoda/util.pyx --- a/pyext/yoda/util.pyx +++ b/pyext/yoda/util.pyx @@ -1,94 +1,93 @@ from collections import namedtuple from operator import itemgetter def as_bool(x): if type(x) is bool: return x s = str(x) if s.lower() in ("true", "yes", "on", "1", "1.0"): return True if s.lower() in ("false", "no", "off", "0", "0.0"): return False raise Exception("'{}' cannot be parsed as a boolean flag".format(s)) -def _autotype(var, autobool=True): +def _autotype(var, autobool=False): """Automatically convert strings to numerical types if possible.""" if type(var) is not str: return var + ## Convert via Python ast parser try: import ast - return ast.literal_eval(var) + var = ast.literal_eval(var) except: - return var - # - # if var.isdigit() or (var.startswith("-") and var[1:].isdigit()): - # return int(var) - # try: - # return float(var) - # except: pass - # if autobool: - # try: - # return as_bool(var) - # except: pass - # return var + # TODO: print a warning? + pass + ## Try friendly string conversions to bool + if autobool and type(var) is str: + try: + var = as_bool(var) + except: + pass + ## Finally return + return var # def _autonp(var): # """Automatically return lists as numpy arrays if numpy is imported""" # if "numpy" in dir(): # return numpy.array(var) # elif "np" in dir(): # return np.array(var) # else: # return var def _autostr(var, precision=8): """Automatically format numerical types as the right sort of string.""" if type(var) is float: return ("% ." + str(precision) + "e") % var elif not isinstance(var, (list,tuple)): return str(var) else: return ",".join(_autostr(subval) for subval in var) cdef class Base: pass def try_loop(fs, *args, char *_msg='Invalid arguments', **kwargs): for f in fs: try: f(*args, **kwargs) return except (TypeError, AttributeError): pass raise TypeError(_msg) XY = namedtuple('XY', ('x', 'y')) XYZ = namedtuple('XYZ', ('x', 'y', 'z')) EdgePair = namedtuple('EdgePair', ('low', 'high')) ErrorPair = namedtuple('ErrorPair', ('minus', 'plus')) ## Utils for handling error conversions to/from std::pair from libcpp.pair cimport pair def read_edge_pair(pair[double, double] es): return EdgePair(es.first, es.second) def read_error_pair(pair[double, double] es): return ErrorPair(es.first, es.second) def read_symmetric(val): try: a, b = val except TypeError: a = b = val return pair[double, double](a, b)