Skip to content

Commit f31a4b1

Browse files
authored
BUGFIX: escape quotechar when escapechar is not None (even if quoting=csv.QUOTE_NONE) (#61514)
* initialize quotechar when self.escapechar is not None even if self.quoting is csv.QUOTENONE * add test * add to whatsnew * add space
1 parent f45cc97 commit f31a4b1

File tree

3 files changed

+22
-2
lines changed

3 files changed

+22
-2
lines changed

doc/source/whatsnew/v3.0.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,7 @@ I/O
791791
- Bug in :meth:`read_stata` where extreme value integers were incorrectly interpreted as missing for format versions 111 and prior (:issue:`58130`)
792792
- Bug in :meth:`read_stata` where the missing code for double was not recognised for format versions 105 and prior (:issue:`58149`)
793793
- Bug in :meth:`set_option` where setting the pandas option ``display.html.use_mathjax`` to ``False`` has no effect (:issue:`59884`)
794+
- Bug in :meth:`to_csv` where ``quotechar``` is not escaped when ``escapechar`` is not None (:issue:`61407`)
794795
- Bug in :meth:`to_excel` where :class:`MultiIndex` columns would be merged to a single row when ``merge_cells=False`` is passed (:issue:`60274`)
795796

796797
Period

pandas/io/formats/csvs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,9 @@ def __init__(
9090
self.index_label = self._initialize_index_label(index_label)
9191
self.errors = errors
9292
self.quoting = quoting or csvlib.QUOTE_MINIMAL
93-
self.quotechar = self._initialize_quotechar(quotechar)
9493
self.doublequote = doublequote
9594
self.escapechar = escapechar
95+
self.quotechar = self._initialize_quotechar(quotechar)
9696
self.lineterminator = lineterminator or os.linesep
9797
self.date_format = date_format
9898
self.cols = self._initialize_columns(cols)
@@ -141,7 +141,7 @@ def _get_index_label_flat(self) -> Sequence[Hashable]:
141141
return [""] if index_label is None else [index_label]
142142

143143
def _initialize_quotechar(self, quotechar: str | None) -> str | None:
144-
if self.quoting != csvlib.QUOTE_NONE:
144+
if self.quoting != csvlib.QUOTE_NONE or self.escapechar is not None:
145145
# prevents crash in _csv
146146
return quotechar
147147
return None

pandas/tests/frame/methods/test_to_csv.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,3 +1450,22 @@ def test_to_csv_warn_when_zip_tar_and_append_mode(self, tmp_path):
14501450
RuntimeWarning, match=msg, raise_on_extra_warnings=False
14511451
):
14521452
df.to_csv(tar_path, mode="a")
1453+
1454+
def test_to_csv_escape_quotechar(self):
1455+
# GH61514
1456+
df = DataFrame(
1457+
{
1458+
"col_a": ["a", "a2"],
1459+
"col_b": ['b"c', None],
1460+
"col_c": ['de,f"', '"c'],
1461+
}
1462+
)
1463+
1464+
result = df.to_csv(quotechar='"', escapechar="\\", quoting=csv.QUOTE_NONE)
1465+
expected_rows = [
1466+
",col_a,col_b,col_c",
1467+
'0,a,b\\"c,de\\,f\\"',
1468+
'1,a2,,\\"c',
1469+
]
1470+
expected = tm.convert_rows_list_to_csv_str(expected_rows)
1471+
assert result == expected

0 commit comments

Comments
 (0)