Как правильно читать и записывать файлы .cfg через python

Мне нужна помощь с чтением и записью файлов .cfg в Python. Файлы .cfg, которые я хочу читать и записывать, имеют определенный формат, который нельзя изменить.

Пример файла .cfg:

[META]
title = "Xxxx xxxx"
creator = "xxx"
artist = "xxxx"
artist = "xxx xxx"
title = "xxx xxx (xxxxxx)"
length = "4:59"

Некоторые файлы .cfg также могут иметь очень длинные форматы (что мне кажется странным). Это всего лишь фрагмент очень длинного .cfg файла:

[EASY]
bpm = 100
offset = 0
spawn = [63, 111, 161, 201, 285, 339, 342, 347, 380, 388, 422, 449, 470, 507, 511, 531, 551, 555, 583, 591, 634, 638, 642, 701, 783]
half_spawn = [0, 1, 8, 16]
initial_data = {
    "type": 1,
    "animation": "xxxx.png",
    "looping": false,
    "fx": "",
    "background": [
        "xxxx.png",
        {
            "static": false
        }
    ],
    "voice_bank": {

    }
}

Я использовал такие библиотеки, как libconf и configparser, но ни одна из них не понимает формат файла и сообщает о многочисленных ошибках.

Больше всего мне повезло, когда я пытался прочитать этот тип файла, используя что-то вроде этого:

lines = open('./test.cfg').read().splitlines()

Но даже это приводит к множеству проблем с форматированием, и с этим трудно работать. Я видел другие сообщения об этом, но они не имеют такого же формата.

Я не очень разбираюсь в Python, любая помощь приветствуется.

какой файл вы пытаетесь прочитать, верхний или нижний?

D.L 05.07.2023 23:21

Не существует единого стандарта для конфигурационных файлов. Знание того, какая программа читает этот .cfg, может помочь найти решение. Если его читает «foobar», вы можете выполнить поиск в Интернете по «формату файла конфигурации foobar» или что-то в этом роде.

tdelaney 05.07.2023 23:26

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

OsRaMoSaO 05.07.2023 23:27

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

frenchytheasian 05.07.2023 23:30

@tdelaney Программа, которая будет читать это, представляет собой ритмическую игру, которая, насколько мне известно, была создана на игровом движке под названием godot, который, как я полагаю, основан на C.

OsRaMoSaO 05.07.2023 23:40

@frenchytheasian Эта доза звучит как то, что мне, возможно, придется в конечном итоге сделать, хотя я понятия не имею, как это сделать. Поскольку у меня нет возможности изменить формат .cfg, поскольку программа, которую я не могу редактировать, должна его прочитать. Я просто модифицирую игру.

OsRaMoSaO 05.07.2023 23:43

Godot ConfigFile выглядит так, как у вас получилось. Ссылка показывает средства доступа GDScript и CSharp. Не знаю, как это поможет, за исключением того, что, возможно, игра примет GDScript, который вы могли бы использовать. Возможно, отдельная программа CSharp, которая преобразуется во что-то, что может анализировать python. Пусть он пишет на стандартный вывод.

tdelaney 06.07.2023 00:30

@tdelaney Я не уверен, что вы имеете в виду под стандартным выводом, но большое спасибо, вы поставили меня на правильный путь, чтобы понять это. Я рассмотрю возможность создания отдельной программы на С#, которая либо дозирует все, что я хочу для меня, полностью избавляясь от Python, либо интегрирую C# вместе с моей текущей программой на Python.

OsRaMoSaO 06.07.2023 00:44

Я думал, что это программа C#, которая берет файл конфигурации, создает JSON и записывает его в sys.stdout. Затем с помощью вызова subprocess.run в python вы можете получить JSON, не касаясь жесткого диска с временным файлом. После небольшого поиска кажется, что другие также работали над углом json. Там может быть уже инструмент там.

tdelaney 06.07.2023 00:51

@tdelaney Это определенно будет проблемой для меня, но я готов к этому. Я попытаюсь сделать, как вы сказали, а также поищу инструмент, который уже может делать то, что я хочу, с частью json.

OsRaMoSaO 06.07.2023 01:06

На самом деле это невозможно сделать только с помощью стандартных инструментов библиотеки, секундочку...

AKX 06.07.2023 14:15
Почему в 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
11
52
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Одним из возможных решений может быть использование библиотеки python-libconf, которая является полностью написанной на Python.

это пример использования:

import libconf
with io.open('file.cfg', encoding='utf-8') as f:
    cfg = libconf.load(f)

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

OsRaMoSaO 05.07.2023 23:35
Ответ принят как подходящий

Вы можете написать небольшой конечный автомат для поддержки «сложных» многострочных значений и стандартную библиотеку ast.literal_eval() для преобразования значений в данные Python.

Это не удастся для таких вызовов, как Vector3(0, 0, 0), которые допустимы в файлах Godot, но вы можете довольно легко добавить код, например. пропустите их или используйте, например. библиотека pure-eval для работы с ними.

Предполагая, что переменная data_fp является открытым файлом (я использовал io.StringIO() для тестирования),

import ast

def parse_godot_value(value: str):
    # The Godot complex value structure is close enough to Python
    # that we can do a couple small modifications
    # and then pass it through ast.literal_eval() to get a Python object.

    try:
        # Try without modifications first – we might get lucky.
        return ast.literal_eval(value)
    except Exception:
        pass

    # These should preferably be done by walking the AST and replacing `false` identifiers
    # with `False` constants, but this is a quick and dirty solution.
    value = value.replace("false", "False")
    value = value.replace("true", "True")
    value = value.replace("null", "None")

    return ast.literal_eval(value)


def parse_godotesque_ini(fp):
    current_section = None
    complex_key = None
    complex_value = None
    for line in fp:
        line = line.rstrip()
        if not line:
            continue
        if line.startswith("["):
            current_section = line.strip("[]")
            continue
        if complex_value is None:  # Not busy parsing a complex value
            key, _, value = line.partition(" = ")
            key = key.strip()
            value = value.strip()
            if not (key and value):
                continue
            if value == "{":
                complex_key = key
                complex_value = ["{"]  # Start of a complex value
            else:
                yield (current_section, key, parse_godot_value(value))
        else:  # Busy parsing a complex value
            complex_value.append(line)
            if line == "}":  # End of a complex value
                yield (current_section, complex_key, parse_godot_value("\n".join(complex_value)))
                complex_key = None
                complex_value = None


for section, key, value in parse_godotesque_ini(data_fp):
    print(section, key, value)

распечатывает

META title Xxxx xxxx
META creator xxx
META artist xxxx
META artist xxx xxx
META title xxx xxx (xxxxxx)
META length 4:59
EASY bpm 100
EASY offset 0
EASY spawn [63, 111, 161, 201, 285, 339, 342, 347, 380, 388, 422, 449, 470, 507, 511, 531, 551, 555, 583, 591, 634, 638, 642, 701, 783]
EASY half_spawn [0, 1, 8, 16]
EASY initial_data {'type': 1, 'animation': 'xxxx.png', 'looping': False, 'fx': '', 'background': ['xxxx.png', {'static': False}], 'voice_bank': {}}

для данных, которые вы вставили.

Удачи!

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