【问题标题】:Writing/parsing text file with fixed width lines使用固定宽度行写入/解析文本文件
【发布时间】:2010-10-25 07:32:27
【问题描述】:

我是 Python 的新手,我正在考虑使用它来编写我们的供应商需要的一些复杂的 EDI 内容。

基本上,他们需要一个 80 个字符的固定宽度文本文件,其中包含带有数据的字段的某些“块”,其他的则留空。我有文档,所以我知道每个“块”的长度是多少。我得到的响应更容易解析,因为它已经有数据,我可以使用 Python 的“切片”来提取我需要的内容,但我不能分配给切片 - 我已经尝试过了,因为它听起来不错解决方案,它不起作用,因为 Python 字符串是不可变的 :)

就像我说的那样,我真的是 Python 的新手,但我对学习它感到很兴奋 :) 我该怎么做呢?理想情况下,我希望能够说范围 10-20 等于“Foo”,并使其成为带有 7 个额外空白字符的字符串“Foo”(假设所述字段的长度为 10)并且具有较大的 80 个字符字段的一部分,但我不知道该怎么做。

【问题讨论】:

  • 您正在处理 X12 EDI 消息吗?布局并不是真正固定的。你在处理其他格式吗?如果是这样,那不是真的 [EDI] 是吗?它只是固定的文件布局。
  • 我不知道,真的。他们在所有文档中都将其称为“EDI”。我所知道的是我必须向他们发送一条记录(他们称之为“H0”记录,他们会发回一个文件让我解析。
  • X12 的 ISA 标头是固定宽度(第一行),因为直到行尾才声明分隔符。

标签: python parsing edi


【解决方案1】:

解析您的问题有点困难,但我收集到您正在接收文件或类似文件的对象,读取它,并用一些业务逻辑结果替换一些值。这是正确的吗?

克服字符串不变性的最简单方法是编写一个新字符串:

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

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

话虽如此,听起来对你来说用这个字符串做一些事情很重要,但我不确定那是什么。您是将其写回输出文件,尝试就地编辑文件还是其他?我提出这一点是因为创建新字符串(恰好与旧字符串具有相同的变量名)的行为应该强调在转换后执行显式写入操作的必要性。

【讨论】:

    【解决方案2】:

    您不需要分配给切片,只需使用% 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'
    >>> 
    

    【讨论】:

      【解决方案3】:

      希望我明白您在寻找什么:通过一个简单的变量方便地识别行的每一部分,但输出它填充到正确的宽度?

      下面的sn-p可能会给你你想要的

      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 中的字段和字段宽度解析列。它也不检查长于分配宽度的实例值。

      【讨论】:

        【解决方案4】:

        您可以将字符串转换为列表并进行切片操作。

        >>> 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'
        

        【讨论】:

        • 有趣。您无需转换为列表即可提取。这很愚蠢。但是构建一个列表,然后折叠成一个字符串……它给你的东西看起来有点像“可变字符串”——前提是你预先分配了足够的空间。
        • 实际上,如果您不太关心性能,则不需要预先分配任何东西。如果为切片的范围分配了更大的范围,则列表类型将自动分配更多空间。
        • 为了清楚起见,还有列表转换。当然,如果他从头开始直接将数据读取到列表中可能会更好,但这不是我想要展示的。
        【解决方案5】:

        编写函数来“修改”字符串很容易。

        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
        

        【讨论】:

          【解决方案6】:

          您可以使用justify 函数在给定宽度的字段中将字符串左对齐、右对齐和居中。

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

          【讨论】:

            【解决方案7】:

            我使用了 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
            

            【讨论】:

              【解决方案8】:

              我知道这个线程已经很老了,但我们使用了一个名为 django-copybook 的库。它与 django 无关(不再)。我们使用它在固定宽度的 cobol 文件和 python 之间切换。您创建一个类来定义您的固定宽度记录布局,并且可以轻松地在键入的 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 次时

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2011-06-22
                • 1970-01-01
                • 1970-01-01
                • 2023-03-25
                • 1970-01-01
                • 1970-01-01
                • 2011-01-26
                • 1970-01-01
                相关资源
                最近更新 更多