From 16d726f89c0ea7df99026480a62e981aeebfc6f3 Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Mon, 5 Mar 2018 11:55:48 +0100 Subject: [PATCH 1/5] DOC: preserve docstring when cache_readonly is used --- pandas/_libs/properties.pyx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pandas/_libs/properties.pyx b/pandas/_libs/properties.pyx index 4beb24f07c21c..37886c675b133 100644 --- a/pandas/_libs/properties.pyx +++ b/pandas/_libs/properties.pyx @@ -9,17 +9,19 @@ from cpython cimport ( cdef class cache_readonly(object): cdef readonly: - object func, name, allow_setting + object func, name, doc, allow_setting def __init__(self, func=None, allow_setting=False): if func is not None: self.func = func self.name = func.__name__ + self.doc = getattr(func, '__doc__', None) self.allow_setting = allow_setting def __call__(self, func, doc=None): self.func = func self.name = func.__name__ + self.doc = getattr(func, '__doc__', None) return self def __get__(self, obj, typ): @@ -30,7 +32,7 @@ cdef class cache_readonly(object): try: cache = obj._cache = {} except (AttributeError): - return + return type('cached', (object, ), {'__doc__': self.doc}) if PyDict_Contains(cache, self.name): # not necessary to Py_INCREF From 30d94bbd099b6a3067b93c2e803068623a5af286 Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Mon, 5 Mar 2018 13:44:55 +0100 Subject: [PATCH 2/5] better way to have docstring by returning itself --- pandas/_libs/properties.pyx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pandas/_libs/properties.pyx b/pandas/_libs/properties.pyx index 37886c675b133..0ae8461dcdbc2 100644 --- a/pandas/_libs/properties.pyx +++ b/pandas/_libs/properties.pyx @@ -6,22 +6,22 @@ from cpython cimport ( PyDict_Contains, PyDict_GetItem, PyDict_SetItem) -cdef class cache_readonly(object): +cdef class CachedProperty(object): cdef readonly: - object func, name, doc, allow_setting + object func, name, __doc__, allow_setting def __init__(self, func=None, allow_setting=False): if func is not None: self.func = func self.name = func.__name__ - self.doc = getattr(func, '__doc__', None) + self.__doc__ = getattr(func, '__doc__', None) self.allow_setting = allow_setting def __call__(self, func, doc=None): self.func = func self.name = func.__name__ - self.doc = getattr(func, '__doc__', None) + self.__doc__ = getattr(func, '__doc__', None) return self def __get__(self, obj, typ): @@ -32,7 +32,7 @@ cdef class cache_readonly(object): try: cache = obj._cache = {} except (AttributeError): - return type('cached', (object, ), {'__doc__': self.doc}) + return self if PyDict_Contains(cache, self.name): # not necessary to Py_INCREF @@ -57,6 +57,10 @@ cdef class cache_readonly(object): PyDict_SetItem(cache, self.name, value) + +cache_readonly = CachedProperty + + cdef class AxisProperty(object): cdef: Py_ssize_t axis From fd2beb9134a9164afedb8f9cdb92e3703252d627 Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Mon, 5 Mar 2018 14:11:24 +0100 Subject: [PATCH 3/5] remove test that with view(Index) that was no longer valid --- pandas/tests/indexes/test_base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index d7f185853ca45..964a6b14d2b1e 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -519,7 +519,6 @@ def test_is_(self): assert not ind.is_(ind.copy()) assert not ind.is_(ind.copy(deep=False)) assert not ind.is_(ind[:]) - assert not ind.is_(ind.view(np.ndarray).view(Index)) assert not ind.is_(np.array(range(10))) # quasi-implementation dependent From 2ae009480deef138f70cfca39563015db0a4b0a6 Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Mon, 5 Mar 2018 14:24:39 +0100 Subject: [PATCH 4/5] remove allow_setting from cache_readonly --- pandas/_libs/properties.pyx | 27 ++------------------------- pandas/core/indexes/base.py | 2 +- 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/pandas/_libs/properties.pyx b/pandas/_libs/properties.pyx index 0ae8461dcdbc2..b378b19f1acd7 100644 --- a/pandas/_libs/properties.pyx +++ b/pandas/_libs/properties.pyx @@ -9,20 +9,12 @@ from cpython cimport ( cdef class CachedProperty(object): cdef readonly: - object func, name, __doc__, allow_setting + object func, name, __doc__ - def __init__(self, func=None, allow_setting=False): - if func is not None: - self.func = func - self.name = func.__name__ - self.__doc__ = getattr(func, '__doc__', None) - self.allow_setting = allow_setting - - def __call__(self, func, doc=None): + def __init__(self, func): self.func = func self.name = func.__name__ self.__doc__ = getattr(func, '__doc__', None) - return self def __get__(self, obj, typ): # Get the cache or set a default one if needed @@ -42,21 +34,6 @@ cdef class CachedProperty(object): PyDict_SetItem(cache, self.name, val) return val - def __set__(self, obj, value): - - if not self.allow_setting: - raise Exception("cannot set values for [%s]" % self.name) - - # Get the cache or set a default one if needed - cache = getattr(obj, '_cache', None) - if cache is None: - try: - cache = obj._cache = {} - except (AttributeError): - return - - PyDict_SetItem(cache, self.name, value) - cache_readonly = CachedProperty diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 0813c12d573d5..d3a8f11a38715 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -1401,7 +1401,7 @@ def _is_strictly_monotonic_decreasing(self): def is_lexsorted_for_tuple(self, tup): return True - @cache_readonly(allow_setting=True) + @cache_readonly def is_unique(self): """ return if the index has unique values """ return self._engine.is_unique From 7e7bd8e297db6e8d63605fb033a72ed1606edf45 Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Mon, 5 Mar 2018 20:36:37 +0100 Subject: [PATCH 5/5] add test --- pandas/_libs/properties.pyx | 5 ++++- pandas/tests/test_lib.py | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/pandas/_libs/properties.pyx b/pandas/_libs/properties.pyx index b378b19f1acd7..67f58851a9a70 100644 --- a/pandas/_libs/properties.pyx +++ b/pandas/_libs/properties.pyx @@ -17,8 +17,11 @@ cdef class CachedProperty(object): self.__doc__ = getattr(func, '__doc__', None) def __get__(self, obj, typ): - # Get the cache or set a default one if needed + if obj is None: + # accessed on the class, not the instance + return self + # Get the cache or set a default one if needed cache = getattr(obj, '_cache', None) if cache is None: try: diff --git a/pandas/tests/test_lib.py b/pandas/tests/test_lib.py index 502f0c3bced61..3e34b48fb6795 100644 --- a/pandas/tests/test_lib.py +++ b/pandas/tests/test_lib.py @@ -3,6 +3,7 @@ import pytest import numpy as np +from pandas import Index from pandas._libs import lib, writers as libwriters import pandas.util.testing as tm @@ -198,3 +199,8 @@ def test_get_reverse_indexer(self): result = lib.get_reverse_indexer(indexer, 5) expected = np.array([4, 2, 3, 6, 7], dtype=np.int64) tm.assert_numpy_array_equal(result, expected) + + +def test_cache_readonly_preserve_docstrings(): + # GH18197 + assert Index.hasnans.__doc__ is not None