Запись/разбор файла фиксированной ширины с использованием Python

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

В основном они нуждаются в текстовом файле с фиксированной шириной 80 символов с определенными "кусками" поля с данными, а остальные остаются пустыми. У меня есть документация, поэтому я знаю, какова длина каждого "куска". Ответ, который я получаю, легче анализировать, поскольку у него уже есть данные, и я могу использовать "срезы" Python, чтобы извлечь то, что мне нужно, но я не могу назначить срез - я пробовал это уже потому, что это звучало как хороший решение, и это не сработало, поскольку строки Python неизменяемы:)

Как я уже сказал, я действительно новичок на Python, но я очень рад узнать об этом:) Как бы я это сделал? В идеале я хотел бы сказать, что диапазон 10-20 равен "Foo", и пусть это будет строка "Foo" с 7 дополнительными пробельными символами (при условии, что указанное поле имеет длину 10), и это будет часть более крупного 80-символьного поля, но я не уверен, как делать то, что я думаю.

Ответ 1

Вам не нужно назначать срезы, просто создайте строку, используя % formatting.

Пример с фиксированным форматом для 3 элементов данных:

>>> fmt="%4s%10s%10s"
>>> fmt % (1,"ONE",2)
'   1       ONE         2'
>>> 

То же самое, ширина поля, поставляемая с данными:

>>> fmt2 = "%*s%*s%*s"
>>> fmt2 % (4,1, 10,"ONE", 10,2)
'   1       ONE         2'
>>> 

Разделите данные и ширину поля и используйте тэки zip() и str.join():

>>> widths=(4,10,10)
>>> items=(1,"ONE",2)
>>> "".join("%*s" % i for i in zip(widths, items))
'   1       ONE         2'
>>> 

Ответ 2

Надеюсь, я понимаю, что вы ищете: каким-то образом удобно идентифицировать каждую часть строки с помощью простой переменной, но выводить ее с нужной шириной?

Ниже приведенный ниже фрагмент может дать вам то, что вы хотите

class FixWidthFieldLine(object):

    fields = (('foo', 10),
              ('bar', 30),
              ('ooga', 30),
              ('booga', 10))

    def __init__(self):
        self.foo = ''
        self.bar = ''
        self.ooga = ''
        self.booga = ''

    def __str__(self):
        return ''.join([getattr(self, field_name).ljust(width) 
                        for field_name, width in self.fields])

f = FixWidthFieldLine()
f.foo = 'hi'
f.bar = 'joe'
f.ooga = 'howya'
f.booga = 'doin?'

print f

Это дает:

hi        joe                           howya                         doing     

Он работает, сохраняя переменную уровня класса fields, которая записывает порядок, в котором каждое поле должно появляться на выходе, вместе с количеством столбцов, которое должно иметь это поле. В __init__ есть соответствующие имена переменных экземпляра, которые изначально устанавливаются в пустую строку.

Метод __str__ выводит эти значения в виде строки. Он использует понимание списка над атрибутом fields на уровне класса, просматривая значение экземпляра для каждого поля по имени, а затем выравнивая его по левому краю в соответствии с столбцами. Полученный список полей затем объединяется пустой строкой.

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

Ответ 3

Вы можете использовать justify функции для выравнивания по левому краю, выравнивания по правому краю и центрирования строки в поле заданной ширины.

'hi'.ljust(10) -> 'hi        '

Ответ 4

Я знаю, что этот поток довольно старый, но мы используем библиотеку django-copybook. Это не имеет никакого отношения к django (больше). Мы используем его для перехода между файлами cobol с фиксированной шириной и питоном. Вы создаете класс для определения макета записи фиксированной ширины и можете легко перемещаться между типизированными объектами python и файлами с фиксированной шириной:

USAGE:
class Person(Record):
    first_name = fields.StringField(length=20)
    last_name = fields.StringField(length=30)
    siblings = fields.IntegerField(length=2)
    birth_date = fields.DateField(length=10, format="%Y-%m-%d")

>>> fixedwidth_record = 'Joe                 Smith                         031982-09-11'
>>> person = Person.from_record(fixedwidth_record)
>>> person.first_name
'Joe'
>>> person.last_name
'Smith'
>>> person.siblings
3
>>> person.birth_date
datetime.date(1982, 9, 11)

Он также может обрабатывать ситуации, подобные функциям Cobol OCCURS, например, когда конкретный раздел повторяется X раз

Ответ 5

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

Самый простой способ преодоления неизменяемости строк - написать новую строку:

# Won't work:
test_string[3:6] = "foo"

# Will work:
test_string = test_string[:3] + "foo" + test_string[6:]

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

Ответ 6

Вы можете преобразовать строку в список и выполнить манипуляции с фрагментами.

>>> text = list("some text")
>>> text[0:4] = list("fine")
>>> text
['f', 'i', 'n', 'e', ' ', 't', 'e', 'x', 't']
>>> text[0:4] = list("all")
>>> text
['a', 'l', 'l', ' ', 't', 'e', 'x', 't']
>>> import string
>>> string.join(text, "")
'all text'

Ответ 7

Легко написать функцию для "модификации" строки.

def change(string, start, end, what):
    length = end - start
    if len(what)<length: what = what + " "*(length-len(what))
    return string[0:start]+what[0:length]+string[end:]

Использование:

test_string = 'This is test string'

print test_string[5:7]  
# is
test_string = change(test_string, 5, 7, 'IS')
# This IS test string
test_string = change(test_string, 8, 12, 'X')
# This IS X    string
test_string = change(test_string, 8, 12, 'XXXXXXXXXXXX')
# This IS XXXX string

Ответ 8

Я использовал пример Jarret Hardie и немного изменил его. Это позволяет выбрать тип выравнивания текста (слева, справа или по центру).

class FixedWidthFieldLine(object):
    def __init__(self, fields, justify = 'L'):
        """ Returns line from list containing tuples of field values and lengths. Accepts
            justification parameter.
            FixedWidthFieldLine(fields[, justify])

            fields = [(value, fieldLenght)[, ...]]
        """
        self.fields = fields

        if (justify in ('L','C','R')):
            self.justify = justify
        else:
            self.justify = 'L'

    def __str__(self):
        if(self.justify == 'L'):
            return ''.join([field[0].ljust(field[1]) for field in self.fields])
        elif(self.justify == 'R'):
            return ''.join([field[0].rjust(field[1]) for field in self.fields])
        elif(self.justify == 'C'):
            return ''.join([field[0].center(field[1]) for field in self.fields])

fieldTest = [('Alex', 10),
         ('Programmer', 20),
         ('Salem, OR', 15)]

f = FixedWidthFieldLine(fieldTest)
print f
f = FixedWidthFieldLine(fieldTest,'R')
print f

Возврат:

Alex      Programmer          Salem, OR      
      Alex          Programmer      Salem, OR