Repository: spyder-ide/spyder

Issue:
Error when closing dataframe while data is being fetched
## Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

I was using the data viewer to open a pandas DataFrame with shape = (1788, 3), after I closed the data viewer, this error appeared.

### Traceback
```python-traceback
  File "C:\ProgramData\Anaconda3\lib\site-packages\spyder\widgets\variableexplorer\dataframeeditor.py", line 513, in <lambda>
    lambda val: self.load_more_data(val, rows=True))
NameError: free variable 'self' referenced before assignment in enclosing scope
```

## Versions

* Spyder version: 3.3.1 
* Python version: 3.6.6
* Qt version: 5.9.6
* PyQt5 version: 5.9.2
* Operating System: Windows 10

### Dependencies

```
pyflakes >=0.6.0  :  2.0.0 (OK)
pycodestyle >=2.3 :  2.4.0 (OK)
pygments >=2.0    :  2.2.0 (OK)
sphinx >=0.6.6    :  1.7.8 (OK)
rope >=0.9.4      :  0.11.0 (OK)
jedi >=0.9.0      :  0.12.1 (OK)
nbconvert >=4.0   :  5.3.1 (OK)
pandas >=0.13.1   :  0.23.4 (OK)
numpy >=1.7       :  1.15.1 (OK)
sympy >=0.7.3     :  1.2 (OK)
cython >=0.21     :  0.28.5 (OK)
qtconsole >=4.2.0 :  4.4.1 (OK)
IPython >=4.0     :  6.5.0 (OK)
matplotlib >=2.0.0:  2.2.3 (OK)
pylint >=0.25     :  2.1.1 (OK)
```



Discussion:
Thanks for reporting. We'll take a look at this in a future release.
Note: This happens when users are scrolling a big dataframe and closing it before it gets all the data that's being fetched.
@juanis2112, this is an easy one for you. Please enclose the entire contents of the `load_more_data` method, placed here:

https://github.com/spyder-ide/spyder/blob/master/spyder/plugins/variableexplorer/widgets/dataframeeditor.py#L512

in a `try/except NameError` block.

And also do the same for the `load_more_data` method in arrayeditor, here:

https://github.com/spyder-ide/spyder/blob/master/spyder/plugins/variableexplorer/widgets/arrayeditor.py#L420

Resolving patch:
diff --git a/spyder/plugins/variableexplorer/widgets/arrayeditor.py b/spyder/plugins/variableexplorer/widgets/arrayeditor.py
--- a/spyder/plugins/variableexplorer/widgets/arrayeditor.py
+++ b/spyder/plugins/variableexplorer/widgets/arrayeditor.py
@@ -418,39 +418,48 @@ def __init__(self, parent, model, dtype, shape):
                                lambda val: self.load_more_data(val, rows=True))
     
     def load_more_data(self, value, rows=False, columns=False):
-        old_selection = self.selectionModel().selection()
-        old_rows_loaded = old_cols_loaded = None
-        
-        if rows and value == self.verticalScrollBar().maximum():
-            old_rows_loaded = self.model().rows_loaded
-            self.model().fetch_more(rows=rows)
-            
-        if columns and value == self.horizontalScrollBar().maximum():
-            old_cols_loaded = self.model().cols_loaded
-            self.model().fetch_more(columns=columns)
-            
-        if old_rows_loaded is not None or old_cols_loaded is not None:
-            # if we've changed anything, update selection
-            new_selection = QItemSelection()
-            for part in old_selection:
-                top = part.top()
-                bottom = part.bottom()
-                if (old_rows_loaded is not None and 
-                    top == 0 and bottom == (old_rows_loaded-1)):
-                    # complete column selected (so expand it to match updated range)
-                    bottom = self.model().rows_loaded-1
-                left = part.left()
-                right = part.right()
-                if (old_cols_loaded is not None
-                    and left == 0 and right == (old_cols_loaded-1)):
-                    # compete row selected (so expand it to match updated range)
-                    right = self.model().cols_loaded-1
-                top_left = self.model().index(top, left)
-                bottom_right = self.model().index(bottom, right)
-                part = QItemSelectionRange(top_left, bottom_right)
-                new_selection.append(part)
-            self.selectionModel().select(new_selection, self.selectionModel().ClearAndSelect)
-                    
+
+        try:
+            old_selection = self.selectionModel().selection()
+            old_rows_loaded = old_cols_loaded = None
+
+            if rows and value == self.verticalScrollBar().maximum():
+                old_rows_loaded = self.model().rows_loaded
+                self.model().fetch_more(rows=rows)
+
+            if columns and value == self.horizontalScrollBar().maximum():
+                old_cols_loaded = self.model().cols_loaded
+                self.model().fetch_more(columns=columns)
+
+            if old_rows_loaded is not None or old_cols_loaded is not None:
+                # if we've changed anything, update selection
+                new_selection = QItemSelection()
+                for part in old_selection:
+                    top = part.top()
+                    bottom = part.bottom()
+                    if (old_rows_loaded is not None and
+                            top == 0 and bottom == (old_rows_loaded-1)):
+                        # complete column selected (so expand it to match
+                        # updated range)
+                        bottom = self.model().rows_loaded-1
+                    left = part.left()
+                    right = part.right()
+                    if (old_cols_loaded is not None
+                            and left == 0 and right == (old_cols_loaded-1)):
+                        # compete row selected (so expand it to match updated
+                        # range)
+                        right = self.model().cols_loaded-1
+                    top_left = self.model().index(top, left)
+                    bottom_right = self.model().index(bottom, right)
+                    part = QItemSelectionRange(top_left, bottom_right)
+                    new_selection.append(part)
+                self.selectionModel().select
+                (new_selection, self.selectionModel().ClearAndSelect)
+
+        except NameError:
+            # Needed to handle a NameError while fetching data when closing
+            # See isue 7880
+            pass
 
     def resize_to_contents(self):
         """Resize cells to contents"""
diff --git a/spyder/plugins/variableexplorer/widgets/dataframeeditor.py b/spyder/plugins/variableexplorer/widgets/dataframeeditor.py
--- a/spyder/plugins/variableexplorer/widgets/dataframeeditor.py
+++ b/spyder/plugins/variableexplorer/widgets/dataframeeditor.py
@@ -511,12 +511,18 @@ def __init__(self, parent, model, header, hscroll, vscroll):
 
     def load_more_data(self, value, rows=False, columns=False):
         """Load more rows and columns to display."""
-        if rows and value == self.verticalScrollBar().maximum():
-            self.model().fetch_more(rows=rows)
-            self.sig_fetch_more_rows.emit()
-        if columns and value == self.horizontalScrollBar().maximum():
-            self.model().fetch_more(columns=columns)
-            self.sig_fetch_more_columns.emit()
+        try:
+            if rows and value == self.verticalScrollBar().maximum():
+                self.model().fetch_more(rows=rows)
+                self.sig_fetch_more_rows.emit()
+            if columns and value == self.horizontalScrollBar().maximum():
+                self.model().fetch_more(columns=columns)
+                self.sig_fetch_more_columns.emit()
+
+        except NameError:
+            # Needed to handle a NameError while fetching data when closing
+            # See issue 7880
+            pass
 
     def sortByColumn(self, index):
         """Implement a column sort."""


Test patch: