Эффективное управление файлами PyYAML - PullRequest
0 голосов
/ 16 сентября 2018

Я пишу программу на Python, которая поддерживает список контактов, каждое из которых имеет 3 поля:

  1. Имя * * 1004
  2. Номер телефона
  3. E-mail

Контакты должны быть сохранены в структурированном файле YAML, и программа должна обеспечивать возможность добавления новых контактов.

Мой код для этого:

class contacts:
    def add_contact(self,file,contact):
        if not os.path.exists(file):
            #Creating for the first time
            temp = []
            temp.append(contact)
            with open(file, "w") as file_desc:
                yaml.dump(temp, file_desc, default_flow_style=False)
            file_desc.close()
        else:
            #Second onwards
            with open(file, "r") as file_desc:
                loaded = yaml.safe_load(file_desc)
                loaded.append(contact)
                with open(file, "w") as file_desc2:
                    yaml.dump(loaded, file_desc2, default_flow_style=False)
                    file_desc2.close()
            file_desc.close()

if __name__ == "__main__":

    data1 = {'name' :'Abcd', 'phone': 1234, 'email': 'abcd@gmail.com'}
    data2 = {'name': 'efgh', 'phone': 5678, 'email': 'efgh@gmail.com'}
    contact = contacts()
    contact.add_contact("contacts.yaml", data1)
    contact.add_contact("contacts.yaml",data2)

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

1 Ответ

0 голосов
/ 16 сентября 2018

В долго выполняющейся программе / процессе P действительно нет необходимости перечитывать данные.Есть несколько вещей, которые нужно иметь в виду:

  1. Если вы используете документ YAML в других программах только тогда, когда P остановлен, то вам нужно только выписать файл при выходе из P.Возможно, вы захотите сделать это, используя atexit, если у вас нет единой точки выхода

  2. Если другие программы могут редактировать / обновлять список, пока Pвыполняется, затем убедитесь, что вы проверили отметку даты и времени файла YAML и перечитали файл перед добавлением нового контакта.При необходимости вы можете работать с блокировками, чтобы убедиться, что одновременно обновляется только одна программа.

  3. Если для других программ требуется обновленный документ YAML, вы можетелибо выписывайте YAML при каждом обновлении, либо вы можете использовать какой-то механизм для уведомления P о необходимости написания документа YAML.Для этого я использовал обработку SIGINT и связь на основе zeromq.

Многое из вышеперечисленного сделано для вас, если вы используете реальную базу данных, и дляпростая таблица записей, которые имеют одинаковые поля, что может быть лучшей альтернативой.Однако, как только все становится более сложным: разные поля для каждой записи, сложные и возможные рекурсивные данные, многие базы данных (SQL) становятся дополнительной проблемой, а не помогают решить ту, которую вы пытаетесь решить.


ruamel.yaml.base (отказ от ответственности: я являюсь автором этого пакета) выполняет пункт 2) для вас из коробки, остальные два элемента также легко реализуются.Единственная хитрость заключается в том, что YAMLBase обычно ожидает сопоставление / dict на корневом уровне для нового файла, поэтому необходимо некоторое принуждение, когда файл еще не существует.

После того, как вы это сделаетеpip install ruamel.yaml.base:

import os
import ruamel.yaml
from ruamel.yaml.base import YAMLBase

yaml_path = 'contacts.yaml'

class Contacts(YAMLBase):
   def __init__(self, path=yaml_path, verbose=0):
       self._create_ok = True  # so the file is auto created if it doesn't exists
       super().__init__(path=path, verbose=verbose)
       if not os.path.exists(yaml_path):
           # this is necessary to force block style sequence at the top
           self._data = ruamel.yaml.comments.CommentedSeq()
           self._changed = True

   def add_record(self, contact):
       self.data.append(contact)
       self._changed = True  # this signals that writing is necessary

   def dump_file(self):
       """dump the contents of the file on disc"""
       print('dumping: "{}"'.format(self._path))
       with open(yaml_path) as fp:
           print(fp.read(), end='')



data1 = {'name' :'Abcd', 'phone': 1234, 'email': 'abcd@gmail.com'}
data2 = {'name': 'efgh', 'phone': 5678, 'email': 'efgh@gmail.com'}

contacts = Contacts()
contacts.add_record(data1)
contacts.save()  # optional
contacts.dump_file()

# this is just for checking 

contacts.add_record(data2)
contacts.save()
contacts.dump_file()

, что дает:

dumping: "contacts.yaml"
- name: Abcd
  phone: 1234
  email: abcd@gmail.com
dumping: "contacts.yaml"
- name: Abcd
  phone: 1234
  email: abcd@gmail.com
- name: efgh
  phone: 5678
  email: efgh@gmail.com

Если вы установите для параметра verbose значение 1, вы получите некоторую информацию о stdout о том, что происходитв пакете.

Если у вас много записей, вы можете изменить self.data в Contacts на self.fast_data, тогда будет загружен YAML с использованием гораздо более быстрого загрузчика на основе Си,за счет невозможности сохранить (добавленные вручную) комментарии и т. д. во входном YAML.(В любом случае используется «safe_load»).

...