@@ -84,7 +84,11 @@ def _expand_ellipsis(indexes, rank):
8484@set_family (MAIN_FAMILY )
8585@set_module ("boost_histogram" )
8686class Histogram (object ):
87- @inject_signature ("self, *axes, storage=Double()" , locals = {"Double" : Double })
87+ __slots__ = ("_hist" , "axes" , "metadata" )
88+
89+ @inject_signature (
90+ "self, *axes, storage=Double(), metadata=None" , locals = {"Double" : Double }
91+ )
8892 def __init__ (self , * axes , ** kwargs ):
8993 """
9094 Construct a new histogram.
@@ -99,22 +103,27 @@ def __init__(self, *axes, **kwargs):
99103 Provide 1 or more axis instances.
100104 storage : Storage = bh.storage.Double()
101105 Select a storage to use in the histogram
106+ metadata : Any = None
107+ Data that is passed along if a new histogram is created
102108 """
103109
104110 # Allow construction from a raw histogram object (internal)
105111 if not kwargs and len (axes ) == 1 and isinstance (axes [0 ], _histograms ):
106112 self ._hist = axes [0 ]
113+ self .metadata = None
107114 self .axes = self ._generate_axes_ ()
108115 return
109116
117+ # If we construct with another Histogram, support that too
110118 if not kwargs and len (axes ) == 1 and isinstance (axes [0 ], Histogram ):
111- self ._hist = copy . copy (axes [0 ]._hist )
112- self .axes = self . _generate_axes_ ()
119+ self .__init__ (axes [0 ]._hist )
120+ self .metadata = axes [ 0 ]. metadata
113121 return
114122
115123 # Keyword only trick (change when Python2 is dropped)
116124 with KWArgs (kwargs ) as k :
117125 storage = k .optional ("storage" , Double ())
126+ self .metadata = k .optional ("metadata" )
118127
119128 # Check for missed parenthesis or incorrect types
120129 if not isinstance (storage , Storage ):
@@ -150,6 +159,22 @@ def _generate_axes_(self):
150159
151160 return AxesTuple (self ._axis (i ) for i in range (self .ndim ))
152161
162+ def _new_hist (self , _hist ):
163+ """
164+ Return a new histogram given a new _hist, copying metadata.
165+ """
166+
167+ other = self .__class__ (_hist )
168+ other .metadata = self .metadata
169+ return other
170+
171+ @property
172+ def ndim (self ):
173+ """
174+ Number of axes (dimensions) of histogram.
175+ """
176+ return self ._hist .rank ()
177+
153178 def view (self , flow = False ):
154179 """
155180 Return a view into the data, optionally with overflow turned on.
@@ -161,7 +186,7 @@ def __array__(self):
161186
162187 def __add__ (self , other ):
163188 if hasattr (other , "_hist" ):
164- return self .__class__ (self ._hist .__add__ (other ._hist ))
189+ return self ._new_hist (self ._hist .__add__ (other ._hist ))
165190 else :
166191 retval = self .copy ()
167192 retval += other
@@ -188,7 +213,7 @@ def __ne__(self, other):
188213
189214 # If these fail, the underlying object throws the correct error
190215 def __mul__ (self , other ):
191- return self .__class__ (self ._hist .__mul__ (other ))
216+ return self ._new_hist (self ._hist .__mul__ (other ))
192217
193218 def __rmul__ (self , other ):
194219 return self * other
@@ -203,15 +228,15 @@ def __truediv__(self, other):
203228 result .__itruediv__ (other )
204229 return result
205230 else :
206- return self .__class__ (self ._hist .__truediv__ (_hist_or_val (other )))
231+ return self ._new_hist (self ._hist .__truediv__ (_hist_or_val (other )))
207232
208233 def __div__ (self , other ):
209234 if isinstance (other , Histogram ):
210235 result = self .copy ()
211236 result .__idiv__ (other )
212237 return result
213238 else :
214- return self .__class__ (self ._hist .__div__ (_hist_or_val (other )))
239+ return self ._new_hist (self ._hist .__div__ (_hist_or_val (other )))
215240
216241 def __itruediv__ (self , other ):
217242 if isinstance (other , Histogram ):
@@ -229,12 +254,6 @@ def __idiv__(self, other):
229254 self ._hist .__idiv__ (_hist_or_val (other ))
230255 return self
231256
232- def __copy__ (self ):
233- other = self .__class__ .__new__ (self .__class__ )
234- other ._hist = copy .copy (self ._hist )
235- other .axes = other ._generate_axes_ ()
236- return other
237-
238257 # TODO: Marked as too complex by flake8. Should be factored out a bit.
239258 @inject_signature ("self, *args, weight=None, sample=None, threads=None" )
240259 def fill (self , * args , ** kwargs ): # noqa: C901
@@ -347,21 +366,26 @@ def _storage_type(self):
347366 return cast (self , self ._hist ._storage_type , Storage )
348367
349368 def _reduce (self , * args ):
350- return self .__class__ (self ._hist .reduce (* args ))
369+ return self ._new_hist (self ._hist .reduce (* args ))
370+
371+ def __copy__ (self ):
372+ other = self ._new_hist (copy .copy (self ._hist ))
373+ return other
351374
352375 def __deepcopy__ (self , memo ):
353376 other = self .__class__ .__new__ (self .__class__ )
354377 other ._hist = copy .deepcopy (self ._hist , memo )
378+ other .metadata = copy .deepcopy (self .metadata , memo )
355379 other .axes = other ._generate_axes_ ()
356380 return other
357381
358382 def __getstate__ (self ):
359- state = self .__dict__ .copy ()
360- del state ["axes" ] # Don't save the cashe
383+ state = {"_hist" : self ._hist , "metadata" : self .metadata }
361384 return state
362385
363386 def __setstate__ (self , state ):
364- self .__dict__ .update (state )
387+ self ._hist = state ["_hist" ]
388+ self .metadata = state ["metadata" ]
365389 self .axes = self ._generate_axes_ ()
366390
367391 def __repr__ (self ):
@@ -489,13 +513,6 @@ def rank(self):
489513 warnings .warn (msg , FutureWarning )
490514 return self ._hist .rank ()
491515
492- @property
493- def ndim (self ):
494- """
495- Number of axes (dimensions) of histogram.
496- """
497- return self ._hist .rank ()
498-
499516 @property
500517 def size (self ):
501518 """
@@ -579,12 +596,12 @@ def __getitem__(self, index): # noqa: C901
579596 reduced = self ._hist .reduce (* slices )
580597
581598 if not integrations :
582- return self .__class__ (reduced )
599+ return self ._new_hist (reduced )
583600 else :
584601 projections = [i for i in range (self .ndim ) if i not in integrations ]
585602
586603 return (
587- self .__class__ (reduced .project (* projections ))
604+ self ._new_hist (reduced .project (* projections ))
588605 if projections
589606 else reduced .sum (flow = True )
590607 )
@@ -701,4 +718,4 @@ def project(self, *args):
701718 those axes only. Flow bins are used if available.
702719 """
703720
704- return self .__class__ (self ._hist .project (* args ))
721+ return self ._new_hist (self ._hist .project (* args ))
0 commit comments