Skip to content

Commit 991fc27

Browse files
authored
REF: Use more context managers to close files (#45579)
* Use more context managers * Add more context managers * Context closing for openpyxl workbooks * Context close xlrd objects * Fix method name * more closing * Use mode
1 parent 642ef16 commit 991fc27

File tree

10 files changed

+96
-88
lines changed

10 files changed

+96
-88
lines changed

doc/source/user_guide/io.rst

+6-9
Original file line numberDiff line numberDiff line change
@@ -839,9 +839,8 @@ The simplest case is to just pass in ``parse_dates=True``:
839839
.. ipython:: python
840840
:suppress:
841841
842-
f = open("foo.csv", "w")
843-
f.write("date,A,B,C\n20090101,a,1,2\n20090102,b,3,4\n20090103,c,4,5")
844-
f.close()
842+
with open("foo.csv", mode="w") as f:
843+
f.write("date,A,B,C\n20090101,a,1,2\n20090102,b,3,4\n20090103,c,4,5")
845844
846845
.. ipython:: python
847846
@@ -1452,16 +1451,15 @@ a different usage of the ``delimiter`` parameter:
14521451
.. ipython:: python
14531452
:suppress:
14541453
1455-
f = open("bar.csv", "w")
14561454
data1 = (
14571455
"id8141 360.242940 149.910199 11950.7\n"
14581456
"id1594 444.953632 166.985655 11788.4\n"
14591457
"id1849 364.136849 183.628767 11806.2\n"
14601458
"id1230 413.836124 184.375703 11916.8\n"
14611459
"id1948 502.953953 173.237159 12468.3"
14621460
)
1463-
f.write(data1)
1464-
f.close()
1461+
with open("bar.csv", "w") as f:
1462+
f.write(data1)
14651463
14661464
Consider a typical fixed-width data file:
14671465

@@ -1604,9 +1602,8 @@ of multi-columns indices.
16041602
:suppress:
16051603
16061604
data = ",a,a,a,b,c,c\n,q,r,s,t,u,v\none,1,2,3,4,5,6\ntwo,7,8,9,10,11,12"
1607-
fh = open("mi2.csv", "w")
1608-
fh.write(data)
1609-
fh.close()
1605+
with open("mi2.csv", "w") as fh:
1606+
fh.write(data)
16101607
16111608
.. ipython:: python
16121609

pandas/io/excel/_base.py

+16-10
Original file line numberDiff line numberDiff line change
@@ -535,11 +535,16 @@ def load_workbook(self, filepath_or_buffer):
535535
pass
536536

537537
def close(self) -> None:
538-
if hasattr(self, "book") and hasattr(self.book, "close"):
539-
# pyxlsb: opens a TemporaryFile
540-
# openpyxl: https://stackoverflow.com/questions/31416842/
541-
# openpyxl-does-not-close-excel-workbook-in-read-only-mode
542-
self.book.close()
538+
if hasattr(self, "book"):
539+
if hasattr(self.book, "close"):
540+
# pyxlsb: opens a TemporaryFile
541+
# openpyxl: https://stackoverflow.com/questions/31416842/
542+
# openpyxl-does-not-close-excel-workbook-in-read-only-mode
543+
self.book.close()
544+
elif hasattr(self.book, "release_resources"):
545+
# xlrd
546+
# https://github.com/python-excel/xlrd/blob/2.0.1/xlrd/book.py#L548
547+
self.book.release_resources()
543548
self.handles.close()
544549

545550
@property
@@ -1266,11 +1271,12 @@ def inspect_excel_format(
12661271
elif not peek.startswith(ZIP_SIGNATURE):
12671272
return None
12681273

1269-
zf = zipfile.ZipFile(stream)
1270-
1271-
# Workaround for some third party files that use forward slashes and
1272-
# lower case names.
1273-
component_names = [name.replace("\\", "/").lower() for name in zf.namelist()]
1274+
with zipfile.ZipFile(stream) as zf:
1275+
# Workaround for some third party files that use forward slashes and
1276+
# lower case names.
1277+
component_names = [
1278+
name.replace("\\", "/").lower() for name in zf.namelist()
1279+
]
12741280

12751281
if "xl/workbook.xml" in component_names:
12761282
return "xlsx"

pandas/tests/io/excel/test_openpyxl.py

+30-26
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import contextlib
12
from pathlib import Path
23
import re
34

@@ -159,12 +160,12 @@ def test_write_append_mode(ext, mode, expected):
159160
with ExcelWriter(f, engine="openpyxl", mode=mode) as writer:
160161
df.to_excel(writer, sheet_name="baz", index=False)
161162

162-
wb2 = openpyxl.load_workbook(f)
163-
result = [sheet.title for sheet in wb2.worksheets]
164-
assert result == expected
163+
with contextlib.closing(openpyxl.load_workbook(f)) as wb2:
164+
result = [sheet.title for sheet in wb2.worksheets]
165+
assert result == expected
165166

166-
for index, cell_value in enumerate(expected):
167-
assert wb2.worksheets[index]["A1"].value == cell_value
167+
for index, cell_value in enumerate(expected):
168+
assert wb2.worksheets[index]["A1"].value == cell_value
168169

169170

170171
@pytest.mark.parametrize(
@@ -187,15 +188,14 @@ def test_if_sheet_exists_append_modes(ext, if_sheet_exists, num_sheets, expected
187188
) as writer:
188189
df2.to_excel(writer, sheet_name="foo", index=False)
189190

190-
wb = openpyxl.load_workbook(f)
191-
assert len(wb.sheetnames) == num_sheets
192-
assert wb.sheetnames[0] == "foo"
193-
result = pd.read_excel(wb, "foo", engine="openpyxl")
194-
assert list(result["fruit"]) == expected
195-
if len(wb.sheetnames) == 2:
196-
result = pd.read_excel(wb, wb.sheetnames[1], engine="openpyxl")
197-
tm.assert_frame_equal(result, df2)
198-
wb.close()
191+
with contextlib.closing(openpyxl.load_workbook(f)) as wb:
192+
assert len(wb.sheetnames) == num_sheets
193+
assert wb.sheetnames[0] == "foo"
194+
result = pd.read_excel(wb, "foo", engine="openpyxl")
195+
assert list(result["fruit"]) == expected
196+
if len(wb.sheetnames) == 2:
197+
result = pd.read_excel(wb, wb.sheetnames[1], engine="openpyxl")
198+
tm.assert_frame_equal(result, df2)
199199

200200

201201
@pytest.mark.parametrize(
@@ -279,9 +279,10 @@ def test_to_excel_with_openpyxl_engine(ext):
279279
def test_read_workbook(datapath, ext, read_only):
280280
# GH 39528
281281
filename = datapath("io", "data", "excel", "test1" + ext)
282-
wb = openpyxl.load_workbook(filename, read_only=read_only)
283-
result = pd.read_excel(wb, engine="openpyxl")
284-
wb.close()
282+
with contextlib.closing(
283+
openpyxl.load_workbook(filename, read_only=read_only)
284+
) as wb:
285+
result = pd.read_excel(wb, engine="openpyxl")
285286
expected = pd.read_excel(filename)
286287
tm.assert_frame_equal(result, expected)
287288

@@ -313,9 +314,10 @@ def test_read_with_bad_dimension(
313314
if read_only is None:
314315
result = pd.read_excel(path, header=header)
315316
else:
316-
wb = openpyxl.load_workbook(path, read_only=read_only)
317-
result = pd.read_excel(wb, engine="openpyxl", header=header)
318-
wb.close()
317+
with contextlib.closing(
318+
openpyxl.load_workbook(path, read_only=read_only)
319+
) as wb:
320+
result = pd.read_excel(wb, engine="openpyxl", header=header)
319321
expected = DataFrame(expected_data)
320322
tm.assert_frame_equal(result, expected)
321323

@@ -349,9 +351,10 @@ def test_read_with_empty_trailing_rows(datapath, ext, read_only, request):
349351
if read_only is None:
350352
result = pd.read_excel(path)
351353
else:
352-
wb = openpyxl.load_workbook(path, read_only=read_only)
353-
result = pd.read_excel(wb, engine="openpyxl")
354-
wb.close()
354+
with contextlib.closing(
355+
openpyxl.load_workbook(path, read_only=read_only)
356+
) as wb:
357+
result = pd.read_excel(wb, engine="openpyxl")
355358
expected = DataFrame(
356359
{
357360
"Title": [np.nan, "A", 1, 2, 3],
@@ -370,8 +373,9 @@ def test_read_empty_with_blank_row(datapath, ext, read_only):
370373
if read_only is None:
371374
result = pd.read_excel(path)
372375
else:
373-
wb = openpyxl.load_workbook(path, read_only=read_only)
374-
result = pd.read_excel(wb, engine="openpyxl")
375-
wb.close()
376+
with contextlib.closing(
377+
openpyxl.load_workbook(path, read_only=read_only)
378+
) as wb:
379+
result = pd.read_excel(wb, engine="openpyxl")
376380
expected = DataFrame()
377381
tm.assert_frame_equal(result, expected)

pandas/tests/io/excel/test_style.py

+19-17
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import contextlib
2+
13
import numpy as np
24
import pytest
35

@@ -37,13 +39,13 @@ def test_styler_to_excel_unstyled(engine):
3739
df.style.to_excel(writer, sheet_name="unstyled")
3840

3941
openpyxl = pytest.importorskip("openpyxl") # test loading only with openpyxl
40-
wb = openpyxl.load_workbook(path)
42+
with contextlib.closing(openpyxl.load_workbook(path)) as wb:
4143

42-
for col1, col2 in zip(wb["dataframe"].columns, wb["unstyled"].columns):
43-
assert len(col1) == len(col2)
44-
for cell1, cell2 in zip(col1, col2):
45-
assert cell1.value == cell2.value
46-
assert_equal_cell_styles(cell1, cell2)
44+
for col1, col2 in zip(wb["dataframe"].columns, wb["unstyled"].columns):
45+
assert len(col1) == len(col2)
46+
for cell1, cell2 in zip(col1, col2):
47+
assert cell1.value == cell2.value
48+
assert_equal_cell_styles(cell1, cell2)
4749

4850

4951
shared_style_params = [
@@ -87,11 +89,11 @@ def test_styler_to_excel_basic(engine, css, attrs, expected):
8789
styler.to_excel(writer, sheet_name="styled")
8890

8991
openpyxl = pytest.importorskip("openpyxl") # test loading only with openpyxl
90-
wb = openpyxl.load_workbook(path)
92+
with contextlib.closing(openpyxl.load_workbook(path)) as wb:
9193

92-
# test unstyled data cell does not have expected styles
93-
# test styled cell has expected styles
94-
u_cell, s_cell = wb["dataframe"].cell(2, 2), wb["styled"].cell(2, 2)
94+
# test unstyled data cell does not have expected styles
95+
# test styled cell has expected styles
96+
u_cell, s_cell = wb["dataframe"].cell(2, 2), wb["styled"].cell(2, 2)
9597
for attr in attrs:
9698
u_cell, s_cell = getattr(u_cell, attr), getattr(s_cell, attr)
9799

@@ -127,12 +129,12 @@ def test_styler_to_excel_basic_indexes(engine, css, attrs, expected):
127129
styler.to_excel(writer, sheet_name="styled")
128130

129131
openpyxl = pytest.importorskip("openpyxl") # test loading only with openpyxl
130-
wb = openpyxl.load_workbook(path)
132+
with contextlib.closing(openpyxl.load_workbook(path)) as wb:
131133

132-
# test null styled index cells does not have expected styles
133-
# test styled cell has expected styles
134-
ui_cell, si_cell = wb["null_styled"].cell(2, 1), wb["styled"].cell(2, 1)
135-
uc_cell, sc_cell = wb["null_styled"].cell(1, 2), wb["styled"].cell(1, 2)
134+
# test null styled index cells does not have expected styles
135+
# test styled cell has expected styles
136+
ui_cell, si_cell = wb["null_styled"].cell(2, 1), wb["styled"].cell(2, 1)
137+
uc_cell, sc_cell = wb["null_styled"].cell(1, 2), wb["styled"].cell(1, 2)
136138
for attr in attrs:
137139
ui_cell, si_cell = getattr(ui_cell, attr), getattr(si_cell, attr)
138140
uc_cell, sc_cell = getattr(uc_cell, attr), getattr(sc_cell, attr)
@@ -163,5 +165,5 @@ def custom_converter(css):
163165
writer, sheet_name="custom"
164166
)
165167

166-
wb = openpyxl.load_workbook(path)
167-
assert wb["custom"].cell(2, 2).font.color.value == "00111222"
168+
with contextlib.closing(openpyxl.load_workbook(path)) as wb:
169+
assert wb["custom"].cell(2, 2).font.color.value == "00111222"

pandas/tests/io/excel/test_xlrd.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,14 @@ def test_read_xlrd_book(read_ext_xlrd, frame):
4545

4646
with tm.ensure_clean(read_ext_xlrd) as pth:
4747
df.to_excel(pth, sheet_name)
48-
book = xlrd.open_workbook(pth)
49-
50-
with ExcelFile(book, engine=engine) as xl:
51-
result = pd.read_excel(xl, sheet_name=sheet_name, index_col=0)
52-
tm.assert_frame_equal(df, result)
53-
54-
result = pd.read_excel(book, sheet_name=sheet_name, engine=engine, index_col=0)
48+
with xlrd.open_workbook(pth) as book:
49+
with ExcelFile(book, engine=engine) as xl:
50+
result = pd.read_excel(xl, sheet_name=sheet_name, index_col=0)
51+
tm.assert_frame_equal(df, result)
52+
53+
result = pd.read_excel(
54+
book, sheet_name=sheet_name, engine=engine, index_col=0
55+
)
5556
tm.assert_frame_equal(df, result)
5657

5758

pandas/tests/io/excel/test_xlsxwriter.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import contextlib
12
import re
23
import warnings
34

@@ -34,12 +35,12 @@ def test_column_format(ext):
3435
col_format = write_workbook.add_format({"num_format": num_format})
3536
write_worksheet.set_column("B:B", None, col_format)
3637

37-
read_workbook = openpyxl.load_workbook(path)
38-
try:
39-
read_worksheet = read_workbook["Sheet1"]
40-
except TypeError:
41-
# compat
42-
read_worksheet = read_workbook.get_sheet_by_name(name="Sheet1")
38+
with contextlib.closing(openpyxl.load_workbook(path)) as read_workbook:
39+
try:
40+
read_worksheet = read_workbook["Sheet1"]
41+
except TypeError:
42+
# compat
43+
read_worksheet = read_workbook.get_sheet_by_name(name="Sheet1")
4344

4445
# Get the number format from the cell.
4546
try:

pandas/tests/io/parser/test_c_parser_only.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -592,11 +592,9 @@ def test_file_handles_mmap(c_parser_only, csv1):
592592
parser = c_parser_only
593593

594594
with open(csv1) as f:
595-
m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
596-
parser.read_csv(m)
597-
598-
assert not m.closed
599-
m.close()
595+
with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as m:
596+
parser.read_csv(m)
597+
assert not m.closed
600598

601599

602600
def test_file_binary_mode(c_parser_only):

pandas/tests/io/parser/test_python_parser_only.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,8 @@ def test_decompression_regex_sep(python_parser_only, csv1, compression, klass):
167167
klass = getattr(module, klass)
168168

169169
with tm.ensure_clean() as path:
170-
tmp = klass(path, mode="wb")
171-
tmp.write(data)
172-
tmp.close()
170+
with klass(path, mode="wb") as tmp:
171+
tmp.write(data)
173172

174173
result = parser.read_csv(path, sep="::", compression=compression)
175174
tm.assert_frame_equal(result, expected)

pandas/tests/io/sas/test_sas7bdat.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import contextlib
12
from datetime import datetime
23
import io
34
import os
@@ -135,9 +136,8 @@ def test_encoding_options(datapath):
135136

136137
from pandas.io.sas.sas7bdat import SAS7BDATReader
137138

138-
rdr = SAS7BDATReader(fname, convert_header_text=False)
139-
df3 = rdr.read()
140-
rdr.close()
139+
with contextlib.closing(SAS7BDATReader(fname, convert_header_text=False)) as rdr:
140+
df3 = rdr.read()
141141
for x, y in zip(df1.columns, df3.columns):
142142
assert x == y.decode()
143143

pandas/tests/io/test_common.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -415,8 +415,8 @@ def test_constructor_bad_file(self, mmap_file):
415415
with pytest.raises(err, match=msg):
416416
icom._MMapWrapper(non_file)
417417

418-
target = open(mmap_file)
419-
target.close()
418+
with open(mmap_file) as target:
419+
pass
420420

421421
msg = "I/O operation on closed file"
422422
with pytest.raises(ValueError, match=msg):

0 commit comments

Comments
 (0)