Почему csv.reader с TextIOWrapper включает символы новой строки?

У меня есть две функции, одна загружает отдельные файлы csv, а другая загружает zip с несколькими файлами csv.

Функция download_and_process_csv работает правильно и, похоже, заменяет символы новой строки пробелом.

"Курица, вода, кукурузная мука, соль, декстроза, сахар, фосфат натрия, эриторбат натрия, нитрит натрия. Произведено на предприятии, где присутствуют аллергены, такие как яйца, молоко, соя, пшеница, горчица, глютен, овес, молочные продукты.'

Функция download_and_process_zip по какой-то причине включает символы новой строки (\n\n). Я пробовал newline='' в io.TextIOWrapper, но он просто заменяет его на \r\n.

"Курица, вода, кукурузная мука, соль, декстроза, сахар, фосфат натрия, эриторбат натрия, нитрит натрия. \n\nПроизведено на предприятии, где присутствуют аллергены, такие как яйца, молоко, соя, пшеница, горчица, глютен, овес, молочные продукты.'

Есть ли способ изменить download_and_process_zip так, чтобы символы новой строки исключались/заменялись, или мне придется перебирать все строки и вручную заменять символы?

@request_exceptions
def download_and_process_csv(client, url, model_class):
    with closing(client.get(url, stream=True)) as response:
        response.raise_for_status()
        response.encoding = 'utf-8'
        reader = csv.reader(response.iter_lines(decode_unicode=True))
        process_copy_from_csv(model_class, reader)


@request_exceptions
def download_and_process_zip(client, url):
    with closing(client.get(url, stream=True)) as response:
        response.raise_for_status()

        with io.BytesIO(response.content) as buffer:
            with zipfile.ZipFile(buffer, 'r') as z:
                for filename in z.namelist():
                    base_filename, file_extension = os.path.splitext(filename)
                    model_class = apps.get_model(base_filename)
                    if file_extension == '.csv':
                        with z.open(filename) as csv_file:
                            reader = csv.reader(io.TextIOWrapper(
                                csv_file,
                                encoding='utf-8',
                                # newline='',
                            ))
                            process_copy_from_csv(model_class, reader)

1. При вызове open() с указанием в качестве аргумента только пути к файлу используются аргументы по умолчанию для остальных параметров. По умолчанию файл открывается в текстовом режиме для чтения -mode='r'. Функция возвращает экземпляр io.TextIOWrapper из-за этого значения по умолчанию mode, так зачем оборачивать это в еще один io.TextIOWrapper?

2. В документации явно указано, что аргумент newline должен быть '', но значение по умолчанию для аргумента newline такое, какое вы используете -newline=None.

Python csv.reader() [python.org]

If csvfile is a file object, it should be opened with newline=''.

Исправить это можно следующим образом:

#•••Rest of code•••
with z.open(filename, newline='', encoding='utf-8') as csv_file:
    reader = csv.reader(csv_file) #[1]
    #•••Rest of code•••

[1] csv_file is an instance of io.TextIOWrapper so the unnecessary code was removed.

Я поиграл с макетным сервером, который обслуживает этот CSV-файл:

"foo
bar"

В CSV есть одно поле "foo\nbar" в одной строке. Я называю новую строку в данных встроенной новой строкой.

Когда я использую метод iter_content на объекте Response:

print("Getting CSV")
resp = requests.get("http://localhost:8999/csv")
x = resp.iter_content(decode_unicode=True)

reader = csv.reader(x)
for row in reader:
    print(row)

Я получаю правильный вывод, печатается одна строка с одним полем данных:

Getting CSV
['foo\nbar']

Если я меняю iter_content на iter_lines, то получаю неправильный вывод:

Getting CSV
['foobar']

Я подозреваю, исходя из названия, что iter_lines ищет любую последовательность символов, похожую на новую строку, и останавливается на ней, прежде чем передать строку в csv-ридер (без новой строки), и таким образом встроенная новая строка эффективно удаляется. Я не могу сказать за ваш результат, где новая строка была заменена пробелом... никакой замены не происходит, просто эффективно удаляется.

В этом популярном SO, Use python requests to download CSV, задается общий вопрос о загрузке CSV с помощью модуля requests, но каждый ответ, похоже, ориентирован на то, что рассматриваемый CSV не содержит встроенных новых строк, и поэтому в нем много ответов с iter_lines. Я не знаю, когда в requests была добавлена функция iter_content(), но ни в одном ответе об этом не упоминается.

Вернуться на верх