diff --git a/ci/requirements-3.6_BUILD_TEST.sh b/ci/requirements-3.6_BUILD_TEST.sh
index 84dd27c50d587..2a3adeff836ee 100644
--- a/ci/requirements-3.6_BUILD_TEST.sh
+++ b/ci/requirements-3.6_BUILD_TEST.sh
@@ -4,4 +4,4 @@ source activate pandas
echo "install 36 BUILD_TEST"
-conda install -n pandas -c conda-forge pyarrow dask
+conda install -n pandas -c conda-forge pyarrow dask pyqt qtpy
diff --git a/doc/source/install.rst b/doc/source/install.rst
index c805f84d0faaa..b8968e18aecb0 100644
--- a/doc/source/install.rst
+++ b/doc/source/install.rst
@@ -251,12 +251,13 @@ Optional Dependencies
* `Jinja2 `__: Template engine for conditional HTML formatting.
* `s3fs `__: necessary for Amazon S3 access (s3fs >= 0.0.7).
* `blosc `__: for msgpack compression using ``blosc``
-* One of `PyQt4
- `__, `PySide
- `__, `pygtk
- `__, `xsel
- `__, or `xclip
- `__: necessary to use
+* One of
+ `qtpy `__ (requires PyQt or PySide),
+ `PyQt5 `__,
+ `PyQt4 `__,
+ `pygtk `__,
+ `xsel `__, or
+ `xclip `__: necessary to use
:func:`~pandas.read_clipboard`. Most package managers on Linux distributions will have ``xclip`` and/or ``xsel`` immediately available for installation.
* For Google BigQuery I/O - see `here `__
diff --git a/doc/source/io.rst b/doc/source/io.rst
index c94d5bc75d4fc..5390fc3399e23 100644
--- a/doc/source/io.rst
+++ b/doc/source/io.rst
@@ -3053,7 +3053,7 @@ We can see that we got the same content back, which we had earlier written to th
.. note::
- You may need to install xclip or xsel (with gtk or PyQt4 modules) on Linux to use these methods.
+ You may need to install xclip or xsel (with gtk, PyQt5, PyQt4 or qtpy) on Linux to use these methods.
.. _io.pickle:
diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt
index 90032a692fd15..9b7226f0fe594 100644
--- a/doc/source/whatsnew/v0.22.0.txt
+++ b/doc/source/whatsnew/v0.22.0.txt
@@ -44,6 +44,7 @@ Other Enhancements
- :class:`pandas.io.formats.style.Styler` now has method ``hide_columns()`` to determine whether columns will be hidden in output (:issue:`14194`)
- Improved wording of ``ValueError`` raised in :func:`to_datetime` when ``unit=`` is passed with a non-convertible value (:issue:`14350`)
- :func:`Series.fillna` now accepts a Series or a dict as a ``value`` for a categorical dtype (:issue:`17033`)
+- :func:`pandas.read_clipboard` updated to use qtpy, falling back to PyQt5 and then PyQt4, adding compatibility with Python3 and multiple python-qt bindings (:issue:`17722`)
.. _whatsnew_0220.api_breaking:
diff --git a/pandas/io/clipboard/__init__.py b/pandas/io/clipboard/__init__.py
index 4066a3be5e850..37d398f20ef41 100644
--- a/pandas/io/clipboard/__init__.py
+++ b/pandas/io/clipboard/__init__.py
@@ -18,7 +18,8 @@
On Linux, install xclip or xsel via package manager. For example, in Debian:
sudo apt-get install xclip
-Otherwise on Linux, you will need the gtk or PyQt4 modules installed.
+Otherwise on Linux, you will need the gtk, qtpy or PyQt modules installed.
+qtpy also requires a python-qt-bindings module: PyQt4, PyQt5, PySide, PySide2
gtk and PyQt4 modules are not available for Python 3,
and this module does not work with PyGObject yet.
@@ -34,9 +35,9 @@
init_klipper_clipboard, init_no_clipboard)
from .windows import init_windows_clipboard
-# `import PyQt4` sys.exit()s if DISPLAY is not in the environment.
+# `import qtpy` sys.exit()s if DISPLAY is not in the environment.
# Thus, we need to detect the presence of $DISPLAY manually
-# and not load PyQt4 if it is absent.
+# and not load qtpy if it is absent.
HAS_DISPLAY = os.getenv("DISPLAY", False)
CHECK_CMD = "where" if platform.system() == "Windows" else "which"
@@ -68,9 +69,23 @@ def determine_clipboard():
return init_gtk_clipboard()
try:
- # Check if PyQt4 is installed
- import PyQt4 # noqa
+ # qtpy is a small abstraction layer that lets you write
+ # applications using a single api call to either PyQt or PySide
+ # https://pypi.python.org/pypi/QtPy
+ import qtpy # noqa
except ImportError:
+ # If qtpy isn't installed, fall back on importing PyQt5, or PyQt5
+ try:
+ import PyQt5 # noqa
+ except ImportError:
+ try:
+ import PyQt4 # noqa
+ except ImportError:
+ pass # fail fast for all non-ImportError exceptions.
+ else:
+ return init_qt_clipboard()
+ else:
+ return init_qt_clipboard()
pass
else:
return init_qt_clipboard()
diff --git a/pandas/io/clipboard/clipboards.py b/pandas/io/clipboard/clipboards.py
index e32380a383374..285d93e3ca497 100644
--- a/pandas/io/clipboard/clipboards.py
+++ b/pandas/io/clipboard/clipboards.py
@@ -46,10 +46,19 @@ def paste_gtk():
def init_qt_clipboard():
# $DISPLAY should exist
- from PyQt4.QtGui import QApplication
- # use the global instance if it exists
- app = QApplication.instance() or QApplication([])
+ # Try to import from qtpy, but if that fails try PyQt5 then PyQt4
+ try:
+ from qtpy.QtWidgets import QApplication
+ except ImportError:
+ try:
+ from PyQt5.QtWidgets import QApplication
+ except ImportError:
+ from PyQt4.QtGui import QApplication
+
+ app = QApplication.instance()
+ if app is None:
+ app = QApplication([])
def copy_qt(text):
cb = app.clipboard()