Repository: joblib/joblib

Issue:
pickling breaks between joblib version 0.11 and 0.12.2
For my script below, will someone please tell me why the result of call_and_shelve() can be pickled in version 0.11,  but not in version 0.12.2? Or tell me what I am doing wrong, or if this is a bug...

Thanks.

## I have this little script
```python
from joblib import Memory
import numpy as np
import pickle
cachedir = 'PATH_TO_CACHE'
memory = Memory(cachedir, verbose=0)

@memory.cache
def g(x):
	print('A long-running calculation, with parameter %s' % x)
	return np.hamming(x)

result = g.call_and_shelve(4)
print('\nMemorized result: {}'.format(result))

canned_pickle = pickle.dumps(result)
open_can = pickle.loads(canned_pickle)

print('Depickled: {}\n'.format(open_can))
```
### With joblib:0.11 everything works and I get this output:
```python
Memorized result: MemorizedResult(cachedir="/Users/mpsommer/Documents/python/job_lib_cache/joblib", func="__main__--Users-mpsommer-Documents-python-job_lib/g-alias", argument_hash="f7c5defad10d1a7505df243840ad5209")
Depickled: MemorizedResult(cachedir="/Users/mpsommer/Documents/python/job_lib_cache/joblib", func="__main__--Users-mpsommer-Documents-python-job_lib/g-alias", argument_hash="f7c5defad10d1a7505df243840ad5209")
```

### With joblib:0.12.2 the pickling breaks and I get this output:
```python
Memorized result: MemorizedResult(location="/Users/mpsommer/Documents/python/job_lib_cache/joblib", func="<function g at 0x104448aa0>", args_id="f7c5defad10d1a7505df243840ad5209")
Traceback (most recent call last):
  File "job_lib.py", line 15, in <module>
    canned_pickle = pickle.dumps(result)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 1374, in dumps
    Pickler(file, protocol).dump(obj)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 224, in dump
    self.save(obj)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 331, in save
    self.save_reduce(obj=obj, *rv)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 401, in save_reduce
    save(args)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 286, in save
    f(self, obj) # Call unbound method with explicit self
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 562, in save_tuple
    save(element)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 286, in save
    f(self, obj) # Call unbound method with explicit self
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.py", line 753, in save_global
    (obj, module, name))
pickle.PicklingError: Can't pickle <function g at 0x104448aa0>: it's not the same object as __main__.g
```


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/joblib/joblib/pull/752?src=pr&el=h1) Report
> Merging [#752](https://codecov.io/gh/joblib/joblib/pull/752?src=pr&el=desc) into [master](https://codecov.io/gh/joblib/joblib/commit/b0bc5dd24b123b445965a5f4d487beaf6fadd083?src=pr&el=desc) will **increase** coverage by `0.04%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/joblib/joblib/pull/752/graphs/tree.svg?src=pr&token=gA6LF5DGTW&height=150&width=650)](https://codecov.io/gh/joblib/joblib/pull/752?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #752      +/-   ##
==========================================
+ Coverage   95.24%   95.28%   +0.04%     
==========================================
  Files          43       43              
  Lines        6137     6150      +13     
==========================================
+ Hits         5845     5860      +15     
+ Misses        292      290       -2
```


| [Impacted Files](https://codecov.io/gh/joblib/joblib/pull/752?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [joblib/test/test\_memory.py](https://codecov.io/gh/joblib/joblib/pull/752/diff?src=pr&el=tree#diff-am9ibGliL3Rlc3QvdGVzdF9tZW1vcnkucHk=) | `97.95% <100%> (+0.04%)` | :arrow_up: |
| [joblib/memory.py](https://codecov.io/gh/joblib/joblib/pull/752/diff?src=pr&el=tree#diff-am9ibGliL21lbW9yeS5weQ==) | `95.75% <100%> (+0.3%)` | :arrow_up: |
| [joblib/\_parallel\_backends.py](https://codecov.io/gh/joblib/joblib/pull/752/diff?src=pr&el=tree#diff-am9ibGliL19wYXJhbGxlbF9iYWNrZW5kcy5weQ==) | `96.8% <0%> (-1.61%)` | :arrow_down: |
| [joblib/test/test\_parallel.py](https://codecov.io/gh/joblib/joblib/pull/752/diff?src=pr&el=tree#diff-am9ibGliL3Rlc3QvdGVzdF9wYXJhbGxlbC5weQ==) | `96.73% <0%> (+0.14%)` | :arrow_up: |
| [joblib/\_store\_backends.py](https://codecov.io/gh/joblib/joblib/pull/752/diff?src=pr&el=tree#diff-am9ibGliL19zdG9yZV9iYWNrZW5kcy5weQ==) | `90.47% <0%> (+0.52%)` | :arrow_up: |
| [joblib/pool.py](https://codecov.io/gh/joblib/joblib/pull/752/diff?src=pr&el=tree#diff-am9ibGliL3Bvb2wucHk=) | `91.37% <0%> (+1.72%)` | :arrow_up: |
| [joblib/backports.py](https://codecov.io/gh/joblib/joblib/pull/752/diff?src=pr&el=tree#diff-am9ibGliL2JhY2twb3J0cy5weQ==) | `95.83% <0%> (+2.08%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/joblib/joblib/pull/752?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/joblib/joblib/pull/752?src=pr&el=footer). Last update [b0bc5dd...188d155](https://codecov.io/gh/joblib/joblib/pull/752?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

aabadie: > can you please add an entry to the changelog?

Done

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 6d01fa4..35f50e2 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,6 +1,14 @@
 Latest changes
 ===============
 
+Release 0.12.3
+--------------
+
+Alexandre Abadie
+
+    Fix MemorizedResult not picklable (#747).
+
+
 Release 0.12.2
 --------------
 
diff --git a/joblib/memory.py b/joblib/memory.py
index bbb52aa..1cebe63 100644
--- a/joblib/memory.py
+++ b/joblib/memory.py
@@ -210,8 +210,11 @@ class MemorizedResult(Logger):
     def __init__(self, location, func, args_id, backend='local',
                  mmap_mode=None, verbose=0, timestamp=None, metadata=None):
         Logger.__init__(self)
-        self.func = func
         self.func_id = _build_func_identifier(func)
+        if isinstance(func, _basestring):
+            self.func = func
+        else:
+            self.func = self.func_id
         self.args_id = args_id
         self.store_backend = _store_backend_factory(backend, location,
                                                     verbose=verbose)


Test patch:
diff --git a/joblib/test/test_memory.py b/joblib/test/test_memory.py
index 59cbec3..4690b5b 100644
--- a/joblib/test/test_memory.py
+++ b/joblib/test/test_memory.py
@@ -964,3 +964,25 @@ def test_dummy_store_backend():
 
     backend_obj = _store_backend_factory(backend_name, "dummy_location")
     assert isinstance(backend_obj, DummyStoreBackend)
+
+
+def test_memorized_result_pickle(tmpdir):
+    # Verify a MemoryResult object can be pickled/depickled. Non regression
+    # test introduced following issue
+    # https://github.com/joblib/joblib/issues/747
+
+    memory = Memory(location=tmpdir.strpath)
+
+    @memory.cache
+    def g(x):
+        return x**2
+
+    memorized_result = g.call_and_shelve(4)
+    memorized_result_pickle = pickle.dumps(memorized_result)
+    memorized_result_loads = pickle.loads(memorized_result_pickle)
+
+    assert memorized_result.store_backend.location == \
+        memorized_result_loads.store_backend.location
+    assert memorized_result.func == memorized_result_loads.func
+    assert memorized_result.args_id == memorized_result_loads.args_id
+    assert str(memorized_result) == str(memorized_result_loads)