Repository: biolab/orange3

Issue:
Canvas won't save NN model to file using Save Model widget
Orange version: 3.15
Expected behavior:  Model saved to file
Actual behavior: Exception is thrown related to pickling a local variable, 0kb resulting in file
Steps to reproduce the behavior:
   1.  Train a NN widget
   2.  Connect it to Save Model widget
   3.  Try saving model with the Save Model widget
Additional Info:
[Screenshot](https://screenshot.net/8qq1gsv)





Discussion:
Same probleme here:
```
  File "/orange3/Orange/widgets/model/owsavemodel.py", line 122, in browse
    self.save(filename)
  File "/orange3/Orange/widgets/model/owsavemodel.py", line 90, in save
    pickle.dump(self.model, f)
AttributeError: Can't pickle local object 'OWNNLearner.__update.<locals>.callback'
```

For some reason `pickle.dump(self.model, f)` is not working...

I tried to change the code and use [dill](https://pypi.org/project/dill/) instead of [pickle](https://docs.python.org/3/library/pickle.html), but without success...
Looks like they're trying to pickle a local variable.  I will look into it.  I've installed the dev version of orange3 \o/
```

Exception:
AttributeError: Can't pickle local object 'OWNNLearner.__update.<locals>.callback'
Module:
Orange.widgets.model.owsavemodel:90
Widget Name:
Save Model
Widget Module:
Orange.widgets.model.owsavemodel:106
Widget Scheme:
C:\Users\FRUITF~1\AppData\Local\Temp\ows-ew10kef7.ows.xml
Version:
3.16.0.dev0+7857c4b
Environment:
Python 3.6.5 on Windows 10 10.0.17134 AMD64
Installed Packages:
AnyQt==0.0.8, BinaryBuyOrder==0.0.0, Bottleneck==1.2.1, CommonMark==0.7.5, Flask==0.10.1, Jinja2==2.10, Markdown==2.6.11, MarkupSafe==1.0, MetaTraderDataCapture==0.0.0, Orange3-Prototypes==0.11.0, Orange3==3.16.0.dev0+7857c4b, Pillow==5.2.0, PyInstaller==3.3.1, PyQt5-sip==4.19.12, PyQt5==5.11.2, PyQtChart==5.11.2, Pygments==2.2.0, QScintilla==2.10.7, Werkzeug==0.14.1, absl-py==0.4.0, altgraph==0.15, astor==0.7.1, backcall==0.1.0, beautifulsoup4==4.6.3, bitstring==3.1.5, certifi==2018.1.18, chardet==3.0.4, colander==0.9.9, colorama==0.3.9, cycler==0.10.0, decorator==4.3.0, docutils==0.14, entrypoints==0.2.3, forex-python==1.1, future==0.16.0, gast==0.2.0, grpcio==1.14.1, idna==2.6, ipython-genutils==0.2.0, ipython==6.5.0, iso8601==0.1.8, itsdangerous==0.24, jedi==0.12.1, joblib==0.12.2, jsonschema==2.6.0, jupyter-core==4.4.0, kaitaistruct==0.8, keyring==13.2.1, keyrings.alt==3.1, kiwisolver==1.0.1, lark-parser==0.5.6, lark==0.0.4, lxml==4.2.0, macholib==1.9, matplotlib==2.2.3, mpmath==1.0.0, nbformat==4.4.0, networkx==2.1, numpy==1.14.5, pandas==0.23.4, parso==0.3.1, pefile==2017.11.5, pickleshare==0.7.4, pip==18.0, plotly==3.1.1, prompt-toolkit==1.0.15, protobuf==3.6.1, psycopg2==2.7.5, pymssql==2.1.4, pyparsing==2.2.0, pypiwin32==223, pyqtgraph==0.10.0, python-dateutil==2.7.2, python-louvain==0.11, pytz==2018.5, pywin32-ctypes==0.2.0, pywin32==223, redis==2.8.0, requests==2.18.4, retrying==1.3.3, scikit-learn==0.19.2, scipy==1.1.0, selenium==3.11.0, serverfiles==0.2.1, setuptools==39.0.1, simplegeneric==0.8.1, simplejson==3.16.0, sip==4.19.8, six==1.11.0, sympy==1.1.1, tensorboard==1.10.0, termcolor==1.1.0, tflearn==0.3.2, traitlets==4.3.2, translationstring==1.3, urllib3==1.22, wcwidth==0.1.7, wheel==0.31.1, xlrd==1.1.0
Machine ID:
57277341914805
Stack Trace:
Traceback (most recent call last):
  File "c:\myprojects\___mechanikos\orange\orange3\Orange\widgets\model\owsavemodel.py", line 106, in savecurrent
    self.save(self.filename)
  File "c:\myprojects\___mechanikos\orange\orange3\Orange\widgets\model\owsavemodel.py", line 90, in save
    pickle.dump(self.model, f)
AttributeError: Can't pickle local object 'OWNNLearner.__update.<locals>.callback'

Local Variables:
OrderedDict([('f',
              <_io.BufferedWriter name='C:/MyProjects/___MECHANIKOS/Orange/Models/mymodel2.pkcls'>),
             ('filename',
              'C:/MyProjects/___MECHANIKOS/Orange/Models/mymodel2.pkcls'),
             ('self',
              <Orange.widgets.model.owsavemodel.OWSaveModel object at 0x140EBA80>)])

```

Resolving patch:
diff --git a/Orange/widgets/model/owneuralnetwork.py b/Orange/widgets/model/owneuralnetwork.py
--- a/Orange/widgets/model/owneuralnetwork.py
+++ b/Orange/widgets/model/owneuralnetwork.py
@@ -222,6 +222,7 @@ def _task_finished(self, f):
         else:
             self.model.name = self.learner_name
             self.model.instances = self.data
+            self.model.skl_model.orange_callback = None  # remove unpicklable callback
             self.Outputs.model.send(self.model)
 
     def cancel(self):


Test patch:
diff --git a/Orange/widgets/tests/base.py b/Orange/widgets/tests/base.py
--- a/Orange/widgets/tests/base.py
+++ b/Orange/widgets/tests/base.py
@@ -1,5 +1,6 @@
 from contextlib import contextmanager
 import os
+import pickle
 import time
 import unittest
 from unittest.mock import Mock
@@ -620,6 +621,14 @@ def test_output_model_name(self):
         self.wait_until_stop_blocking()
         self.assertEqual(self.get_output(self.widget.Outputs.model).name, new_name)
 
+    def test_output_model_picklable(self):
+        """Check if model can be pickled"""
+        self.send_signal("Data", self.data)
+        self.widget.apply_button.button.click()
+        self.wait_until_stop_blocking()
+        model = self.get_output(self.widget.Outputs.model)
+        pickle.dumps(model)
+
     def _get_param_value(self, learner, param):
         if isinstance(learner, Fitter):
             # Both is just a was to indicate to the tests, fitters don't