Repository: yt-project/yt

Issue:
 AttributeError: 'Clump' object has no attribute 'clump_info'
Hi,

I am using the yt clumpfinder, and I tried to pickle the Clump object after running `find_clump()`, but got an error when I pickled: 
```
master_clump = Clump(dd, field)
find_clumps(master_clump, c_min, c_max, step)
leaf5 = get_lowest_clumps(master_clump)
pickle.dump(leaf5, open("blah.p", "wb"))
```

Error: 
```
File "/mnt/home/daisyleung/Downloads/yt-conda/lib/python2.7/site-packages/yt/analysis_modules/level_sets/clump_handling.py", line 404, in __reduce__ self.valid, self.children, self.data, self.clump_info, 
AttributeError: 'Clump' object has no attribute 'clump_info'
```

  * Python Version: 2.7
  * yt version: 3.4.1



Discussion:
Hi there!

Can I recommend you try using the `save_as_dataset` command instead of the pickle method? This function has been designed to save the clump structure to a reloadable format.

For example:
```
fn = master_clump.save_as_dataset(fields=["density", "particle_mass"])
clumps_ds = ds.load(fn)
leaves = clumps_ds.leaves
```

You can find more information on using this method in [this recipe](http://yt-project.org/docs/dev/cookbook/constructing_data_objects.html#cookbook-find-clumps).
Also here: http://yt-project.org/doc/analyzing/analysis_modules/clump_finding.html#saving-and-reloading-clump-data

That said, we have `__setstate__` method and we should probably fix it (and add tests) now that it's broken.

Resolving patch:
diff --git a/yt/data_objects/level_sets/clump_handling.py b/yt/data_objects/level_sets/clump_handling.py
--- a/yt/data_objects/level_sets/clump_handling.py
+++ b/yt/data_objects/level_sets/clump_handling.py
@@ -23,7 +23,6 @@
 from yt.funcs import \
     deprecate, \
     get_output_filename, \
-    iterable, \
     mylog
 from yt.extern.six import \
     string_types
@@ -73,10 +72,11 @@ def __init__(self, data, field, parent=None,
         if base is None:
             base = self
             self.total_clumps = 0
-            if clump_info is None:
-                self.set_default_clump_info()
-            else:
-                self.clump_info = clump_info
+
+        if clump_info is None:
+            self.set_default_clump_info()
+        else:
+            self.clump_info = clump_info
 
         self.base = base
         self.clump_id = self.base.total_clumps
@@ -193,7 +193,6 @@ def __iter__(self):
 
     def save_as_dataset(self, filename=None, fields=None):
         r"""Export clump tree to a reloadable yt dataset.
-
         This function will take a clump object and output a dataset
         containing the fields given in the ``fields`` list and all info
         items.  The resulting dataset can be reloaded as a yt dataset.
@@ -399,35 +398,13 @@ def _validate(self):
         return self.valid
 
     def __reduce__(self):
-        return (_reconstruct_clump, 
-                (self.parent, self.field, self.min_val, self.max_val,
-                 self.valid, self.children, self.data, self.clump_info, 
-                 self.function))
-
-    def __getitem__(self,request):
+        raise RuntimeError(
+            "Pickling Clump instances is not supported. Please use "
+            "Clump.save_as_dataset instead")
+    
+    def __getitem__(self, request):
         return self.data[request]
 
-def _reconstruct_clump(parent, field, mi, ma, valid, children, 
-                       data, clump_info, 
-        function=None):
-    obj = object.__new__(Clump)
-    if iterable(parent):
-        try:
-            parent = parent[1]
-        except KeyError:
-            parent = parent
-    if children is None: children = []
-    obj.parent, obj.field, obj.min_val, obj.max_val, \
-      obj.valid, obj.children, obj.clump_info, obj.function = \
-        parent, field, mi, ma, valid, children, clump_info, function
-    # Now we override, because the parent/child relationship seems a bit
-    # unreliable in the unpickling
-    for child in children: child.parent = obj
-    obj.data = data[1] # Strip out the PF
-    obj.quantities = obj.data.quantities
-    if obj.parent is None: return (data[0], obj)
-    return obj
-
 def find_clumps(clump, min_val, max_val, d_clump):
     mylog.info("Finding clumps: min: %e, max: %e, step: %f" % 
                (min_val, max_val, d_clump))


Test patch: