Python: повторно открывать файл на каждой итерации или усекать для перезаписи?

В Python, если у вас есть цикл, в каждой итерации которого вы хотите записать в файл (маринование в моем случае), перезаписывая любые данные, которые уже есть, один из вариантов — открыть файл перед циклом, оставить его открытым, и обрезать его на каждой итерации, чтобы стереть предыдущие данные перед записью новых данных:

import pickle
with open(filename, 'wb') as file:
    for blah in blahs:
        file.truncate(0)
        file.seek(0)
        pickle.dump(blah, file)

а другой — просто повторно открывать файл на каждой итерации, так как открытие его в wb автоматически усекает его:

import pickle
for blah in blahs:
    with open(filename, 'wb') as file:
        pickle.dump(blah, file)

Что лучше (с точки зрения производительности/скорости, обработки системных ресурсов и т. д.)? Есть ли лучший способ перезаписать данные в уже открытом файле, чем использовать file.truncate() и file.seek(), как указано выше?

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

Which is best (in terms of performance — похоже, вы могли бы легко рассчитать время для обоих подходов в ваших конкретных условиях.
FlyingTeller 14.07.2023 17:13

@FlyingTeller Вопрос больше в том, чтобы узнать, существуют ли общие независимые от системы концепции, которые сделали бы одну лучше другой (и что это за концепции), основанные на вещах, выходящих за рамки производительности (обработка ресурсов и т. д.), а также на любой другой общей мудрости и советы, относящиеся к этому

o c 14.07.2023 20:26

Я действительно не понимаю, почему вы хотите использовать цикл, если вы продолжаете перезаписывать данные. Вы можете просто написать последнюю запись в blahs.

Matthias 15.07.2023 02:37

@Matthias Приведенный код является просто иллюстративным примером базовой структуры - он явно не предназначен для использования на практике. Примером практического варианта использования структуры является цикл while с динамически генерируемыми данными, и вы хотите записывать данные на каждой итерации в файл в качестве резервной копии перед их дальнейшей обработкой в ​​цикле, а затем после того, как вы обработав его, вам больше не нужна резервная копия, поэтому ее можно впоследствии перезаписать.

o c 15.07.2023 03:14

На многих платформах truncate(0) не заполняется. Вы можете подтвердить это, выполнив truncate(0), а затем написав 1 байт без seek(0). Таким образом, выполнение seek, dump, затем truncate (при необходимости) в таком порядке уменьшит количество избыточного заполнения нулями. Однако разница намного меньше, чем время, необходимое для dump, поэтому я не думаю, что это стоит делать. Другая возможность заключается в том, что если размер буфера достаточно больше, чем blah, это может уменьшить количество фактических операций записи на диск. Этот эксплойт нельзя было бы повторно открыть, так как он всегда пишет при закрытии.

ken 15.07.2023 07:36
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
5
88
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я не люблю гадать, поэтому я описал два подхода:

import pickle
import tempfile
from random import choices
from string import ascii_lowercase, ascii_uppercase, digits
from pathlib import Path

from performance_measurement import run_performance_comparison


class Bla:
    def __init__(self):
        population = ascii_uppercase + digits + ascii_lowercase
        self._content = str.join("", choices(population, k=50))


def truncate_approach(blahs: list[Bla], filename: str):
    with open(filename, "wb") as file:
        for blah in blahs:
            file.truncate(0)
            file.seek(0)
            pickle.dump(blah, file)


def reopen_approach(blahs: list[Bla], filename: str):
    for blah in blahs:
        with open(filename, "wb") as file:
            pickle.dump(blah, file)


def setup(N):
    return [[Bla() for i in range(N)], Path(tempfile.NamedTemporaryFile().name)]


run_performance_comparison(
    approaches=[truncate_approach, reopen_approach],
    data_size=[10, 20, 30, 100, 200, 300, 1000, 2000, 3000],
    setup=setup,
    number_of_repetitions=10,
)

truncate_approach немного быстрее. Я предполагаю, что это потому, что мы меньше взаимодействуем с диском и иногда урезаем содержимое и переустанавливаем буфер записи, прежде чем нам придется взаимодействовать с жестким диском.

Код профилирования:

import timeit
from functools import partial

import matplotlib.pyplot as plt
from typing import List, Dict, Callable

from contextlib import contextmanager
import matplotlib.pyplot as plt
import matplotlib.transforms as mtransforms
import matplotlib.ticker as ticker
import numpy as np


@contextmanager
def data_provider(data_size, setup=lambda N: N, teardown=lambda: None):
    data = setup(data_size)
    yield data
    teardown(*data)


def run_performance_comparison(approaches: List[Callable],
                               data_size: List[int],
                               *,
                               setup=lambda N: [N],
                               teardown=lambda *N: None,
                               number_of_repetitions=5,
                               title='Performance Comparison',
                               data_name='N',
                               yscale='log',
                               xscale='log'):
    approach_times: Dict[Callable, List[float]] = {approach: [] for approach in approaches}
    for N in data_size:
        with data_provider(N, setup, teardown) as data:
            print(f'Running performance comparison for {data_name} = {N}')
            for approach in approaches:
                function = partial(approach, *data)
                approach_time = min(timeit.Timer(function).repeat(repeat=number_of_repetitions, number=1))
                approach_times[approach].append(approach_time)

    for approach in approaches:
        plt.plot(data_size, approach_times[approach], label=approach.__name__)
    plt.yscale(yscale)
    plt.xscale(xscale)

    plt.xlabel(data_name)
    plt.ylabel('Execution Time (seconds)')
    plt.title(title)
    plt.legend()
    plt.show()

Другие вопросы по теме