diff --git a/doc/source/whatsnew/v1.5.2.rst b/doc/source/whatsnew/v1.5.2.rst index dd909415d9e85..4654b7630c882 100644 --- a/doc/source/whatsnew/v1.5.2.rst +++ b/doc/source/whatsnew/v1.5.2.rst @@ -27,7 +27,7 @@ Fixed regressions Bug fixes ~~~~~~~~~ - Bug in the Copy-on-Write implementation losing track of views in certain chained indexing cases (:issue:`48996`) -- +- Fixed memory leak in :meth:`.Styler.to_excel` (:issue:`49751`) .. --------------------------------------------------------------------------- .. _whatsnew_152.other: diff --git a/pandas/io/formats/excel.py b/pandas/io/formats/excel.py index a26b85390fd49..1c2aa8f4262f6 100644 --- a/pandas/io/formats/excel.py +++ b/pandas/io/formats/excel.py @@ -170,10 +170,13 @@ def __init__(self, inherited: str | None = None) -> None: self.inherited = self.compute_css(inherited) else: self.inherited = None + # We should avoid lru_cache on the __call__ method. + # Otherwise once the method __call__ has been called + # garbage collection no longer deletes the instance. + self._call_cached = lru_cache(maxsize=None)(self._call_uncached) compute_css = CSSResolver() - @lru_cache(maxsize=None) def __call__( self, declarations: str | frozenset[tuple[str, str]] ) -> dict[str, dict[str, str]]: @@ -193,6 +196,11 @@ def __call__( A style as interpreted by ExcelWriter when found in ExcelCell.style. """ + return self._call_cached(declarations) + + def _call_uncached( + self, declarations: str | frozenset[tuple[str, str]] + ) -> dict[str, dict[str, str]]: properties = self.compute_css(declarations, self.inherited) return self.build_xlstyle(properties) diff --git a/pandas/tests/io/formats/test_to_excel.py b/pandas/tests/io/formats/test_to_excel.py index 7481baaee94f6..2a0f9f59972ef 100644 --- a/pandas/tests/io/formats/test_to_excel.py +++ b/pandas/tests/io/formats/test_to_excel.py @@ -357,7 +357,7 @@ def test_css_excel_cell_precedence(styles, expected): """It applies favors latter declarations over former declarations""" # See GH 47371 converter = CSSToExcelConverter() - converter.__call__.cache_clear() + converter._call_cached.cache_clear() css_styles = {(0, 0): styles} cell = CssExcelCell( row=0, @@ -369,7 +369,7 @@ def test_css_excel_cell_precedence(styles, expected): css_col=0, css_converter=converter, ) - converter.__call__.cache_clear() + converter._call_cached.cache_clear() assert cell.style == converter(expected) @@ -410,7 +410,7 @@ def test_css_excel_cell_cache(styles, cache_hits, cache_misses): """It caches unique cell styles""" # See GH 47371 converter = CSSToExcelConverter() - converter.__call__.cache_clear() + converter._call_cached.cache_clear() css_styles = {(0, i): _style for i, _style in enumerate(styles)} for css_row, css_col in css_styles: @@ -424,8 +424,8 @@ def test_css_excel_cell_cache(styles, cache_hits, cache_misses): css_col=css_col, css_converter=converter, ) - cache_info = converter.__call__.cache_info() - converter.__call__.cache_clear() + cache_info = converter._call_cached.cache_info() + converter._call_cached.cache_clear() assert cache_info.hits == cache_hits assert cache_info.misses == cache_misses diff --git a/pyproject.toml b/pyproject.toml index 1ccf577538db7..2c032fd4d8dea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -134,7 +134,6 @@ disable = [ "invalid-envvar-default", "invalid-overridden-method", "keyword-arg-before-vararg", - "method-cache-max-size-none", "non-parent-init-called", "overridden-final-method", "pointless-statement",