PERF: Improve Styler `to_excel` Performance
State: closed

- [x] closes #47352
- [x] [Tests added and passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#writing-tests) if fixing a bug or adding a new feature
- [x] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).
- [x] Added [type annotations](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#type-hints) to new arguments/methods/functions.
- [x] Added an entry in the latest `doc/source/whatsnew/vX.X.X.rst` file if fixing a bug or adding a new feature.


Labels:
name: Performance
description: Memory or execution speed performance
name: IO Excel
description: read_excel, to_excel

Comments:
Hello @tehunter! Thanks for updating this PR. We checked the lines you've touched for [PEP 8](https://www.python.org/dev/peps/pep-0008) issues, and found:









There are currently no PEP 8 issues detected in this Pull Request. Cheers! :beers: 

##### Comment last updated at 2022-06-29 14:18:30 UTC
ASV Results:

```
..       before           after         ratio
     [6f0be79b]       [db365209]
                      <styler-performance>
-       915±200ms         594±30ms     0.65  io.excel.WriteExcel.time_write_excel('openpyxl')
-       658±200ms         323±20ms     0.49  io.excel.WriteExcel.time_write_excel('xlsxwriter')
-       3.08±0.1s         934±40ms     0.30  io.excel.WriteExcelStyled.time_write_excel_style('xlsxwriter')

SOME BENCHMARKS HAVE CHANGED SIGNIFICANTLY.
PERFORMANCE INCREASED.
```
@github-actions pre-commit
Overview of changes:

- Moved `CSSResolver.expand_*` methods (which are accessed in `.atomize`) to a dictionary called `CSS_EXPANSIONS` which is keyed directly by the CSS property, instead of having to do a string replace and concatenation.
- `CSSResolver.__call__` now accepts an iterable of declaration tuples `(property, value)` in addition to the previous string type.
    - When an iterable is passed, `.parse` does not need to be called
- `CSSExcelCell.__init__` no longer converts the css_styles (i.e. `Styler.ctx`) from a list of tuples to a CSS string. Instead, the list is converted to a frozenset via a dictionary (to keep only the final instance of each property declaration)
    - The list of tuples was already obtained from the initial CSS string(s) in `Styler._update_ctx`, so we were previously parsing, reforming into string, and parsing again. 
- `CSSToExcelConverter.__call__` is now cached using `lru_cache`. The frozenset was necessary to make all the arguments hashable for the caching mechanism.
Benchmarks as of [8e56402](https://github.com/pandas-dev/pandas/pull/47371/commits/8e56402ed0639c231c98d06c25c62ee3da6f4524)
```
.       before           after         ratio
     [2b1184dd]       [8e56402e]
                      <styler-performance>
-        260±10ms          233±5ms     0.90  io.excel.WriteExcel.time_write_excel('xlsxwriter')
-        620±70ms         450±20ms     0.73  io.excel.WriteExcel.time_write_excel('openpyxl')
-      2.51±0.02s        1.42±0.3s     0.57  io.excel.WriteExcelStyled.time_write_excel_style('openpyxl')
-      1.69±0.07s         608±10ms     0.36  io.excel.WriteExcelStyled.time_write_excel_style('xlsxwriter')
- ```
Awesome improvement! Thanks @tehunter