Skip to content

Commit a921d80

Browse files
authored
REF: Styler.export (#43318)
Co-authored-by: JHM Darbyshire (iMac) <[email protected]>
1 parent d1ee9ff commit a921d80

File tree

4 files changed

+141
-14
lines changed

4 files changed

+141
-14
lines changed

doc/source/user_guide/style.ipynb

+6-2
Original file line numberDiff line numberDiff line change
@@ -1380,8 +1380,12 @@
13801380
"metadata": {},
13811381
"outputs": [],
13821382
"source": [
1383-
"style1 = df2.style.applymap(style_negative, props='color:red;')\\\n",
1384-
" .applymap(lambda v: 'opacity: 20%;' if (v < 0.3) and (v > -0.3) else None)"
1383+
"style1 = df2.style\\\n",
1384+
" .applymap(style_negative, props='color:red;')\\\n",
1385+
" .applymap(lambda v: 'opacity: 20%;' if (v < 0.3) and (v > -0.3) else None)\\\n",
1386+
" .set_table_styles([{\"selector\": \"th\", \"props\": \"color: blue;\"}])\\\n",
1387+
" .hide_index()\n",
1388+
"style1"
13851389
]
13861390
},
13871391
{

doc/source/whatsnew/v1.4.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Styler
8282
- :meth:`Styler.to_html` omits CSSStyle rules for hidden table elements (:issue:`43619`)
8383
- Custom CSS classes can now be directly specified without string replacement (:issue:`43686`)
8484
- Bug where row trimming failed to reflect hidden rows (:issue:`43703`)
85+
- Update and expand the export and use mechanics (:issue:`40675`)
8586

8687
Formerly Styler relied on ``display.html.use_mathjax``, which has now been replaced by ``styler.html.mathjax``.
8788

pandas/io/formats/style.py

+99-12
Original file line numberDiff line numberDiff line change
@@ -1742,42 +1742,129 @@ def set_table_attributes(self, attributes: str) -> Styler:
17421742
self.table_attributes = attributes
17431743
return self
17441744

1745-
def export(self) -> list[tuple[Callable, tuple, dict]]:
1745+
def export(self) -> dict[str, Any]:
17461746
"""
1747-
Export the styles applied to the current ``Styler``.
1747+
Export the styles applied to the current Styler.
17481748
17491749
Can be applied to a second Styler with ``Styler.use``.
17501750
17511751
Returns
17521752
-------
1753-
styles : list
1753+
styles : dict
17541754
17551755
See Also
17561756
--------
1757-
Styler.use: Set the styles on the current ``Styler``.
1757+
Styler.use: Set the styles on the current Styler.
1758+
Styler.copy: Create a copy of the current Styler.
1759+
1760+
Notes
1761+
-----
1762+
This method is designed to copy non-data dependent attributes of
1763+
one Styler to another. It differs from ``Styler.copy`` where data and
1764+
data dependent attributes are also copied.
1765+
1766+
The following items are exported since they are not generally data dependent:
1767+
1768+
- Styling functions added by the ``apply`` and ``applymap``
1769+
- Whether axes and names are hidden from the display, if unambiguous.
1770+
- Table attributes
1771+
- Table styles
1772+
1773+
The following attributes are considered data dependent and therefore not
1774+
exported:
1775+
1776+
- Caption
1777+
- UUID
1778+
- Tooltips
1779+
- Any hidden rows or columns identified by Index labels
1780+
- Any formatting applied using ``Styler.format``
1781+
- Any CSS classes added using ``Styler.set_td_classes``
1782+
1783+
Examples
1784+
--------
1785+
1786+
>>> styler = DataFrame([[1, 2], [3, 4]]).style
1787+
>>> styler2 = DataFrame([[9, 9, 9]]).style
1788+
>>> styler.hide_index().highlight_max(axis=1) # doctest: +SKIP
1789+
>>> export = styler.export()
1790+
>>> styler2.use(export) # doctest: +SKIP
17581791
"""
1759-
return self._todo
1792+
return {
1793+
"apply": copy.copy(self._todo),
1794+
"table_attributes": self.table_attributes,
1795+
"table_styles": copy.copy(self.table_styles),
1796+
"hide_index": all(self.hide_index_),
1797+
"hide_columns": all(self.hide_columns_),
1798+
"hide_index_names": self.hide_index_names,
1799+
"hide_column_names": self.hide_column_names,
1800+
"css": copy.copy(self.css),
1801+
}
17601802

1761-
def use(self, styles: list[tuple[Callable, tuple, dict]]) -> Styler:
1803+
def use(self, styles: dict[str, Any]) -> Styler:
17621804
"""
1763-
Set the styles on the current ``Styler``.
1805+
Set the styles on the current Styler.
17641806
17651807
Possibly uses styles from ``Styler.export``.
17661808
17671809
Parameters
17681810
----------
1769-
styles : list
1770-
List of style functions.
1811+
styles : dict(str, Any)
1812+
List of attributes to add to Styler. Dict keys should contain only:
1813+
- "apply": list of styler functions, typically added with ``apply`` or
1814+
``applymap``.
1815+
- "table_attributes": HTML attributes, typically added with
1816+
``set_table_attributes``.
1817+
- "table_styles": CSS selectors and properties, typically added with
1818+
``set_table_styles``.
1819+
- "hide_index": whether the index is hidden, typically added with
1820+
``hide_index``, or a boolean list for hidden levels.
1821+
- "hide_columns": whether column headers are hidden, typically added with
1822+
``hide_columns``, or a boolean list for hidden levels.
1823+
- "hide_index_names": whether index names are hidden.
1824+
- "hide_column_names": whether column header names are hidden.
1825+
- "css": the css class names used.
17711826
17721827
Returns
17731828
-------
17741829
self : Styler
17751830
17761831
See Also
17771832
--------
1778-
Styler.export : Export the styles to applied to the current ``Styler``.
1779-
"""
1780-
self._todo.extend(styles)
1833+
Styler.export : Export the non data dependent attributes to the current Styler.
1834+
1835+
Examples
1836+
--------
1837+
1838+
>>> styler = DataFrame([[1, 2], [3, 4]]).style
1839+
>>> styler2 = DataFrame([[9, 9, 9]]).style
1840+
>>> styler.hide_index().highlight_max(axis=1) # doctest: +SKIP
1841+
>>> export = styler.export()
1842+
>>> styler2.use(export) # doctest: +SKIP
1843+
"""
1844+
self._todo.extend(styles.get("apply", []))
1845+
table_attributes: str = self.table_attributes or ""
1846+
obj_table_atts: str = (
1847+
""
1848+
if styles.get("table_attributes") is None
1849+
else str(styles.get("table_attributes"))
1850+
)
1851+
self.set_table_attributes((table_attributes + " " + obj_table_atts).strip())
1852+
if styles.get("table_styles"):
1853+
self.set_table_styles(styles.get("table_styles"), overwrite=False)
1854+
1855+
for obj in ["index", "columns"]:
1856+
hide_obj = styles.get("hide_" + obj)
1857+
if hide_obj is not None:
1858+
if isinstance(hide_obj, bool):
1859+
n = getattr(self, obj).nlevels
1860+
setattr(self, "hide_" + obj + "_", [hide_obj] * n)
1861+
else:
1862+
setattr(self, "hide_" + obj + "_", hide_obj)
1863+
1864+
self.hide_index_names = styles.get("hide_index_names", False)
1865+
self.hide_column_names = styles.get("hide_column_names", False)
1866+
if styles.get("css"):
1867+
self.css = styles.get("css") # type: ignore[assignment]
17811868
return self
17821869

17831870
def set_uuid(self, uuid: str) -> Styler:

pandas/tests/io/formats/style/test_style.py

+35
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def mi_styler(mi_df):
4242
@pytest.fixture
4343
def mi_styler_comp(mi_styler):
4444
# comprehensively add features to mi_styler
45+
mi_styler = mi_styler._copy(deepcopy=True)
4546
mi_styler.css = {**mi_styler.css, **{"row": "ROW", "col": "COL"}}
4647
mi_styler.uuid_len = 5
4748
mi_styler.uuid = "abcde"
@@ -257,6 +258,10 @@ def test_copy(comprehensive, render, deepcopy, mi_styler, mi_styler_comp):
257258
"cellstyle_map", # render time vars..
258259
"cellstyle_map_columns",
259260
"cellstyle_map_index",
261+
"template_latex", # render templates are class level
262+
"template_html",
263+
"template_html_style",
264+
"template_html_table",
260265
]
261266
if not deepcopy: # check memory locations are equal for all included attributes
262267
for attr in [a for a in styler.__dict__ if (not callable(a) and a not in excl)]:
@@ -311,6 +316,10 @@ def test_clear(mi_styler_comp):
311316
"cellstyle_map_index", # execution time only
312317
"precision", # deprecated
313318
"na_rep", # deprecated
319+
"template_latex", # render templates are class level
320+
"template_html",
321+
"template_html_style",
322+
"template_html_table",
314323
]
315324
# tests vars are not same vals on obj and clean copy before clear (except for excl)
316325
for attr in [a for a in styler.__dict__ if not (callable(a) or a in excl)]:
@@ -324,6 +333,32 @@ def test_clear(mi_styler_comp):
324333
assert all(res) if hasattr(res, "__iter__") else res
325334

326335

336+
def test_export(mi_styler_comp, mi_styler):
337+
exp_attrs = [
338+
"_todo",
339+
"hide_index_",
340+
"hide_index_names",
341+
"hide_columns_",
342+
"hide_column_names",
343+
"table_attributes",
344+
"table_styles",
345+
"css",
346+
]
347+
for attr in exp_attrs:
348+
check = getattr(mi_styler, attr) == getattr(mi_styler_comp, attr)
349+
assert not (
350+
all(check) if (hasattr(check, "__iter__") and len(check) > 0) else check
351+
)
352+
353+
export = mi_styler_comp.export()
354+
used = mi_styler.use(export)
355+
for attr in exp_attrs:
356+
check = getattr(used, attr) == getattr(mi_styler_comp, attr)
357+
assert all(check) if (hasattr(check, "__iter__") and len(check) > 0) else check
358+
359+
used.to_html()
360+
361+
327362
def test_hide_raises(mi_styler):
328363
msg = "`subset` and `level` cannot be passed simultaneously"
329364
with pytest.raises(ValueError, match=msg):

0 commit comments

Comments
 (0)