Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/io/excel/_base.py: 25%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

419 statements  

1from __future__ import annotations 

2 

3from collections.abc import ( 

4 Callable, 

5 Hashable, 

6 Iterable, 

7 Mapping, 

8 Sequence, 

9) 

10import datetime 

11from decimal import Decimal 

12from functools import partial 

13import os 

14import sys 

15from typing import ( 

16 IO, 

17 TYPE_CHECKING, 

18 Any, 

19 Generic, 

20 Literal, 

21 Self, 

22 TypeVar, 

23 cast, 

24 overload, 

25) 

26import warnings 

27import zipfile 

28 

29from pandas._config.config import _global_config as config 

30 

31from pandas._libs import lib 

32from pandas.compat._optional import import_optional_dependency 

33from pandas.errors import ( 

34 EmptyDataError, 

35 Pandas4Warning, 

36) 

37from pandas.util._decorators import ( 

38 set_module, 

39) 

40from pandas.util._exceptions import find_stack_level 

41from pandas.util._validators import check_dtype_backend 

42 

43from pandas.core.dtypes.common import ( 

44 is_bool, 

45 is_decimal, 

46 is_float, 

47 is_integer, 

48 is_list_like, 

49) 

50 

51from pandas.core.frame import DataFrame 

52 

53from pandas.io.common import ( 

54 IOHandles, 

55 get_handle, 

56 stringify_path, 

57 validate_header_arg, 

58) 

59from pandas.io.excel._util import ( 

60 fill_mi_header, 

61 get_default_engine, 

62 get_writer, 

63 maybe_convert_usecols, 

64 pop_header_name, 

65) 

66from pandas.io.parsers import TextParser 

67from pandas.io.parsers.readers import validate_integer 

68 

69if TYPE_CHECKING: 

70 from types import TracebackType 

71 

72 from pandas._typing import ( 

73 DtypeArg, 

74 DtypeBackend, 

75 ExcelWriterIfSheetExists, 

76 FilePath, 

77 HashableT, 

78 IntStrT, 

79 ReadBuffer, 

80 SequenceNotStr, 

81 StorageOptions, 

82 WriteExcelBuffer, 

83 ) 

84 

85EXCEL_ROWS_MAX = 1_048_576 

86 

87 

88@overload 

89def read_excel( 

90 io, 

91 # sheet name is str or int -> DataFrame 

92 sheet_name: str | int = ..., 

93 *, 

94 header: int | Sequence[int] | None = ..., 

95 names: SequenceNotStr[Hashable] | range | None = ..., 

96 index_col: int | str | Sequence[int] | None = ..., 

97 usecols: int 

98 | str 

99 | Sequence[int] 

100 | Sequence[str] 

101 | Callable[[HashableT], bool] 

102 | None = ..., 

103 dtype: DtypeArg | None = ..., 

104 engine: Literal["xlrd", "openpyxl", "odf", "pyxlsb", "calamine"] | None = ..., 

105 converters: dict[str, Callable] | dict[int, Callable] | None = ..., 

106 true_values: Iterable[Hashable] | None = ..., 

107 false_values: Iterable[Hashable] | None = ..., 

108 skiprows: Sequence[int] | int | Callable[[int], object] | None = ..., 

109 nrows: int | None = ..., 

110 na_values=..., 

111 keep_default_na: bool = ..., 

112 na_filter: bool = ..., 

113 verbose: bool = ..., 

114 parse_dates: list | dict | bool = ..., 

115 date_format: dict[Hashable, str] | str | None = ..., 

116 thousands: str | None = ..., 

117 decimal: str = ..., 

118 comment: str | None = ..., 

119 skipfooter: int = ..., 

120 storage_options: StorageOptions = ..., 

121 dtype_backend: DtypeBackend | lib.NoDefault = ..., 

122) -> DataFrame: ... 

123 

124 

125@overload 

126def read_excel( 

127 io, 

128 # sheet name is list or None -> dict[IntStrT, DataFrame] 

129 sheet_name: list[IntStrT] | None, 

130 *, 

131 header: int | Sequence[int] | None = ..., 

132 names: SequenceNotStr[Hashable] | range | None = ..., 

133 index_col: int | str | Sequence[int] | None = ..., 

134 usecols: int 

135 | str 

136 | Sequence[int] 

137 | Sequence[str] 

138 | Callable[[HashableT], bool] 

139 | None = ..., 

140 dtype: DtypeArg | None = ..., 

141 engine: Literal["xlrd", "openpyxl", "odf", "pyxlsb", "calamine"] | None = ..., 

142 converters: dict[str, Callable] | dict[int, Callable] | None = ..., 

143 true_values: Iterable[Hashable] | None = ..., 

144 false_values: Iterable[Hashable] | None = ..., 

145 skiprows: Sequence[int] | int | Callable[[int], object] | None = ..., 

146 nrows: int | None = ..., 

147 na_values=..., 

148 keep_default_na: bool = ..., 

149 na_filter: bool = ..., 

150 verbose: bool = ..., 

151 parse_dates: list | dict | bool = ..., 

152 date_format: dict[Hashable, str] | str | None = ..., 

153 thousands: str | None = ..., 

154 decimal: str = ..., 

155 comment: str | None = ..., 

156 skipfooter: int = ..., 

157 storage_options: StorageOptions = ..., 

158 dtype_backend: DtypeBackend | lib.NoDefault = ..., 

159) -> dict[IntStrT, DataFrame]: ... 

160 

161 

162@set_module("pandas") 

163def read_excel( 

164 io, 

165 sheet_name: str | int | list[IntStrT] | None = 0, 

166 *, 

167 header: int | Sequence[int] | None = 0, 

168 names: SequenceNotStr[Hashable] | range | None = None, 

169 index_col: int | str | Sequence[int] | None = None, 

170 usecols: int 

171 | str 

172 | Sequence[int] 

173 | Sequence[str] 

174 | Callable[[HashableT], bool] 

175 | None = None, 

176 dtype: DtypeArg | None = None, 

177 engine: Literal["xlrd", "openpyxl", "odf", "pyxlsb", "calamine"] | None = None, 

178 converters: dict[str, Callable] | dict[int, Callable] | None = None, 

179 true_values: Iterable[Hashable] | None = None, 

180 false_values: Iterable[Hashable] | None = None, 

181 skiprows: Sequence[int] | int | Callable[[int], object] | None = None, 

182 nrows: int | None = None, 

183 na_values=None, 

184 keep_default_na: bool = True, 

185 na_filter: bool = True, 

186 verbose: bool = False, 

187 parse_dates: list | dict | bool = False, 

188 date_format: dict[Hashable, str] | str | None = None, 

189 thousands: str | None = None, 

190 decimal: str = ".", 

191 comment: str | None = None, 

192 skipfooter: int = 0, 

193 storage_options: StorageOptions | None = None, 

194 dtype_backend: DtypeBackend | lib.NoDefault = lib.no_default, 

195 engine_kwargs: dict | None = None, 

196) -> DataFrame | dict[IntStrT, DataFrame]: 

197 """ 

198 Read an Excel file into a ``DataFrame``. 

199 

200 Supports `xls`, `xlsx`, `xlsm`, `xlsb`, `odf`, `ods` and `odt` file extensions 

201 read from a local filesystem or URL. Supports an option to read 

202 a single sheet or a list of sheets. 

203 

204 This function requires an external library depending on the 

205 file format; see the ``engine`` parameter below. 

206 

207 Parameters 

208 ---------- 

209 io : str, ExcelFile, workbook object, path object, or file-like object 

210 Any valid string path is acceptable. The string could be a URL. Valid 

211 URL schemes include http, ftp, s3, and file. For file URLs, a host is 

212 expected. A local file could be: ``file://localhost/path/to/table.xlsx``. 

213 

214 Certain URL schemes may require additional packages. For example, S3 

215 URLs require the ``s3fs`` library. See 

216 :ref:`install.optional_dependencies` for a full list. 

217 

218 If you want to pass in a path object, pandas accepts any ``os.PathLike``. 

219 

220 By file-like object, we refer to objects with a ``read()`` method, 

221 such as a file handle (e.g. via builtin ``open`` function) 

222 or ``StringIO``. 

223 

224 A workbook object from a supported engine's library, such as an 

225 ``openpyxl.Workbook``, is read with that engine. 

226 

227 sheet_name : str, int, list, or None, default 0 

228 Strings are used for sheet names. Integers are used in zero-indexed 

229 sheet positions (chart sheets do not count as a sheet position). 

230 Lists of strings/integers are used to request multiple sheets. 

231 When ``None``, will return a dictionary containing DataFrames for each sheet. 

232 

233 Available cases: 

234 

235 * Defaults to ``0``: 1st sheet as a `DataFrame` 

236 * ``1``: 2nd sheet as a `DataFrame` 

237 * ``"Sheet1"``: Load sheet with name "Sheet1" 

238 * ``[0, 1, "Sheet5"]``: Load first, second and sheet named "Sheet5" 

239 as a dict of `DataFrame` 

240 * ``None``: Returns a dictionary containing DataFrames for each sheet. 

241 

242 header : int, list of int, default 0 

243 Row (0-indexed) to use for the column labels of the parsed 

244 DataFrame. If a list of integers is passed those row positions will 

245 be combined into a ``MultiIndex``. Use None if there is no header. 

246 names : array-like, default None 

247 List of column names to use. If file contains no header row, 

248 then you should explicitly pass header=None. 

249 index_col : int, str, list of int, default None 

250 Column (0-indexed) to use as the row labels of the DataFrame. 

251 Pass None if there is no such column. If a list is passed, 

252 those columns will be combined into a ``MultiIndex``. If a 

253 subset of data is selected with ``usecols``, index_col 

254 is based on the subset. 

255 

256 Missing values will be forward filled to allow roundtripping with 

257 ``to_excel`` for ``merged_cells=True``. To avoid forward filling the 

258 missing values use ``set_index`` after reading the data instead of 

259 ``index_col``. 

260 

261 When ``header`` is a list of two or more rows (``MultiIndex`` 

262 columns), the row after the last header row is expected to hold the 

263 index name(s) — blank if the index is unnamed — as written by 

264 ``to_excel``. A file lacking this separator row can lose its first 

265 data row to the index name(s); see :ref:`the user guide 

266 <io.excel.reading_multiindex>` for details. 

267 usecols : str, list-like, or callable, default None 

268 * If None, then parse all columns. 

269 * If str, then indicates comma separated list of Excel column letters 

270 and column ranges (e.g. "A:E" or "A,C,E:F"). Ranges are inclusive of 

271 both sides. 

272 * If list of int, then indicates list of column numbers to be parsed 

273 (0-indexed). 

274 * If list of string, then indicates list of column names to be parsed. 

275 * If callable, then evaluate each column name against it and parse the 

276 column if the callable returns ``True``. 

277 

278 Returns a subset of the columns according to behavior above. 

279 Element order is ignored, so ``usecols=[0, 1]`` is the same as ``[1, 0]``. 

280 To instantiate a :class:`~pandas.DataFrame` with element order preserved 

281 use ``pd.read_excel(path, usecols=['foo', 'bar'])[['foo', 'bar']]`` for 

282 columns in ``['foo', 'bar']`` order or 

283 ``pd.read_excel(path, usecols=['foo', 'bar'])[['bar', 'foo']]`` for 

284 ``['bar', 'foo']`` order. 

285 dtype : Type name or dict of column -> type, default None 

286 Data type for data or columns. E.g. {'a': np.float64, 'b': np.int32} 

287 Use ``object`` to preserve data as stored in Excel and not interpret dtype, 

288 which will necessarily result in ``object`` dtype. 

289 Specifying a ``dtype`` does not change how missing values are parsed; 

290 values recognized as missing (see ``na_values`` and ``keep_default_na``) 

291 are still read as ``NaN`` (or the dtype's NA value) rather than cast to 

292 the requested ``dtype``. 

293 If converters are specified, they will be applied INSTEAD 

294 of dtype conversion. 

295 If you use ``None``, it will infer the dtype of each column based on the data. 

296 engine : {'openpyxl', 'calamine', 'odf', 'pyxlsb', 'xlrd'}, default None 

297 Engine compatibility : 

298 

299 - ``openpyxl`` supports newer Excel file formats. 

300 - ``calamine`` supports Excel (.xls, .xlsx, .xlsm, .xlsb) 

301 and OpenDocument (.ods) file formats. 

302 - ``odf`` supports OpenDocument file formats (.odf, .ods, .odt). 

303 - ``pyxlsb`` supports Binary Excel files. 

304 - ``xlrd`` supports old-style Excel files (.xls). 

305 

306 When ``engine=None``, the following logic will be used to determine the engine: 

307 

308 - If ``io`` is a workbook object, the engine that created it will be used. 

309 - Otherwise if ``path_or_buffer`` is an OpenDocument format (.odf, .ods, .odt), 

310 then `odf <https://pypi.org/project/odfpy/>`_ will be used. 

311 - Otherwise if ``path_or_buffer`` is an xls format, ``xlrd`` will be used. 

312 - Otherwise if ``path_or_buffer`` is in xlsb format, ``pyxlsb`` will be used. 

313 - Otherwise ``openpyxl`` will be used. 

314 

315 .. deprecated:: 3.1.0 

316 For xlsx/xlsm files the default engine will change from 

317 ``openpyxl`` to ``calamine`` in a future version. Pass ``engine`` 

318 explicitly, or set the ``io.excel.xlsx.reader`` option (xlsm 

319 files are format-detected as xlsx), to keep the current behavior. 

320 

321 converters : dict, default None 

322 Dict of functions for converting values in certain columns. Keys can 

323 either be integers or column labels, values are functions that take one 

324 input argument, the Excel cell content, and return the transformed 

325 content. 

326 true_values : list, default None 

327 Values to consider as True. 

328 false_values : list, default None 

329 Values to consider as False. 

330 skiprows : list-like, int, or callable, optional 

331 Line numbers to skip (0-indexed) or number of lines to skip (int) at the 

332 start of the file. If callable, the callable function will be evaluated 

333 against the row indices, returning True if the row should be skipped and 

334 False otherwise. An example of a valid callable argument would be ``lambda 

335 x: x in [0, 2]``. 

336 nrows : int, default None 

337 Number of rows to parse. Does not include header rows. 

338 na_values : scalar, str, list-like, or dict, default None 

339 Additional strings to recognize as NA/NaN. If dict passed, specific 

340 per-column NA values. By default the following values are interpreted 

341 as NaN: '', '#N/A', '#N/A N/A', '#NA', '-1.#IND', '-1.#QNAN', '-NaN', '-nan', 

342 '1.#IND', '1.#QNAN', '<NA>', 'N/A', 'NA', 'NULL', 'NaN', 'None', 

343 'n/a', 'nan', 'null'. 

344 keep_default_na : bool, default True 

345 Whether or not to include the default NaN values when parsing the data. 

346 Depending on whether ``na_values`` is passed in, the behavior is as follows: 

347 

348 * If ``keep_default_na`` is True, and ``na_values`` are specified, 

349 ``na_values`` is appended to the default NaN values used for parsing. 

350 * If ``keep_default_na`` is True, and ``na_values`` are not specified, only 

351 the default NaN values are used for parsing. 

352 * If ``keep_default_na`` is False, and ``na_values`` are specified, only 

353 the NaN values specified ``na_values`` are used for parsing. 

354 * If ``keep_default_na`` is False, and ``na_values`` are not specified, no 

355 strings will be parsed as NaN. 

356 

357 Note that if `na_filter` is passed in as False, the ``keep_default_na`` and 

358 ``na_values`` parameters will be ignored. 

359 na_filter : bool, default True 

360 Detect missing value markers (empty strings and the value of na_values). In 

361 data without any NAs, passing ``na_filter=False`` can improve the 

362 performance of reading a large file. 

363 verbose : bool, default False 

364 Indicate number of NA values placed in non-numeric columns. 

365 parse_dates : bool, list-like, or dict, default False 

366 The behavior is as follows: 

367 

368 * ``bool``. If True -> try parsing the index. 

369 * ``list`` of int or names. e.g. If [1, 2, 3] -> try parsing columns 1, 2, 3 

370 each as a separate date column. 

371 * ``list`` of lists. e.g. If [[1, 3]] -> combine columns 1 and 3 and parse as 

372 a single date column. 

373 * ``dict``, e.g. {'foo' : [1, 3]} -> parse columns 1, 3 as date and call 

374 result 'foo' 

375 

376 If a column or index contains an unparsable date, the entire column or 

377 index will be returned unaltered as an object data type. If you don`t want to 

378 parse some cells as date just change their type in Excel to "Text". 

379 For non-standard datetime parsing, use ``pd.to_datetime`` after 

380 ``pd.read_excel``. 

381 

382 Note: A fast-path exists for iso8601-formatted dates. 

383 date_format : str or dict of column -> format, default ``None`` 

384 If used in conjunction with ``parse_dates``, will parse dates according to this 

385 format. For anything more complex, 

386 please read in as ``object`` and then apply :func:`to_datetime` as-needed. 

387 

388 .. versionadded:: 2.0.0 

389 

390 thousands : str, default None 

391 Thousands separator for parsing string columns to numeric. Note that 

392 this parameter is only necessary for columns stored as TEXT in Excel, 

393 any numeric columns will automatically be parsed, regardless of display 

394 format. 

395 decimal : str, default '.' 

396 Character to recognize as decimal point for parsing string columns to numeric. 

397 Note that this parameter is only necessary for columns stored as TEXT in Excel, 

398 any numeric columns will automatically be parsed, regardless of display 

399 format.(e.g. use ',' for European data). 

400 comment : str, default None 

401 Comments out remainder of line. Pass a character or characters to this 

402 argument to indicate comments in the input file. Any data between the 

403 comment string and the end of the current line is ignored. 

404 skipfooter : int, default 0 

405 Rows at the end to skip (0-indexed). 

406 storage_options : dict, optional 

407 Extra options that make sense for a particular storage connection, e.g. 

408 host, port, username, password, etc. For HTTP(S) URLs the key-value pairs 

409 are forwarded to ``urllib.request.Request`` as header options. For other 

410 URLs (e.g. starting with "s3://", and "gcs://") the key-value pairs are 

411 forwarded to ``fsspec.open``. Please see ``fsspec`` and ``urllib`` for more 

412 details, and for more examples on storage options refer `here 

413 <https://pandas.pydata.org/docs/user_guide/io.html? 

414 highlight=storage_options#reading-writing-remote-files>`_. 

415 

416 dtype_backend : {'numpy_nullable', 'pyarrow'} 

417 Back-end data type applied to the resultant :class:`DataFrame` 

418 (still experimental). If not specified, the default behavior 

419 is to not use nullable data types. If specified, the behavior 

420 is as follows: 

421 

422 * ``"numpy_nullable"``: returns nullable-dtype-backed :class:`DataFrame` 

423 * ``"pyarrow"``: returns pyarrow-backed nullable :class:`ArrowDtype` 

424 :class:`DataFrame` 

425 

426 .. versionadded:: 2.0 

427 

428 engine_kwargs : dict, optional 

429 Arbitrary keyword arguments passed to excel engine. 

430 

431 Returns 

432 ------- 

433 DataFrame or dict of DataFrames 

434 DataFrame from the passed in Excel file. See notes in sheet_name 

435 argument for more information on when a dict of DataFrames is returned. 

436 

437 See Also 

438 -------- 

439 DataFrame.to_excel : Write DataFrame to an Excel file. 

440 DataFrame.to_csv : Write DataFrame to a comma-separated values (csv) file. 

441 read_csv : Read a comma-separated values (csv) file into DataFrame. 

442 read_fwf : Read a table of fixed-width formatted lines into DataFrame. 

443 

444 Notes 

445 ----- 

446 For specific information on the methods used for each Excel engine, refer to the 

447 pandas 

448 :ref:`user guide <io.excel_reader>` 

449 

450 Examples 

451 -------- 

452 The file can be read using the file name as string or an open file object: 

453 

454 >>> pd.read_excel("tmp.xlsx", index_col=0) # doctest: +SKIP 

455 Name Value 

456 0 string1 1 

457 1 string2 2 

458 2 #Comment 3 

459 

460 >>> pd.read_excel(open("tmp.xlsx", "rb"), sheet_name="Sheet3") # doctest: +SKIP 

461 Unnamed: 0 Name Value 

462 0 0 string1 1 

463 1 1 string2 2 

464 2 2 #Comment 3 

465 

466 Index and header can be specified via the `index_col` and `header` arguments 

467 

468 >>> pd.read_excel("tmp.xlsx", index_col=None, header=None) # doctest: +SKIP 

469 0 1 2 

470 0 NaN Name Value 

471 1 0.0 string1 1 

472 2 1.0 string2 2 

473 3 2.0 #Comment 3 

474 

475 Column types are inferred but can be explicitly specified 

476 

477 >>> pd.read_excel( 

478 ... "tmp.xlsx", index_col=0, dtype={"Name": str, "Value": float} 

479 ... ) # doctest: +SKIP 

480 Name Value 

481 0 string1 1.0 

482 1 string2 2.0 

483 2 #Comment 3.0 

484 

485 True, False, and NA values, and thousands separators have defaults, 

486 but can be explicitly specified, too. Supply the values you would like 

487 as strings or lists of strings! 

488 

489 >>> pd.read_excel( 

490 ... "tmp.xlsx", index_col=0, na_values=["string1", "string2"] 

491 ... ) # doctest: +SKIP 

492 Name Value 

493 0 NaN 1 

494 1 NaN 2 

495 2 #Comment 3 

496 

497 Comment lines in the excel input file can be skipped using the 

498 ``comment`` kwarg. 

499 

500 >>> pd.read_excel("tmp.xlsx", index_col=0, comment="#") # doctest: +SKIP 

501 Name Value 

502 0 string1 1.0 

503 1 string2 2.0 

504 2 None NaN 

505 """ 

506 check_dtype_backend(dtype_backend) 

507 should_close = False 

508 if engine_kwargs is None: 

509 engine_kwargs = {} 

510 

511 if not isinstance(io, ExcelFile): 

512 should_close = True 

513 io = ExcelFile( 

514 io, 

515 storage_options=storage_options, 

516 engine=engine, 

517 engine_kwargs=engine_kwargs, 

518 ) 

519 elif engine and engine != io.engine: 

520 raise ValueError( 

521 "Engine should not be specified when passing " 

522 "an ExcelFile - ExcelFile already has the engine set" 

523 ) 

524 

525 try: 

526 data = io._reader.parse( 

527 sheet_name=sheet_name, 

528 header=header, 

529 names=names, 

530 index_col=index_col, 

531 usecols=usecols, 

532 dtype=dtype, 

533 converters=converters, 

534 true_values=true_values, 

535 false_values=false_values, 

536 skiprows=skiprows, 

537 nrows=nrows, 

538 na_values=na_values, 

539 keep_default_na=keep_default_na, 

540 na_filter=na_filter, 

541 verbose=verbose, 

542 parse_dates=parse_dates, 

543 date_format=date_format, 

544 thousands=thousands, 

545 decimal=decimal, 

546 comment=comment, 

547 skipfooter=skipfooter, 

548 dtype_backend=dtype_backend, 

549 ) 

550 finally: 

551 # make sure to close opened file handles 

552 if should_close: 

553 io.close() 

554 return data 

555 

556 

557_WorkbookT = TypeVar("_WorkbookT") 

558 

559 

560class BaseExcelReader(Generic[_WorkbookT]): 

561 book: _WorkbookT 

562 

563 def __init__( 

564 self, 

565 filepath_or_buffer, 

566 storage_options: StorageOptions | None = None, 

567 engine_kwargs: dict | None = None, 

568 ) -> None: 

569 if engine_kwargs is None: 

570 engine_kwargs = {} 

571 

572 self.handles = IOHandles( 

573 handle=filepath_or_buffer, compression={"method": None} 

574 ) 

575 if not isinstance(filepath_or_buffer, (ExcelFile, self._workbook_class)): 

576 self.handles = get_handle( 

577 filepath_or_buffer, "rb", storage_options=storage_options, is_text=False 

578 ) 

579 

580 if isinstance(self.handles.handle, self._workbook_class): 

581 self.book = self.handles.handle 

582 elif hasattr(self.handles.handle, "read"): 

583 # N.B. xlrd.Book has a read attribute too 

584 self.handles.handle.seek(0) 

585 try: 

586 self.book = self.load_workbook(self.handles.handle, engine_kwargs) 

587 except Exception: 

588 self.close() 

589 raise 

590 else: 

591 raise ValueError( 

592 "Must explicitly set engine if not passing in buffer or path for io." 

593 ) 

594 

595 @property 

596 def _workbook_class(self) -> type[_WorkbookT]: 

597 raise NotImplementedError 

598 

599 def load_workbook(self, filepath_or_buffer, engine_kwargs) -> _WorkbookT: 

600 raise NotImplementedError 

601 

602 def close(self) -> None: 

603 if hasattr(self, "book"): 

604 if hasattr(self.book, "close"): 

605 # pyxlsb: opens a TemporaryFile 

606 # openpyxl: https://stackoverflow.com/questions/31416842/ 

607 # openpyxl-does-not-close-excel-workbook-in-read-only-mode 

608 self.book.close() 

609 elif hasattr(self.book, "release_resources"): 

610 # xlrd 

611 # https://github.com/python-excel/xlrd/blob/2.0.1/xlrd/book.py#L548 

612 self.book.release_resources() 

613 self.handles.close() 

614 

615 @property 

616 def sheet_names(self) -> list[str]: 

617 raise NotImplementedError 

618 

619 def get_sheet_by_name(self, name: str): 

620 raise NotImplementedError 

621 

622 def get_sheet_by_index(self, index: int): 

623 raise NotImplementedError 

624 

625 def get_sheet_data(self, sheet, rows: int | None = None): 

626 raise NotImplementedError 

627 

628 def raise_if_bad_sheet_by_index(self, index: int) -> None: 

629 n_sheets = len(self.sheet_names) 

630 if index >= n_sheets: 

631 raise ValueError( 

632 f"Worksheet index {index} is invalid, {n_sheets} worksheets found" 

633 ) 

634 

635 def raise_if_bad_sheet_by_name(self, name: str) -> None: 

636 if name not in self.sheet_names: 

637 raise ValueError(f"Worksheet named '{name}' not found") 

638 

639 def _check_skiprows_func( 

640 self, 

641 skiprows: Callable, 

642 rows_to_use: int, 

643 ) -> int | None: 

644 """ 

645 Determine how many file rows are required to obtain `nrows` data 

646 rows when `skiprows` is a function. 

647 

648 Parameters 

649 ---------- 

650 skiprows : function 

651 The function passed to read_excel by the user. 

652 rows_to_use : int 

653 The number of rows that will be needed for the header and 

654 the data. 

655 

656 Returns 

657 ------- 

658 int 

659 """ 

660 i = 0 

661 rows_used_so_far = 0 

662 while rows_used_so_far < rows_to_use: 

663 if not skiprows(i): 

664 rows_used_so_far += 1 

665 i += 1 

666 if i >= EXCEL_ROWS_MAX: 

667 return None 

668 return i 

669 

670 def _calc_rows( 

671 self, 

672 header: int | Sequence[int] | None, 

673 index_col: int | Sequence[int] | None, 

674 skiprows: Sequence[int] | int | Callable[[int], object] | None, 

675 nrows: int | None, 

676 ) -> int | None: 

677 """ 

678 If nrows specified, find the number of rows needed from the 

679 file, otherwise return None. 

680 

681 

682 Parameters 

683 ---------- 

684 header : int, list of int, or None 

685 See read_excel docstring. 

686 index_col : int, str, list of int, or None 

687 See read_excel docstring. 

688 skiprows : list-like, int, callable, or None 

689 See read_excel docstring. 

690 nrows : int or None 

691 See read_excel docstring. 

692 

693 Returns 

694 ------- 

695 int or None 

696 """ 

697 if nrows is None: 

698 return None 

699 if header is None: 

700 header_rows = 1 

701 elif is_integer(header): 

702 header = cast("int", header) 

703 header_rows = 1 + header 

704 else: 

705 header = cast("Sequence", header) 

706 header_rows = 1 + header[-1] 

707 # If there is a MultiIndex header and an index then there is also 

708 # a row containing just the index name(s) 

709 if is_list_like(header) and index_col is not None: 

710 header = cast("Sequence", header) 

711 if len(header) > 1: 

712 header_rows += 1 

713 if skiprows is None: 

714 return header_rows + nrows 

715 if is_integer(skiprows): 

716 skiprows = cast("int", skiprows) 

717 return header_rows + nrows + skiprows 

718 if is_list_like(skiprows): 

719 

720 def f(skiprows: Sequence, x: int) -> bool: 

721 return x in skiprows 

722 

723 skiprows = cast("Sequence", skiprows) 

724 return self._check_skiprows_func(partial(f, skiprows), header_rows + nrows) 

725 if callable(skiprows): 

726 return self._check_skiprows_func( 

727 skiprows, 

728 header_rows + nrows, 

729 ) 

730 # else unexpected skiprows type: read_excel will not optimize 

731 # the number of rows read from file 

732 return None 

733 

734 def parse( 

735 self, 

736 sheet_name: str | int | list[int] | list[str] | None = 0, 

737 header: int | Sequence[int] | None = 0, 

738 names: SequenceNotStr[Hashable] | range | None = None, 

739 index_col: int | Sequence[int] | None = None, 

740 usecols=None, 

741 dtype: DtypeArg | None = None, 

742 true_values: Iterable[Hashable] | None = None, 

743 false_values: Iterable[Hashable] | None = None, 

744 skiprows: Sequence[int] | int | Callable[[int], object] | None = None, 

745 nrows: int | None = None, 

746 na_values=None, 

747 verbose: bool = False, 

748 parse_dates: list | dict | bool = False, 

749 date_format: dict[Hashable, str] | str | None = None, 

750 thousands: str | None = None, 

751 decimal: str = ".", 

752 comment: str | None = None, 

753 skipfooter: int = 0, 

754 dtype_backend: DtypeBackend | lib.NoDefault = lib.no_default, 

755 **kwds, 

756 ): 

757 validate_header_arg(header) 

758 validate_integer("nrows", nrows) 

759 

760 ret_dict = False 

761 

762 # Keep sheetname to maintain backwards compatibility. 

763 sheets: list[int] | list[str] 

764 if isinstance(sheet_name, list): 

765 sheets = sheet_name 

766 ret_dict = True 

767 elif sheet_name is None: 

768 sheets = self.sheet_names 

769 ret_dict = True 

770 elif isinstance(sheet_name, str): 

771 sheets = [sheet_name] 

772 else: 

773 sheets = [sheet_name] 

774 

775 # handle same-type duplicates. 

776 sheets = cast("list[int] | list[str]", list(dict.fromkeys(sheets).keys())) 

777 

778 output = {} 

779 

780 last_sheetname = None 

781 for asheetname in sheets: 

782 last_sheetname = asheetname 

783 if verbose: 

784 print(f"Reading sheet {asheetname}") 

785 

786 if isinstance(asheetname, str): 

787 sheet = self.get_sheet_by_name(asheetname) 

788 else: # assume an integer if not a string 

789 sheet = self.get_sheet_by_index(asheetname) 

790 

791 file_rows_needed = self._calc_rows(header, index_col, skiprows, nrows) 

792 data = self.get_sheet_data(sheet, file_rows_needed) 

793 if hasattr(sheet, "close"): 

794 # pyxlsb opens two TemporaryFiles 

795 sheet.close() 

796 usecols = maybe_convert_usecols(usecols) 

797 

798 if not data: 

799 output[asheetname] = DataFrame() 

800 continue 

801 

802 output = self._parse_sheet( 

803 data=data, 

804 output=output, 

805 asheetname=asheetname, 

806 header=header, 

807 names=names, 

808 index_col=index_col, 

809 usecols=usecols, 

810 dtype=dtype, 

811 skiprows=skiprows, 

812 nrows=nrows, 

813 true_values=true_values, 

814 false_values=false_values, 

815 na_values=na_values, 

816 parse_dates=parse_dates, 

817 date_format=date_format, 

818 thousands=thousands, 

819 decimal=decimal, 

820 comment=comment, 

821 skipfooter=skipfooter, 

822 dtype_backend=dtype_backend, 

823 **kwds, 

824 ) 

825 

826 if last_sheetname is None: 

827 raise ValueError("Sheet name is an empty list") 

828 

829 if ret_dict: 

830 return output 

831 else: 

832 return output[last_sheetname] 

833 

834 def _parse_sheet( 

835 self, 

836 data: list, 

837 output: dict, 

838 asheetname: str | int | None = None, 

839 header: int | Sequence[int] | None = 0, 

840 names: SequenceNotStr[Hashable] | range | None = None, 

841 index_col: int | Sequence[int] | None = None, 

842 usecols=None, 

843 dtype: DtypeArg | None = None, 

844 skiprows: Sequence[int] | int | Callable[[int], object] | None = None, 

845 nrows: int | None = None, 

846 true_values: Iterable[Hashable] | None = None, 

847 false_values: Iterable[Hashable] | None = None, 

848 na_values=None, 

849 parse_dates: list | dict | bool = False, 

850 date_format: dict[Hashable, str] | str | None = None, 

851 thousands: str | None = None, 

852 decimal: str = ".", 

853 comment: str | None = None, 

854 skipfooter: int = 0, 

855 dtype_backend: DtypeBackend | lib.NoDefault = lib.no_default, 

856 **kwds, 

857 ): 

858 is_list_header = False 

859 is_len_one_list_header = False 

860 if is_list_like(header): 

861 assert isinstance(header, Sequence) 

862 is_list_header = True 

863 if len(header) == 1: 

864 is_len_one_list_header = True 

865 

866 if is_len_one_list_header: 

867 header = cast("Sequence[int]", header)[0] 

868 

869 # forward fill and pull out names for MultiIndex column 

870 header_names = None 

871 if header is not None and is_list_like(header): 

872 assert isinstance(header, Sequence) 

873 

874 header_names = [] 

875 control_row = [True] * len(data[0]) 

876 

877 for row in header: 

878 if is_integer(skiprows): 

879 assert isinstance(skiprows, int) 

880 row += skiprows 

881 

882 if row > len(data) - 1: 

883 raise ValueError( 

884 f"header index {row} exceeds maximum index " 

885 f"{len(data) - 1} of data.", 

886 ) 

887 

888 data[row], control_row = fill_mi_header(data[row], control_row) 

889 

890 if index_col is not None: 

891 header_name, _ = pop_header_name(data[row], index_col) 

892 header_names.append(header_name) 

893 

894 # If there is a MultiIndex header and an index then there is also 

895 # a row containing just the index name(s) 

896 has_index_names = False 

897 if is_list_header and not is_len_one_list_header and index_col is not None: 

898 index_col_set: set[int] 

899 if isinstance(index_col, int): 

900 index_col_set = {index_col} 

901 else: 

902 assert isinstance(index_col, Sequence) 

903 index_col_set = set(index_col) 

904 

905 # We have to handle mi without names. If any of the entries in the data 

906 # columns are not empty, this is a regular row 

907 assert isinstance(header, Sequence) 

908 if len(header) < len(data): 

909 potential_index_names = data[len(header)] 

910 has_index_names = all( 

911 x == "" or x is None 

912 for i, x in enumerate(potential_index_names) 

913 if not control_row[i] and i not in index_col_set 

914 ) 

915 

916 if is_list_like(index_col): 

917 # Forward fill values for MultiIndex index. 

918 if header is None: 

919 offset = 0 

920 elif isinstance(header, int): 

921 offset = 1 + header 

922 else: 

923 offset = 1 + max(header) 

924 

925 # GH34673: if MultiIndex names present and not defined in the header, 

926 # offset needs to be incremented so that forward filling starts 

927 # from the first MI value instead of the name 

928 if has_index_names: 

929 offset += 1 

930 

931 # Check if we have an empty dataset 

932 # before trying to collect data. 

933 if offset < len(data): 

934 assert isinstance(index_col, Sequence) 

935 

936 for col in index_col: 

937 last = data[offset][col] 

938 

939 for row in range(offset + 1, len(data)): 

940 if data[row][col] == "" or data[row][col] is None: 

941 data[row][col] = last 

942 else: 

943 last = data[row][col] 

944 

945 # GH 12292 : error when read one empty column from excel file 

946 try: 

947 parser = TextParser( 

948 data, 

949 names=names, 

950 header=header, 

951 index_col=index_col, 

952 has_index_names=has_index_names, 

953 dtype=dtype, 

954 true_values=true_values, 

955 false_values=false_values, 

956 skiprows=skiprows, 

957 nrows=nrows, 

958 na_values=na_values, 

959 skip_blank_lines=False, # GH 39808 

960 parse_dates=parse_dates, 

961 date_format=date_format, 

962 thousands=thousands, 

963 decimal=decimal, 

964 comment=comment, 

965 skipfooter=skipfooter, 

966 usecols=usecols, 

967 dtype_backend=dtype_backend, 

968 **kwds, 

969 ) 

970 

971 output[asheetname] = parser.read(nrows=nrows) 

972 

973 if header_names: 

974 output[asheetname].columns = output[asheetname].columns.set_names( 

975 header_names 

976 ) 

977 

978 except EmptyDataError: 

979 # No Data, return an empty DataFrame 

980 output[asheetname] = DataFrame() 

981 

982 except Exception as err: 

983 err.args = (f"{err.args[0]} (sheet: {asheetname})", *err.args[1:]) 

984 raise err 

985 

986 return output 

987 

988 

989@set_module("pandas") 

990class ExcelWriter(Generic[_WorkbookT]): 

991 """ 

992 Class for writing DataFrame objects into excel sheets. 

993 

994 The default ``engine`` is chosen based on the file extension. The values 

995 below are the engine strings (i.e. valid values for the ``engine`` 

996 keyword); the package each one wraps is shown in parentheses: 

997 

998 * ``.xlsx``: ``"xlsxwriter"`` (`XlsxWriter 

999 <https://pypi.org/project/XlsxWriter/>`__) if installed, otherwise 

1000 ``"openpyxl"`` (`openpyxl <https://pypi.org/project/openpyxl/>`__). 

1001 * ``.xlsm``: ``"openpyxl"`` (`openpyxl 

1002 <https://pypi.org/project/openpyxl/>`__). 

1003 * ``.ods``: ``"odf"`` (`odfpy <https://pypi.org/project/odfpy/>`__). 

1004 

1005 These defaults can be overridden via the ``engine`` argument or by 

1006 setting the ``io.excel.<extension>.writer`` option. 

1007 

1008 See :meth:`DataFrame.to_excel` for typical usage. 

1009 

1010 The writer should be used as a context manager. Otherwise, call `close()` to save 

1011 and close any opened file handles. 

1012 

1013 Parameters 

1014 ---------- 

1015 path : str or typing.BinaryIO 

1016 Path to xlsx, xlsm, or ods file. 

1017 engine : {'openpyxl', 'xlsxwriter', 'odf'}, optional 

1018 Engine to use for writing, given as the engine string (not the 

1019 package name). Which engines are accepted depends on the file 

1020 extension: 

1021 

1022 * ``.xlsx``: ``"openpyxl"`` or ``"xlsxwriter"``. 

1023 * ``.xlsm``: ``"openpyxl"``. 

1024 * ``.ods``: ``"odf"`` (provided by the ``odfpy`` package). 

1025 

1026 If None, defaults to ``io.excel.<extension>.writer``. NOTE: can only 

1027 be passed as a keyword argument. 

1028 date_format : str, default None 

1029 Format string for dates written into Excel files (e.g. 'YYYY-MM-DD'). 

1030 datetime_format : str, default None 

1031 Format string for datetime objects written into Excel files. 

1032 (e.g. 'YYYY-MM-DD HH:MM:SS'). 

1033 mode : {'w', 'a'}, default 'w' 

1034 File mode to use (write or append). Append does not work with fsspec 

1035 URLs. 

1036 

1037 .. warning:: 

1038 

1039 In append mode the existing workbook is read and completely 

1040 rewritten by the engine, so any content the engine cannot 

1041 represent (e.g. VBA macros in ``.xlsm`` files, charts or images) 

1042 is silently lost and the workbook may be otherwise altered. 

1043 storage_options : dict, optional 

1044 Extra options that make sense for a particular storage connection, e.g. 

1045 host, port, username, password, etc. For HTTP(S) URLs the key-value pairs 

1046 are forwarded to ``urllib.request.Request`` as header options. For other 

1047 URLs (e.g. starting with "s3://", and "gcs://") the key-value pairs are 

1048 forwarded to ``fsspec.open``. Please see ``fsspec`` and ``urllib`` for more 

1049 details, and for more examples on storage options refer `here 

1050 <https://pandas.pydata.org/docs/user_guide/io.html? 

1051 highlight=storage_options#reading-writing-remote-files>`_. 

1052 

1053 if_sheet_exists : {'error', 'new', 'replace', 'overlay'}, default 'error' 

1054 How to behave when trying to write to a sheet that already 

1055 exists (append mode only). 

1056 

1057 * error: raise a ValueError. 

1058 * new: Create a new sheet, with a name determined by the engine. 

1059 * replace: Delete the contents of the sheet before writing to it. 

1060 * overlay: Write contents to the existing sheet without first removing, 

1061 but possibly over top of, the existing contents. 

1062 

1063 engine_kwargs : dict, optional 

1064 Keyword arguments to be passed into the engine. The bullets below 

1065 are keyed by the ``engine`` string (not the package name); for 

1066 each engine, ``engine_kwargs`` is forwarded as follows: 

1067 

1068 * ``"xlsxwriter"``: ``xlsxwriter.Workbook(file, **engine_kwargs)`` 

1069 * ``"openpyxl"`` (write mode): ``openpyxl.Workbook(**engine_kwargs)`` 

1070 * ``"openpyxl"`` (append mode): 

1071 ``openpyxl.load_workbook(file, **engine_kwargs)`` 

1072 * ``"odf"``: 

1073 ``odf.opendocument.OpenDocumentSpreadsheet(**engine_kwargs)`` 

1074 

1075 See Also 

1076 -------- 

1077 read_excel : Read an Excel sheet values (xlsx) file into DataFrame. 

1078 read_csv : Read a comma-separated values (csv) file into DataFrame. 

1079 read_fwf : Read a table of fixed-width formatted lines into DataFrame. 

1080 

1081 Notes 

1082 ----- 

1083 For compatibility with CSV writers, ExcelWriter serializes lists 

1084 and dicts to strings before writing. 

1085 

1086 Examples 

1087 -------- 

1088 Default usage: 

1089 

1090 >>> df = pd.DataFrame([["ABC", "XYZ"]], columns=["Foo", "Bar"]) # doctest: +SKIP 

1091 >>> with pd.ExcelWriter("path_to_file.xlsx") as writer: 

1092 ... df.to_excel(writer) # doctest: +SKIP 

1093 

1094 To write to separate sheets in a single file: 

1095 

1096 >>> df1 = pd.DataFrame([["AAA", "BBB"]], columns=["Spam", "Egg"]) # doctest: +SKIP 

1097 >>> df2 = pd.DataFrame([["ABC", "XYZ"]], columns=["Foo", "Bar"]) # doctest: +SKIP 

1098 >>> with pd.ExcelWriter("path_to_file.xlsx") as writer: 

1099 ... df1.to_excel(writer, sheet_name="Sheet1") # doctest: +SKIP 

1100 ... df2.to_excel(writer, sheet_name="Sheet2") # doctest: +SKIP 

1101 

1102 You can set the date format or datetime format: 

1103 

1104 >>> from datetime import date, datetime # doctest: +SKIP 

1105 >>> df = pd.DataFrame( 

1106 ... [ 

1107 ... [date(2014, 1, 31), date(1999, 9, 24)], 

1108 ... [datetime(1998, 5, 26, 23, 33, 4), datetime(2014, 2, 28, 13, 5, 13)], 

1109 ... ], 

1110 ... index=["Date", "Datetime"], 

1111 ... columns=["X", "Y"], 

1112 ... ) # doctest: +SKIP 

1113 >>> with pd.ExcelWriter( 

1114 ... "path_to_file.xlsx", 

1115 ... date_format="YYYY-MM-DD", 

1116 ... datetime_format="YYYY-MM-DD HH:MM:SS", 

1117 ... ) as writer: 

1118 ... df.to_excel(writer) # doctest: +SKIP 

1119 

1120 You can also append to an existing Excel file: 

1121 

1122 >>> with pd.ExcelWriter("path_to_file.xlsx", mode="a", engine="openpyxl") as writer: 

1123 ... df.to_excel(writer, sheet_name="Sheet3") # doctest: +SKIP 

1124 

1125 Here, the `if_sheet_exists` parameter can be set to replace a sheet if it 

1126 already exists: 

1127 

1128 >>> with pd.ExcelWriter( 

1129 ... "path_to_file.xlsx", 

1130 ... mode="a", 

1131 ... engine="openpyxl", 

1132 ... if_sheet_exists="replace", 

1133 ... ) as writer: 

1134 ... df.to_excel(writer, sheet_name="Sheet1") # doctest: +SKIP 

1135 

1136 You can also write multiple DataFrames to a single sheet. Note that the 

1137 ``if_sheet_exists`` parameter needs to be set to ``overlay``: 

1138 

1139 >>> with pd.ExcelWriter( 

1140 ... "path_to_file.xlsx", 

1141 ... mode="a", 

1142 ... engine="openpyxl", 

1143 ... if_sheet_exists="overlay", 

1144 ... ) as writer: 

1145 ... df1.to_excel(writer, sheet_name="Sheet1") 

1146 ... df2.to_excel(writer, sheet_name="Sheet1", startcol=3) # doctest: +SKIP 

1147 

1148 You can store Excel file in RAM: 

1149 

1150 >>> import io 

1151 >>> df = pd.DataFrame([["ABC", "XYZ"]], columns=["Foo", "Bar"]) 

1152 >>> buffer = io.BytesIO() 

1153 >>> with pd.ExcelWriter(buffer) as writer: 

1154 ... df.to_excel(writer) 

1155 

1156 You can pack Excel file into zip archive: 

1157 

1158 >>> import zipfile # doctest: +SKIP 

1159 >>> df = pd.DataFrame([["ABC", "XYZ"]], columns=["Foo", "Bar"]) # doctest: +SKIP 

1160 >>> with zipfile.ZipFile("path_to_file.zip", "w") as zf: 

1161 ... with zf.open("filename.xlsx", "w") as buffer: 

1162 ... with pd.ExcelWriter(buffer) as writer: 

1163 ... df.to_excel(writer) # doctest: +SKIP 

1164 

1165 You can specify additional arguments to the underlying engine: 

1166 

1167 >>> with pd.ExcelWriter( 

1168 ... "path_to_file.xlsx", 

1169 ... engine="xlsxwriter", 

1170 ... engine_kwargs={"options": {"nan_inf_to_errors": True}}, 

1171 ... ) as writer: 

1172 ... df.to_excel(writer) # doctest: +SKIP 

1173 

1174 In append mode, ``engine_kwargs`` are passed through to 

1175 openpyxl's ``load_workbook``: 

1176 

1177 >>> with pd.ExcelWriter( 

1178 ... "path_to_file.xlsx", 

1179 ... engine="openpyxl", 

1180 ... mode="a", 

1181 ... engine_kwargs={"keep_vba": True}, 

1182 ... ) as writer: 

1183 ... df.to_excel(writer, sheet_name="Sheet2") # doctest: +SKIP 

1184 """ 

1185 

1186 # Defining an ExcelWriter implementation (see abstract methods for more...) 

1187 

1188 # - Mandatory 

1189 # - ``write_cells(self, cells, sheet_name=None, startrow=0, startcol=0)`` 

1190 # --> called to write additional DataFrames to disk 

1191 # - ``_supported_extensions`` (tuple of supported extensions), used to 

1192 # check that engine supports the given extension. 

1193 # - ``_engine`` - string that gives the engine name. Necessary to 

1194 # instantiate class directly and bypass ``ExcelWriterMeta`` engine 

1195 # lookup. 

1196 # - ``save(self)`` --> called to save file to disk 

1197 # - Mostly mandatory (i.e. should at least exist) 

1198 # - book, cur_sheet, path 

1199 

1200 # - Optional: 

1201 # - ``__init__(self, path, engine=None, **kwargs)`` --> always called 

1202 # with path as first argument. 

1203 

1204 # You also need to register the class with ``register_writer()``. 

1205 # Technically, ExcelWriter implementations don't need to subclass 

1206 # ExcelWriter. 

1207 

1208 _engine: str 

1209 _supported_extensions: tuple[str, ...] 

1210 

1211 def __new__( 

1212 cls, 

1213 path: FilePath | WriteExcelBuffer | ExcelWriter, 

1214 engine: str | None = None, 

1215 date_format: str | None = None, 

1216 datetime_format: str | None = None, 

1217 mode: str = "w", 

1218 storage_options: StorageOptions | None = None, 

1219 if_sheet_exists: ExcelWriterIfSheetExists | None = None, 

1220 engine_kwargs: dict | None = None, 

1221 ) -> Self: 

1222 # only switch class if generic(ExcelWriter) 

1223 if cls is ExcelWriter: 

1224 if engine is None or (isinstance(engine, str) and engine == "auto"): 

1225 if isinstance(path, str): 

1226 ext = os.path.splitext(path)[-1][1:] 

1227 else: 

1228 ext = "xlsx" 

1229 

1230 try: 

1231 engine = config["io"]["excel"][ext]["writer"] 

1232 if engine == "auto": 

1233 engine = get_default_engine(ext, mode="writer") 

1234 except KeyError as err: 

1235 raise ValueError(f"No engine for filetype: '{ext}'") from err 

1236 

1237 # for mypy 

1238 assert engine is not None 

1239 # error: Incompatible types in assignment (expression has type 

1240 # "type[ExcelWriter[Any]]", variable has type "type[Self]") 

1241 cls = get_writer(engine) # type: ignore[assignment] 

1242 

1243 return object.__new__(cls) 

1244 

1245 @property 

1246 def supported_extensions(self) -> tuple[str, ...]: 

1247 """Extensions that writer engine supports.""" 

1248 return self._supported_extensions 

1249 

1250 @property 

1251 def engine(self) -> str: 

1252 """Name of engine.""" 

1253 return self._engine 

1254 

1255 @property 

1256 def sheets(self) -> dict[str, Any]: 

1257 """Mapping of sheet names to sheet objects.""" 

1258 raise NotImplementedError 

1259 

1260 @property 

1261 def book(self) -> _WorkbookT: 

1262 """ 

1263 Book instance. Class type will depend on the engine used. 

1264 

1265 This attribute can be used to access engine-specific features. 

1266 """ 

1267 raise NotImplementedError 

1268 

1269 def _write_cells( 

1270 self, 

1271 cells, 

1272 sheet_name: str | None = None, 

1273 startrow: int = 0, 

1274 startcol: int = 0, 

1275 freeze_panes: tuple[int, int] | None = None, 

1276 autofilter_range: str | None = None, 

1277 ) -> None: 

1278 """ 

1279 Write given formatted cells into Excel an excel sheet 

1280 

1281 Parameters 

1282 ---------- 

1283 cells : generator 

1284 cell of formatted data to save to Excel sheet 

1285 sheet_name : str, default None 

1286 Name of Excel sheet, if None, then use self.cur_sheet 

1287 startrow : upper left cell row to dump data frame 

1288 startcol : upper left cell column to dump data frame 

1289 freeze_panes: int tuple of length 2 

1290 contains the bottom-most row and right-most column to freeze 

1291 autofilter_range: str, default None 

1292 column ranges to add automatic filters to, for example "A1:D5" 

1293 """ 

1294 raise NotImplementedError 

1295 

1296 def _save(self) -> None: 

1297 """ 

1298 Save workbook to disk. 

1299 """ 

1300 raise NotImplementedError 

1301 

1302 def __init__( 

1303 self, 

1304 path: FilePath | WriteExcelBuffer | ExcelWriter, 

1305 engine: str | None = None, 

1306 date_format: str | None = None, 

1307 datetime_format: str | None = None, 

1308 mode: str = "w", 

1309 storage_options: StorageOptions | None = None, 

1310 if_sheet_exists: ExcelWriterIfSheetExists | None = None, 

1311 engine_kwargs: dict[str, Any] | None = None, 

1312 ) -> None: 

1313 # validate that this engine can handle the extension 

1314 if isinstance(path, str): 

1315 ext = os.path.splitext(path)[-1] 

1316 self.check_extension(ext) 

1317 

1318 # use mode to open the file 

1319 if "b" not in mode: 

1320 mode += "b" 

1321 # use "a" for the user to append data to excel but internally use "r+" to let 

1322 # the excel backend first read the existing file and then write any data to it 

1323 mode = mode.replace("a", "r+") 

1324 

1325 if if_sheet_exists not in (None, "error", "new", "replace", "overlay"): 

1326 raise ValueError( 

1327 f"'{if_sheet_exists}' is not valid for if_sheet_exists. " 

1328 "Valid options are 'error', 'new', 'replace' and 'overlay'." 

1329 ) 

1330 if if_sheet_exists and "r+" not in mode: 

1331 raise ValueError("if_sheet_exists is only valid in append mode (mode='a')") 

1332 if if_sheet_exists is None: 

1333 if_sheet_exists = "error" 

1334 self._if_sheet_exists = if_sheet_exists 

1335 

1336 # cast ExcelWriter to avoid adding 'if self._handles is not None' 

1337 self._handles = IOHandles( 

1338 cast("IO[bytes]", path), compression={"compression": None} 

1339 ) 

1340 if not isinstance(path, ExcelWriter): 

1341 self._handles = get_handle( 

1342 path, mode, storage_options=storage_options, is_text=False 

1343 ) 

1344 self._cur_sheet = None 

1345 

1346 if date_format is None: 

1347 self._date_format = "YYYY-MM-DD" 

1348 else: 

1349 self._date_format = date_format 

1350 if datetime_format is None: 

1351 self._datetime_format = "YYYY-MM-DD HH:MM:SS" 

1352 else: 

1353 self._datetime_format = datetime_format 

1354 

1355 self._mode = mode 

1356 

1357 @property 

1358 def date_format(self) -> str: 

1359 """ 

1360 Format string for dates written into Excel files (e.g. 'YYYY-MM-DD'). 

1361 """ 

1362 return self._date_format 

1363 

1364 @property 

1365 def datetime_format(self) -> str: 

1366 """ 

1367 Format string for dates written into Excel files (e.g. 'YYYY-MM-DD'). 

1368 """ 

1369 return self._datetime_format 

1370 

1371 @property 

1372 def if_sheet_exists(self) -> str: 

1373 """ 

1374 How to behave when writing to a sheet that already exists in append mode. 

1375 """ 

1376 return self._if_sheet_exists 

1377 

1378 def __fspath__(self) -> str: 

1379 return getattr(self._handles.handle, "name", "") 

1380 

1381 def _get_sheet_name(self, sheet_name: str | None) -> str: 

1382 if sheet_name is None: 

1383 sheet_name = self._cur_sheet 

1384 if sheet_name is None: # pragma: no cover 

1385 raise ValueError("Must pass explicit sheet_name or set _cur_sheet property") 

1386 return sheet_name 

1387 

1388 def _value_with_fmt( 

1389 self, val 

1390 ) -> tuple[ 

1391 int | float | bool | str | datetime.datetime | datetime.date, str | None 

1392 ]: 

1393 """ 

1394 Convert numpy types to Python types for the Excel writers. 

1395 

1396 Parameters 

1397 ---------- 

1398 val : object 

1399 Value to be written into cells 

1400 

1401 Returns 

1402 ------- 

1403 Tuple with the first element being the converted value and the second 

1404 being an optional format 

1405 """ 

1406 fmt = None 

1407 

1408 if is_integer(val): 

1409 val = int(val) 

1410 elif is_float(val): 

1411 val = float(val) 

1412 elif is_bool(val): 

1413 val = bool(val) 

1414 elif is_decimal(val): 

1415 val = Decimal(val) 

1416 elif isinstance(val, datetime.datetime): 

1417 fmt = self._datetime_format 

1418 elif isinstance(val, datetime.date): 

1419 fmt = self._date_format 

1420 elif isinstance(val, datetime.timedelta): 

1421 val = val.total_seconds() / 86400 

1422 fmt = "0" 

1423 else: 

1424 val = str(val) 

1425 # GH#56954 

1426 # Excel's limitation on cell contents is 32767 characters 

1427 # xref https://support.microsoft.com/en-au/office/excel-specifications-and-limits-1672b34d-7043-467e-8e27-269d656771c3 

1428 if len(val) > 32767: 

1429 warnings.warn( 

1430 "Cell contents too long, truncated to 32767 characters", 

1431 UserWarning, 

1432 stacklevel=find_stack_level(), 

1433 ) 

1434 return val, fmt 

1435 

1436 @classmethod 

1437 def check_extension(cls, ext: str) -> Literal[True]: 

1438 """ 

1439 checks that path's extension against the Writer's supported 

1440 extensions. If it isn't supported, raises UnsupportedFiletypeError. 

1441 """ 

1442 if ext.startswith("."): 

1443 ext = ext[1:] 

1444 if not any(ext in extension for extension in cls._supported_extensions): 

1445 raise ValueError(f"Invalid extension for engine '{cls.engine}': '{ext}'") 

1446 return True 

1447 

1448 # Allow use as a contextmanager 

1449 def __enter__(self) -> Self: 

1450 return self 

1451 

1452 def __exit__( 

1453 self, 

1454 exc_type: type[BaseException] | None, 

1455 exc_value: BaseException | None, 

1456 traceback: TracebackType | None, 

1457 ) -> None: 

1458 self.close() 

1459 

1460 def close(self) -> None: 

1461 """synonym for save, to make it more file-like""" 

1462 self._save() 

1463 self._handles.close() 

1464 

1465 

1466XLS_SIGNATURES = ( 

1467 b"\x09\x00\x04\x00\x07\x00\x10\x00", # BIFF2 

1468 b"\x09\x02\x06\x00\x00\x00\x10\x00", # BIFF3 

1469 b"\x09\x04\x06\x00\x00\x00\x10\x00", # BIFF4 

1470 b"\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1", # Compound File Binary 

1471) 

1472ZIP_SIGNATURE = b"PK\x03\x04" 

1473PEEK_SIZE = max(map(len, (*XLS_SIGNATURES, ZIP_SIGNATURE))) 

1474 

1475 

1476def inspect_excel_format( 

1477 content_or_path: FilePath | ReadBuffer[bytes], 

1478 storage_options: StorageOptions | None = None, 

1479) -> str | None: 

1480 """ 

1481 Inspect the path or content of an excel file and get its format. 

1482 

1483 Adopted from xlrd: https://github.com/python-excel/xlrd. 

1484 

1485 Parameters 

1486 ---------- 

1487 content_or_path : str or file-like object 

1488 Path to file or content of file to inspect. May be a URL. 

1489 storage_options : dict, optional 

1490 Extra options that make sense for a particular storage connection, e.g. 

1491 host, port, username, password, etc. For HTTP(S) URLs the key-value pairs 

1492 are forwarded to ``urllib.request.Request`` as header options. For other 

1493 URLs (e.g. starting with "s3://", and "gcs://") the key-value pairs are 

1494 forwarded to ``fsspec.open``. Please see ``fsspec`` and ``urllib`` for more 

1495 details, and for more examples on storage options refer `here 

1496 <https://pandas.pydata.org/docs/user_guide/io.html? 

1497 highlight=storage_options#reading-writing-remote-files>`_. 

1498 

1499 Returns 

1500 ------- 

1501 str or None 

1502 Format of file if it can be determined. 

1503 

1504 Raises 

1505 ------ 

1506 ValueError 

1507 If resulting stream is empty. 

1508 BadZipFile 

1509 If resulting stream does not have an XLS signature and is not a valid zipfile. 

1510 """ 

1511 with get_handle( 

1512 content_or_path, "rb", storage_options=storage_options, is_text=False 

1513 ) as handle: 

1514 stream = handle.handle 

1515 stream.seek(0) 

1516 buf = stream.read(PEEK_SIZE) 

1517 if buf is None: 

1518 raise ValueError("stream is empty") 

1519 assert isinstance(buf, bytes) 

1520 peek = buf 

1521 stream.seek(0) 

1522 

1523 if any(peek.startswith(sig) for sig in XLS_SIGNATURES): 

1524 return "xls" 

1525 elif not peek.startswith(ZIP_SIGNATURE): 

1526 return None 

1527 

1528 with zipfile.ZipFile(stream) as zf: 

1529 # Workaround for some third party files that use forward slashes and 

1530 # lower case names. 

1531 component_names = { 

1532 name.replace("\\", "/").lower() for name in zf.namelist() 

1533 } 

1534 

1535 if "xl/workbook.xml" in component_names: 

1536 return "xlsx" 

1537 if "xl/workbook.bin" in component_names: 

1538 return "xlsb" 

1539 if "content.xml" in component_names: 

1540 return "ods" 

1541 return "zip" 

1542 

1543 

1544# GH#46352 - engine name -> (module, class) of the workbook objects it reads 

1545_WORKBOOK_CLASSES = { 

1546 "xlrd": ("xlrd", "Book"), 

1547 "openpyxl": ("openpyxl", "Workbook"), 

1548 "odf": ("odf.opendocument", "OpenDocument"), 

1549 "pyxlsb": ("pyxlsb", "Workbook"), 

1550 "calamine": ("python_calamine", "CalamineWorkbook"), 

1551} 

1552 

1553 

1554def _infer_engine_from_workbook(obj) -> str | None: 

1555 """ 

1556 Return the engine whose library created the workbook ``obj``, if any. 

1557 """ 

1558 for engine, (module_name, class_name) in _WORKBOOK_CLASSES.items(): 

1559 # GH#56692 - avoid importing optional dependencies; such a workbook 

1560 # can only exist if the caller already imported its module. 

1561 module = sys.modules.get(module_name) 

1562 if module is not None and isinstance(obj, getattr(module, class_name)): 

1563 return engine 

1564 return None 

1565 

1566 

1567@set_module("pandas") 

1568class ExcelFile: 

1569 """ 

1570 Class for parsing tabular Excel sheets into DataFrame objects. 

1571 

1572 See read_excel for more documentation. 

1573 

1574 Parameters 

1575 ---------- 

1576 path_or_buffer : str, bytes, pathlib.Path, 

1577 A file-like object or a workbook object from a supported engine. 

1578 If a string or path object, expected to be a path to a 

1579 .xls, .xlsx, .xlsb, .xlsm, .odf, .ods, or .odt file. 

1580 engine : str, default None 

1581 Supported engines: ``xlrd``, ``openpyxl``, ``odf``, ``pyxlsb``, ``calamine`` 

1582 Engine compatibility : 

1583 

1584 - ``xlrd`` supports old-style Excel files (.xls). 

1585 - ``openpyxl`` supports newer Excel file formats. 

1586 - ``odf`` supports OpenDocument file formats (.odf, .ods, .odt). 

1587 - ``pyxlsb`` supports Binary Excel files. 

1588 - ``calamine`` supports Excel (.xls, .xlsx, .xlsm, .xlsb) 

1589 and OpenDocument (.ods) file formats. 

1590 

1591 The engine `xlrd <https://xlrd.readthedocs.io/en/latest/>`_ 

1592 now only supports old-style ``.xls`` files. 

1593 When ``engine=None``, the following logic will be 

1594 used to determine the engine: 

1595 

1596 - If ``path_or_buffer`` is a workbook object, the engine that created it 

1597 will be used. 

1598 - Otherwise if ``path_or_buffer`` is an OpenDocument format (.odf, .ods, .odt), 

1599 then `odf <https://pypi.org/project/odfpy/>`_ will be used. 

1600 - Otherwise if ``path_or_buffer`` is an xls format, 

1601 ``xlrd`` will be used. 

1602 - Otherwise if ``path_or_buffer`` is in xlsb format, 

1603 `pyxlsb <https://pypi.org/project/pyxlsb/>`_ will be used. 

1604 - Otherwise if `openpyxl <https://pypi.org/project/openpyxl/>`_ is installed, 

1605 then ``openpyxl`` will be used. 

1606 - Otherwise if ``xlrd >= 2.0`` is installed, a ``ValueError`` will be raised. 

1607 

1608 .. deprecated:: 3.1.0 

1609 For xlsx/xlsm files the default engine will change from 

1610 ``openpyxl`` to ``calamine`` in a future version. Pass ``engine`` 

1611 explicitly, or set the ``io.excel.xlsx.reader`` option (xlsm 

1612 files are format-detected as xlsx), to keep the current behavior. 

1613 

1614 .. warning:: 

1615 

1616 Please do not report issues when using ``xlrd`` to read ``.xlsx`` files. 

1617 This is not supported, switch to using ``openpyxl`` instead. 

1618 storage_options : dict, optional 

1619 Extra options that make sense for a particular storage connection, e.g. 

1620 host, port, username, password, etc. For HTTP(S) URLs the key-value pairs 

1621 are forwarded to ``urllib.request.Request`` as header options. For other 

1622 URLs (e.g. starting with "s3://", and "gcs://") the key-value pairs are 

1623 forwarded to ``fsspec.open``. Please see ``fsspec`` and ``urllib`` for more 

1624 details, and for more examples on storage options refer `here 

1625 <https://pandas.pydata.org/docs/user_guide/io.html? 

1626 highlight=storage_options#reading-writing-remote-files>`_. 

1627 engine_kwargs : dict, optional 

1628 Arbitrary keyword arguments passed to excel engine. 

1629 

1630 See Also 

1631 -------- 

1632 DataFrame.to_excel : Write DataFrame to an Excel file. 

1633 DataFrame.to_csv : Write DataFrame to a comma-separated values (csv) file. 

1634 read_csv : Read a comma-separated values (csv) file into DataFrame. 

1635 read_fwf : Read a table of fixed-width formatted lines into DataFrame. 

1636 

1637 Examples 

1638 -------- 

1639 >>> file = pd.ExcelFile("myfile.xlsx") # doctest: +SKIP 

1640 >>> with pd.ExcelFile("myfile.xls") as xls: # doctest: +SKIP 

1641 ... df1 = pd.read_excel(xls, "Sheet1") # doctest: +SKIP 

1642 """ 

1643 

1644 from pandas.io.excel._calamine import CalamineReader 

1645 from pandas.io.excel._odfreader import ODFReader 

1646 from pandas.io.excel._openpyxl import OpenpyxlReader 

1647 from pandas.io.excel._pyxlsb import PyxlsbReader 

1648 from pandas.io.excel._xlrd import XlrdReader 

1649 

1650 _engines: Mapping[str, Any] = { 

1651 "xlrd": XlrdReader, 

1652 "openpyxl": OpenpyxlReader, 

1653 "odf": ODFReader, 

1654 "pyxlsb": PyxlsbReader, 

1655 "calamine": CalamineReader, 

1656 } 

1657 

1658 def __init__( 

1659 self, 

1660 path_or_buffer, 

1661 engine: str | None = None, 

1662 storage_options: StorageOptions | None = None, 

1663 engine_kwargs: dict | None = None, 

1664 ) -> None: 

1665 if engine_kwargs is None: 

1666 engine_kwargs = {} 

1667 

1668 if engine is not None and engine not in self._engines: 

1669 raise ValueError(f"Unknown engine: {engine}") 

1670 

1671 # Always a string 

1672 self._io = stringify_path(path_or_buffer) 

1673 

1674 if engine is None: 

1675 # GH#68086 - must precede inspect_excel_format, which seeks. 

1676 engine = _infer_engine_from_workbook(path_or_buffer) 

1677 

1678 if engine is None: 

1679 ext = inspect_excel_format( 

1680 content_or_path=path_or_buffer, storage_options=storage_options 

1681 ) 

1682 if ext is None: 

1683 raise ValueError( 

1684 "Excel file format cannot be determined, you must specify " 

1685 "an engine manually." 

1686 ) 

1687 

1688 engine = config["io"]["excel"][ext]["reader"] 

1689 if engine == "auto": 

1690 engine = get_default_engine(ext, mode="reader") 

1691 # GH#56542 - the calamine engine will become the default 

1692 # for xlsx/xlsm. Warn while calamine is available so the 

1693 # switch is actionable; an explicit engine or the 

1694 # io.excel.xlsx.reader option silences this. xlsm files 

1695 # are format-sniffed as xlsx, so ext is never "xlsm" here. 

1696 if ( 

1697 ext == "xlsx" 

1698 and engine == "openpyxl" 

1699 and import_optional_dependency("python_calamine", errors="ignore") 

1700 is not None 

1701 ): 

1702 warnings.warn( 

1703 "The default engine for reading 'xlsx' files " 

1704 "will change from 'openpyxl' to 'calamine' in a " 

1705 "future version. Pass engine='openpyxl' (or set " 

1706 "the 'io.excel.xlsx.reader' option) to keep the " 

1707 "current engine and silence this warning.", 

1708 Pandas4Warning, 

1709 stacklevel=find_stack_level(), 

1710 ) 

1711 

1712 assert engine is not None 

1713 self.engine = engine 

1714 self.storage_options = storage_options 

1715 

1716 self._reader = self._engines[engine]( 

1717 self._io, 

1718 storage_options=storage_options, 

1719 engine_kwargs=engine_kwargs, 

1720 ) 

1721 

1722 def __fspath__(self): 

1723 return self._io 

1724 

1725 def parse( 

1726 self, 

1727 sheet_name: str | int | list[int] | list[str] | None = 0, 

1728 header: int | Sequence[int] | None = 0, 

1729 names: SequenceNotStr[Hashable] | range | None = None, 

1730 index_col: int | Sequence[int] | None = None, 

1731 usecols=None, 

1732 converters=None, 

1733 true_values: Iterable[Hashable] | None = None, 

1734 false_values: Iterable[Hashable] | None = None, 

1735 skiprows: Sequence[int] | int | Callable[[int], object] | None = None, 

1736 nrows: int | None = None, 

1737 na_values=None, 

1738 parse_dates: list | dict | bool = False, 

1739 date_format: str | dict[Hashable, str] | None = None, 

1740 thousands: str | None = None, 

1741 comment: str | None = None, 

1742 skipfooter: int = 0, 

1743 dtype_backend: DtypeBackend | lib.NoDefault = lib.no_default, 

1744 **kwds, 

1745 ) -> DataFrame | dict[str, DataFrame] | dict[int, DataFrame]: 

1746 """ 

1747 Parse specified sheet(s) into a DataFrame. 

1748 

1749 .. deprecated:: 3.1.0 

1750 Use :func:`pd.read_excel` instead. 

1751 

1752 Equivalent to read_excel(ExcelFile, ...) See the read_excel 

1753 docstring for more info on accepted parameters. 

1754 

1755 Parameters 

1756 ---------- 

1757 sheet_name : str, int, list, or None, default 0 

1758 Strings are used for sheet names. Integers are used in zero-indexed 

1759 sheet positions (chart sheets do not count as a sheet position). 

1760 Lists of strings/integers are used to request multiple sheets. 

1761 When ``None``, will return a dictionary containing DataFrames for 

1762 each sheet. 

1763 header : int, list of int, default 0 

1764 Row (0-indexed) to use for the column labels of the parsed 

1765 DataFrame. If a list of integers is passed those row positions will 

1766 be combined into a ``MultiIndex``. Use None if there is no header. 

1767 names : array-like, default None 

1768 List of column names to use. If file contains no header row, 

1769 then you should explicitly pass header=None. 

1770 index_col : int, str, list of int, default None 

1771 Column (0-indexed) to use as the row labels of the DataFrame. 

1772 Pass None if there is no such column. If a list is passed, 

1773 those columns will be combined into a ``MultiIndex``. If a 

1774 subset of data is selected with ``usecols``, index_col 

1775 is based on the subset. 

1776 

1777 Missing values will be forward filled to allow roundtripping with 

1778 ``to_excel`` for ``merged_cells=True``. To avoid forward filling the 

1779 missing values use ``set_index`` after reading the data instead of 

1780 ``index_col``. 

1781 

1782 When ``header`` is a list of two or more rows (``MultiIndex`` 

1783 columns), the row after the last header row is expected to hold 

1784 the index name(s) — blank if the index is unnamed — as written by 

1785 ``to_excel``. A file lacking this separator row can lose its first 

1786 data row to the index name(s); see :ref:`the user guide 

1787 <io.excel.reading_multiindex>` for details. 

1788 usecols : str, list-like, or callable, default None 

1789 * If None, then parse all columns. 

1790 * If str, then indicates comma separated list of Excel column letters 

1791 and column ranges (e.g. "A:E" or "A,C,E:F"). Ranges are inclusive of 

1792 both sides. 

1793 * If list of int, then indicates list of column numbers to be parsed 

1794 (0-indexed). 

1795 * If list of string, then indicates list of column names to be parsed. 

1796 * If callable, then evaluate each column name against it and parse the 

1797 column if the callable returns ``True``. 

1798 

1799 Returns a subset of the columns according to behavior above. 

1800 Element order is ignored, so ``usecols=[0, 1]`` is the same as 

1801 ``[1, 0]``. To instantiate a :class:`~pandas.DataFrame` with element 

1802 order preserved use 

1803 ``pd.read_excel(path, usecols=['foo', 'bar'])[['foo', 'bar']]`` for 

1804 columns in ``['foo', 'bar']`` order or 

1805 ``pd.read_excel(path, usecols=['foo', 'bar'])[['bar', 'foo']]`` for 

1806 ``['bar', 'foo']`` order. 

1807 converters : dict, default None 

1808 Dict of functions for converting values in certain columns. Keys can 

1809 either be integers or column labels, values are functions that take one 

1810 input argument, the Excel cell content, and return the transformed 

1811 content. 

1812 true_values : list, default None 

1813 Values to consider as True. 

1814 false_values : list, default None 

1815 Values to consider as False. 

1816 skiprows : list-like, int, or callable, optional 

1817 Line numbers to skip (0-indexed) or number of lines to skip (int) at the 

1818 start of the file. If callable, the callable function will be evaluated 

1819 against the row indices, returning True if the row should be skipped and 

1820 False otherwise. An example of a valid callable argument would be ``lambda 

1821 x: x in [0, 2]``. 

1822 nrows : int, default None 

1823 Number of rows to parse. 

1824 na_values : scalar, str, list-like, or dict, default None 

1825 Additional strings to recognize as NA/NaN. If dict passed, specific 

1826 per-column NA values. 

1827 parse_dates : bool, list-like, or dict, default False 

1828 The behavior is as follows: 

1829 

1830 * ``bool``. If True -> try parsing the index. 

1831 * ``list`` of int or names. e.g. If [1, 2, 3] -> try parsing columns 1, 2, 3 

1832 each as a separate date column. 

1833 * ``list`` of lists. e.g. If [[1, 3]] -> combine columns 1 and 3 and 

1834 parse as a single date column. 

1835 * ``dict``, e.g. {'foo' : [1, 3]} -> parse columns 1, 3 as date and call 

1836 result 'foo' 

1837 

1838 If a column or index contains an unparsable date, the entire column or 

1839 index will be returned unaltered as an object data type. If you 

1840 don`t want to parse some cells as date just change their type 

1841 in Excel to "Text".For non-standard datetime parsing, use 

1842 ``pd.to_datetime`` after ``pd.read_excel``. 

1843 

1844 Note: A fast-path exists for iso8601-formatted dates. 

1845 date_format : str or dict of column -> format, default ``None`` 

1846 If used in conjunction with ``parse_dates``, will parse dates 

1847 according to this format. For anything more complex, 

1848 please read in as ``object`` and then apply :func:`to_datetime` as-needed. 

1849 thousands : str, default None 

1850 Thousands separator for parsing string columns to numeric. Note that 

1851 this parameter is only necessary for columns stored as TEXT in Excel, 

1852 any numeric columns will automatically be parsed, regardless of display 

1853 format. 

1854 comment : str, default None 

1855 Comments out remainder of line. Pass a character or characters to this 

1856 argument to indicate comments in the input file. Any data between the 

1857 comment string and the end of the current line is ignored. 

1858 skipfooter : int, default 0 

1859 Rows at the end to skip (0-indexed). 

1860 dtype_backend : {'numpy_nullable', 'pyarrow'} 

1861 Back-end data type applied to the resultant :class:`DataFrame` 

1862 (still experimental). If not specified, the default behavior 

1863 is to not use nullable data types. If specified, the behavior 

1864 is as follows: 

1865 

1866 * ``"numpy_nullable"``: returns nullable-dtype-backed :class:`DataFrame` 

1867 * ``"pyarrow"``: returns pyarrow-backed nullable 

1868 :class:`ArrowDtype` :class:`DataFrame` 

1869 

1870 .. versionadded:: 2.0 

1871 **kwds : dict, optional 

1872 Arbitrary keyword arguments passed to excel engine. 

1873 

1874 Returns 

1875 ------- 

1876 DataFrame or dict of DataFrames 

1877 DataFrame from the passed in Excel file. 

1878 

1879 See Also 

1880 -------- 

1881 read_excel : Read an Excel sheet values (xlsx) file into DataFrame. 

1882 read_csv : Read a comma-separated values (csv) file into DataFrame. 

1883 read_fwf : Read a table of fixed-width formatted lines into DataFrame. 

1884 

1885 Examples 

1886 -------- 

1887 >>> df = pd.DataFrame([[1, 2, 3], [4, 5, 6]], columns=["A", "B", "C"]) 

1888 >>> df.to_excel("myfile.xlsx") # doctest: +SKIP 

1889 >>> file = pd.ExcelFile("myfile.xlsx") # doctest: +SKIP 

1890 >>> file.parse() # doctest: +SKIP 

1891 """ 

1892 warnings.warn( 

1893 # GH#58247 

1894 f"{type(self).__name__}.parse is deprecated. Use pd.read_excel instead.", 

1895 Pandas4Warning, 

1896 stacklevel=find_stack_level(), 

1897 ) 

1898 return self._reader.parse( 

1899 sheet_name=sheet_name, 

1900 header=header, 

1901 names=names, 

1902 index_col=index_col, 

1903 usecols=usecols, 

1904 converters=converters, 

1905 true_values=true_values, 

1906 false_values=false_values, 

1907 skiprows=skiprows, 

1908 nrows=nrows, 

1909 na_values=na_values, 

1910 parse_dates=parse_dates, 

1911 date_format=date_format, 

1912 thousands=thousands, 

1913 comment=comment, 

1914 skipfooter=skipfooter, 

1915 dtype_backend=dtype_backend, 

1916 **kwds, 

1917 ) 

1918 

1919 @property 

1920 def book(self): 

1921 """ 

1922 Gets the Excel workbook. 

1923 

1924 Workbook is the top-level container for all document information. 

1925 

1926 Returns 

1927 ------- 

1928 Excel Workbook 

1929 The workbook object of the type defined by the engine being used. 

1930 

1931 See Also 

1932 -------- 

1933 read_excel : Read an Excel file into a pandas DataFrame. 

1934 

1935 Examples 

1936 -------- 

1937 >>> file = pd.ExcelFile("myfile.xlsx") # doctest: +SKIP 

1938 >>> file.book # doctest: +SKIP 

1939 <openpyxl.workbook.workbook.Workbook object at 0x11eb5ad70> 

1940 >>> file.book.path # doctest: +SKIP 

1941 '/xl/workbook.xml' 

1942 >>> file.book.active # doctest: +SKIP 

1943 <openpyxl.worksheet._read_only.ReadOnlyWorksheet object at 0x11eb5b370> 

1944 >>> file.book.sheetnames # doctest: +SKIP 

1945 ['Sheet1', 'Sheet2'] 

1946 """ 

1947 return self._reader.book 

1948 

1949 @property 

1950 def sheet_names(self): 

1951 """ 

1952 Names of the sheets in the document. 

1953 

1954 This is particularly useful for loading a specific sheet into a DataFrame when 

1955 you do not know the sheet names beforehand. 

1956 

1957 Returns 

1958 ------- 

1959 list of str 

1960 List of sheet names in the document. 

1961 

1962 See Also 

1963 -------- 

1964 ExcelFile.parse : Parse a sheet into a DataFrame. 

1965 read_excel : Read an Excel file into a pandas DataFrame. If you know the sheet 

1966 names, it may be easier to specify them directly to read_excel. 

1967 

1968 Examples 

1969 -------- 

1970 >>> file = pd.ExcelFile("myfile.xlsx") # doctest: +SKIP 

1971 >>> file.sheet_names # doctest: +SKIP 

1972 ["Sheet1", "Sheet2"] 

1973 """ 

1974 return self._reader.sheet_names 

1975 

1976 def close(self) -> None: 

1977 """close io if necessary""" 

1978 self._reader.close() 

1979 

1980 def __enter__(self) -> Self: 

1981 return self 

1982 

1983 def __exit__( 

1984 self, 

1985 exc_type: type[BaseException] | None, 

1986 exc_value: BaseException | None, 

1987 traceback: TracebackType | None, 

1988 ) -> None: 

1989 self.close()