Come analizzare in modo efficiente i file a larghezza fissa?


84

Sto cercando di trovare un modo efficiente per analizzare i file che contengono linee a larghezza fissa. Ad esempio, i primi 20 caratteri rappresentano una colonna, dalle 21:30 un'altra e così via.

Supponendo che la riga contenga 100 caratteri, quale sarebbe un modo efficiente per analizzare una riga in più componenti?

Potrei usare lo string slicing per riga, ma è un po 'brutto se la linea è grande. Esistono altri metodi veloci?

Risposte:


73

Usare il structmodulo della libreria standard Python sarebbe abbastanza facile ed estremamente veloce dato che è scritto in C.

Ecco come potrebbe essere utilizzato per fare quello che vuoi. Consente inoltre di saltare le colonne di caratteri specificando valori negativi per il numero di caratteri nel campo.

import struct

fieldwidths = (2, -10, 24)  # negative widths represent ignored padding fields
fmtstring = ' '.join('{}{}'.format(abs(fw), 'x' if fw < 0 else 's')
                        for fw in fieldwidths)
fieldstruct = struct.Struct(fmtstring)
parse = fieldstruct.unpack_from
print('fmtstring: {!r}, recsize: {} chars'.format(fmtstring, fieldstruct.size))

line = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\n'
fields = parse(line)
print('fields: {}'.format(fields))

Produzione:

fmtstring: '2s 10x 24s', recsize: 36 chars
fields: ('AB', 'MNOPQRSTUVWXYZ0123456789')

Le seguenti modifiche lo adatterebbero funzionare in Python 2 o 3 (e gestiscono l'input Unicode):

import struct
import sys

fieldstruct = struct.Struct(fmtstring)
if sys.version_info[0] < 3:
    parse = fieldstruct.unpack_from
else:
    # converts unicode input to byte string and results back to unicode string
    unpack = fieldstruct.unpack_from
    parse = lambda line: tuple(s.decode() for s in unpack(line.encode()))

Ecco un modo per farlo con le fette di stringa, come stavi considerando ma temevi che potesse diventare troppo brutto. La cosa bella è che, oltre a non essere poi così brutto, è che funziona inalterato sia in Python 2 che in 3, oltre ad essere in grado di gestire stringhe Unicode. Dal punto di vista della velocità è, ovviamente, più lento delle versioni basate sul structmodulo, ma potrebbe essere leggermente accelerato rimuovendo la possibilità di avere campi di riempimento.

try:
    from itertools import izip_longest  # added in Py 2.6
except ImportError:
    from itertools import zip_longest as izip_longest  # name change in Py 3.x

try:
    from itertools import accumulate  # added in Py 3.2
except ImportError:
    def accumulate(iterable):
        'Return running totals (simplified version).'
        total = next(iterable)
        yield total
        for value in iterable:
            total += value
            yield total

def make_parser(fieldwidths):
    cuts = tuple(cut for cut in accumulate(abs(fw) for fw in fieldwidths))
    pads = tuple(fw < 0 for fw in fieldwidths) # bool values for padding fields
    flds = tuple(izip_longest(pads, (0,)+cuts, cuts))[:-1]  # ignore final one
    parse = lambda line: tuple(line[i:j] for pad, i, j in flds if not pad)
    # optional informational function attributes
    parse.size = sum(abs(fw) for fw in fieldwidths)
    parse.fmtstring = ' '.join('{}{}'.format(abs(fw), 'x' if fw < 0 else 's')
                                                for fw in fieldwidths)
    return parse

line = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\n'
fieldwidths = (2, -10, 24)  # negative widths represent ignored padding fields
parse = make_parser(fieldwidths)
fields = parse(line)
print('format: {!r}, rec size: {} chars'.format(parse.fmtstring, parse.size))
print('fields: {}'.format(fields))

Produzione:

format: '2s 10x 24s', rec size: 36 chars
fields: ('AB', 'MNOPQRSTUVWXYZ0123456789')

+1 è carino. In un certo senso, penso che questo sia simile al mio approccio (almeno quando ottieni i risultati), ma ovviamente molto più veloce.
Reiner Gerecke

1
Come funzionerebbe con Unicode? O una stringa codificata utf-8? struct.unpacksembra funzionare su dati binari. Non riesco a farlo funzionare.
Reiner Gerecke

3
@Reiner Gerecke: Il modulo struct è progettato per funzionare su dati binari. I file con campi a larghezza fissa sono lavori legacy che è anche molto probabile che pre-data UTF-8 (in mente, se non in cronologia). I byte letti dai file sono dati binari. Non hai Unicode nei file. È necessario decodificare i byte per ottenere Unicode.
John Machin

1
@Reiner Gerecke: Chiarimento: in quei formati di file legacy, ogni campo è un numero fisso di byte , non un numero fisso di caratteri. Sebbene sia improbabile che siano codificati in UTF-8, possono essere codificati in una codifica che ha un numero variabile di byte per carattere, ad esempio gbk, big5, euc-jp, shift-jis, ecc. Se desideri lavorare in Unicode, devi non può decodificare l'intero record in una volta; devi decodificare ogni campo.
John Machin

1
Questo si interrompe completamente quando si tenta di applicarlo a valori Unicode (come in Python 3) con testo al di fuori del set di caratteri ASCII e dove "larghezza fissa" significa "numero fisso di caratteri ", non byte.
Martijn Pieters

69

Non sono davvero sicuro che sia efficiente, ma dovrebbe essere leggibile (al contrario di fare lo slicing manualmente). Ho definito una funzione slicesche ottiene una stringa e lunghezze di colonna e restituisce le sottostringhe. L'ho creato un generatore, quindi per righe molto lunghe, non crea un elenco temporaneo di sottostringhe.

def slices(s, *args):
    position = 0
    for length in args:
        yield s[position:position + length]
        position += length

Esempio

In [32]: list(slices('abcdefghijklmnopqrstuvwxyz0123456789', 2))
Out[32]: ['ab']

In [33]: list(slices('abcdefghijklmnopqrstuvwxyz0123456789', 2, 10, 50))
Out[33]: ['ab', 'cdefghijkl', 'mnopqrstuvwxyz0123456789']

In [51]: d,c,h = slices('dogcathouse', 3, 3, 5)
In [52]: d,c,h
Out[52]: ('dog', 'cat', 'house')

Ma penso che il vantaggio di un generatore sia perso se hai bisogno di tutte le colonne contemporaneamente. Dove si potrebbe trarre vantaggio è quando si desidera elaborare le colonne una per una, ad esempio in un ciclo.


2
AFAICT, questo metodo è più lento di struct, ma è leggibile e più facile da gestire. Ho fatto alcuni test utilizzando il slices function, structmodulo ed anche remodulo e si scopre per file di grandi dimensioni, structè il più veloce, reè al secondo posto (1,5x più lento) e slicesterzo (2x più lento). C'è tuttavia un piccolo overhead nell'utilizzo in structmodo da slices functionpoter essere più veloce su file più piccoli.
YeO

27

Altre due opzioni più semplici e più belle delle soluzioni già menzionate:

Il primo è usare i panda:

import pandas as pd

path = 'filename.txt'

# Using Pandas with a column specification
col_specification = [(0, 20), (21, 30), (31, 50), (51, 100)]
data = pd.read_fwf(path, colspecs=col_specification)

E la seconda opzione utilizzando numpy.loadtxt:

import numpy as np

# Using NumPy and letting it figure it out automagically
data_also = np.loadtxt(path)

Dipende davvero dal modo in cui desideri utilizzare i tuoi dati.


Questo è competitivo con la risposta accettata in termini di velocità?
asachet

1
Non l'ho testato, ma dovrebbe essere molto più veloce della risposta accettata.
Tom M

1
I panda possono eseguire il rilevamento automatico da soli se imposti colspecs='infer' pandas.pydata.org/pandas-docs/stable/generated/…
James Paul Mason

14

Il codice seguente fornisce uno schizzo di ciò che potresti voler fare se hai una seria gestione dei file a larghezza di colonna fissa da fare.

"Serious" = più tipi di record in ciascuno dei più tipi di file, record fino a 1000 byte, il definitore del layout e il produttore / consumatore "avversario" è un dipartimento governativo con atteggiamento, le modifiche al layout risultano in colonne inutilizzate, fino a un milione di record in un file, ...

Caratteristiche: precompila i formati struct. Ignora le colonne indesiderate. Converte le stringhe di input nei tipi di dati richiesti (lo schizzo omette la gestione degli errori). Converte i record in istanze di oggetti (o dict o tuple con nome se preferisci).

Codice:

import struct, datetime, io, pprint

# functions for converting input fields to usable data
cnv_text = rstrip
cnv_int = int
cnv_date_dmy = lambda s: datetime.datetime.strptime(s, "%d%m%Y") # ddmmyyyy
# etc

# field specs (field name, start pos (1-relative), len, converter func)
fieldspecs = [
    ('surname', 11, 20, cnv_text),
    ('given_names', 31, 20, cnv_text),
    ('birth_date', 51, 8, cnv_date_dmy),
    ('start_date', 71, 8, cnv_date_dmy),
    ]

fieldspecs.sort(key=lambda x: x[1]) # just in case

# build the format for struct.unpack
unpack_len = 0
unpack_fmt = ""
for fieldspec in fieldspecs:
    start = fieldspec[1] - 1
    end = start + fieldspec[2]
    if start > unpack_len:
        unpack_fmt += str(start - unpack_len) + "x"
    unpack_fmt += str(end - start) + "s"
    unpack_len = end
field_indices = range(len(fieldspecs))
print unpack_len, unpack_fmt
unpacker = struct.Struct(unpack_fmt).unpack_from

class Record(object):
    pass
    # or use named tuples

raw_data = """\
....v....1....v....2....v....3....v....4....v....5....v....6....v....7....v....8
          Featherstonehaugh   Algernon Marmaduke  31121969            01012005XX
"""

f = cStringIO.StringIO(raw_data)
headings = f.next()
for line in f:
    # The guts of this loop would of course be hidden away in a function/method
    # and could be made less ugly
    raw_fields = unpacker(line)
    r = Record()
    for x in field_indices:
        setattr(r, fieldspecs[x][0], fieldspecs[x][3](raw_fields[x]))
    pprint.pprint(r.__dict__)
    print "Customer name:", r.given_names, r.surname

Produzione:

78 10x20s20s8s12x8s
{'birth_date': datetime.datetime(1969, 12, 31, 0, 0),
 'given_names': 'Algernon Marmaduke',
 'start_date': datetime.datetime(2005, 1, 1, 0, 0),
 'surname': 'Featherstonehaugh'}
Customer name: Algernon Marmaduke Featherstonehaugh

Come si aggiorna questo codice per analizzare i record maggiori di 1000 byte? Sto struct.error: unpack_from requires a buffer of at least 1157 bytes
riscontrando

4
> str = '1234567890'
> w = [0,2,5,7,10]
> [ str[ w[i-1] : w[i] ] for i in range(1,len(w)) ]
['12', '345', '67', '890']

1

Ecco un semplice modulo per Python 3, basato sulla risposta di John Machin : adattalo secondo necessità :)

"""
fixedwidth

Parse and iterate through a fixedwidth text file, returning record objects.

Adapted from https://stackoverflow.com/a/4916375/243392


USAGE

    import fixedwidth, pprint

    # define the fixed width fields we want
    # fieldspecs is a list of [name, description, start, width, type] arrays.
    fieldspecs = [
        ["FILEID", "File Identification", 1, 6, "A/N"],
        ["STUSAB", "State/U.S. Abbreviation (USPS)", 7, 2, "A"],
        ["SUMLEV", "Summary Level", 9, 3, "A/N"],
        ["LOGRECNO", "Logical Record Number", 19, 7, "N"],
        ["POP100", "Population Count (100%)", 30, 9, "N"],
    ]

    # define the fieldtype conversion functions
    fieldtype_fns = {
        'A': str.rstrip,
        'A/N': str.rstrip,
        'N': int,
    }

    # iterate over record objects in the file
    with open(f, 'rb'):
        for record in fixedwidth.reader(f, fieldspecs, fieldtype_fns):
            pprint.pprint(record.__dict__)

    # output:
    {'FILEID': 'SF1ST', 'LOGRECNO': 2, 'POP100': 1, 'STUSAB': 'TX', 'SUMLEV': '040'}
    {'FILEID': 'SF1ST', 'LOGRECNO': 3, 'POP100': 2, 'STUSAB': 'TX', 'SUMLEV': '040'}    
    ...

"""

import struct, io


# fieldspec columns
iName, iDescription, iStart, iWidth, iType = range(5)


def get_struct_unpacker(fieldspecs):
    """
    Build the format string for struct.unpack to use, based on the fieldspecs.
    fieldspecs is a list of [name, description, start, width, type] arrays.
    Returns a string like "6s2s3s7x7s4x9s".
    """
    unpack_len = 0
    unpack_fmt = ""
    for fieldspec in fieldspecs:
        start = fieldspec[iStart] - 1
        end = start + fieldspec[iWidth]
        if start > unpack_len:
            unpack_fmt += str(start - unpack_len) + "x"
        unpack_fmt += str(end - start) + "s"
        unpack_len = end
    struct_unpacker = struct.Struct(unpack_fmt).unpack_from
    return struct_unpacker


class Record(object):
    pass
    # or use named tuples


def reader(f, fieldspecs, fieldtype_fns):
    """
    Wrap a fixedwidth file and return records according to the given fieldspecs.
    fieldspecs is a list of [name, description, start, width, type] arrays.
    fieldtype_fns is a dictionary of functions used to transform the raw string values, 
    one for each type.
    """

    # make sure fieldspecs are sorted properly
    fieldspecs.sort(key=lambda fieldspec: fieldspec[iStart])

    struct_unpacker = get_struct_unpacker(fieldspecs)

    field_indices = range(len(fieldspecs))

    for line in f:
        raw_fields = struct_unpacker(line) # split line into field values
        record = Record()
        for i in field_indices:
            fieldspec = fieldspecs[i]
            fieldname = fieldspec[iName]
            s = raw_fields[i].decode() # convert raw bytes to a string
            fn = fieldtype_fns[fieldspec[iType]] # get conversion function
            value = fn(s) # convert string to value (eg to an int)
            setattr(record, fieldname, value)
        yield record


if __name__=='__main__':

    # test module

    import pprint, io

    # define the fields we want
    # fieldspecs are [name, description, start, width, type]
    fieldspecs = [
        ["FILEID", "File Identification", 1, 6, "A/N"],
        ["STUSAB", "State/U.S. Abbreviation (USPS)", 7, 2, "A"],
        ["SUMLEV", "Summary Level", 9, 3, "A/N"],
        ["LOGRECNO", "Logical Record Number", 19, 7, "N"],
        ["POP100", "Population Count (100%)", 30, 9, "N"],
    ]

    # define a conversion function for integers
    def to_int(s):
        """
        Convert a numeric string to an integer.
        Allows a leading ! as an indicator of missing or uncertain data.
        Returns None if no data.
        """
        try:
            return int(s)
        except:
            try:
                return int(s[1:]) # ignore a leading !
            except:
                return None # assume has a leading ! and no value

    # define the conversion fns
    fieldtype_fns = {
        'A': str.rstrip,
        'A/N': str.rstrip,
        'N': to_int,
        # 'N': int,
        # 'D': lambda s: datetime.datetime.strptime(s, "%d%m%Y"), # ddmmyyyy
        # etc
    }

    # define a fixedwidth sample
    sample = """\
SF1ST TX04089000  00000023748        1 
SF1ST TX04090000  00000033748!       2
SF1ST TX04091000  00000043748!        
"""
    sample_data = sample.encode() # convert string to bytes
    file_like = io.BytesIO(sample_data) # create a file-like wrapper around bytes

    # iterate over record objects in the file
    for record in reader(file_like, fieldspecs, fieldtype_fns):
        # print(record)
        pprint.pprint(record.__dict__)

1

È così che ho risolto con un dizionario che contiene dove iniziano e finiscono i campi. Dare punti di inizio e fine mi ha aiutato a gestire anche le modifiche alla lunghezza della colonna.

# fixed length
#      '---------- ------- ----------- -----------'
line = '20.06.2019 myname  active      mydevice   '
SLICES = {'date_start': 0,
         'date_end': 10,
         'name_start': 11,
         'name_end': 18,
         'status_start': 19,
         'status_end': 30,
         'device_start': 31,
         'device_end': 42}

def get_values_as_dict(line, SLICES):
    values = {}
    key_list = {key.split("_")[0] for key in SLICES.keys()}
    for key in key_list:
       values[key] = line[SLICES[key+"_start"]:SLICES[key+"_end"]].strip()
    return values

>>> print (get_values_as_dict(line,SLICES))
{'status': 'active', 'name': 'myname', 'date': '20.06.2019', 'device': 'mydevice'}

1

Ecco cosa utilizza NumPy sotto il cofano (molto molto semplificato, ma comunque - questo codice si trova LineSplitter classall'interno di _iotools module):

import numpy as np

DELIMITER = (20, 10, 10, 20, 10, 10, 20)

idx = np.cumsum([0] + list(DELIMITER))
slices = [slice(i, j) for (i, j) in zip(idx[:-1], idx[1:])]

def parse(line):
    return [line[s] for s in slices]

Non gestisce delimitatori negativi per ignorare la colonna, quindi non è versatile come struct, ma è più veloce.


0

Il taglio delle corde non deve essere brutto fintanto che lo mantieni organizzato. Considera l'idea di memorizzare le larghezze dei tuoi campi in un dizionario e quindi di utilizzare i nomi associati per creare un oggetto:

from collections import OrderedDict

class Entry:
    def __init__(self, line):

        name2width = OrderedDict()
        name2width['foo'] = 2
        name2width['bar'] = 3
        name2width['baz'] = 2

        pos = 0
        for name, width in name2width.items():

            val = line[pos : pos + width]
            if len(val) != width:
                raise ValueError("not enough characters: \'{}\'".format(line))

            setattr(self, name, val)
            pos += width

file = "ab789yz\ncd987wx\nef555uv"

entry = []

for line in file.split('\n'):
    entry.append(Entry(line))

print(entry[1].bar) # output: 987

0

Poiché il mio vecchio lavoro spesso gestisce 1 milione di righe di dati a larghezza fissa, ho fatto ricerche su questo problema quando ho iniziato a utilizzare Python.

Esistono 2 tipi di larghezza fissa

  1. ASCII FixedWidth (lunghezza del carattere ASCII = 1, lunghezza del carattere codificato a doppio byte = 2)
  2. Larghezza fissa Unicode (caratteri ASCII e lunghezza dei caratteri codificati a doppio byte = 1)

Se la stringa della risorsa è tutta composta da caratteri ASCII, ASCII FixedWidth = Unicode FixedWidth

Fortunatamente, stringa e byte sono diversi in py3, il che riduce molta confusione quando si ha a che fare con caratteri codificati a doppio byte (eggbk, big5, euc-jp, shift-jis, ecc.).
Per l'elaborazione di "ASCII FixedWidth", la stringa viene solitamente convertita in byte e quindi suddivisa.

Senza importare moduli di terze parti
totalLineCount = 1 milione, lineLength = 800 byte, FixedWidthArgs = (10,25,4, ....), ho diviso la linea in circa 5 modi e ottengo la seguente conclusione:

  1. struct è il più veloce (1x)
  2. Solo il ciclo, non la pre-elaborazione FixedWidthArgs è il più lento (5x +)
  3. slice(bytes) è più veloce di slice(string)
  4. La stringa di origine è il risultato del test in byte: struct (1x), operator.itemgetter (1.7x), sliceObject precompilato e comprensioni elenco (2.8x), oggetto re.patten (2.9x)

Quando si tratta di file di grandi dimensioni, usiamo spesso with open ( file, "rb") as f:.
Il metodo attraversa uno dei file sopra, circa 2,4 secondi.
Penso che il gestore appropriato, che elabora 1 milione di righe di dati, divide ogni riga in 20 campi e impiega meno di 2,4 secondi.

Lo trovo solo stucte itemgettersoddisfi i requisiti

ps: per la visualizzazione normale, ho convertito unicode str in byte. Se ti trovi in ​​un ambiente a doppio byte, non è necessario farlo.

from itertools import accumulate
from operator import itemgetter

def oprt_parser(sArgs):
    sum_arg = tuple(accumulate(abs(i) for i in sArgs))
    # Negative parameter field index
    cuts = tuple(i for i,num in enumerate(sArgs) if num < 0)
    # Get slice args and Ignore fields of negative length
    ig_Args = tuple(item for i, item in enumerate(zip((0,)+sum_arg,sum_arg)) if i not in cuts)
    # Generate `operator.itemgetter` object
    oprtObj =itemgetter(*[slice(s,e) for s,e in ig_Args])
    return oprtObj

lineb = b'abcdefghijklmnopqrstuvwxyz\xb0\xa1\xb2\xbb\xb4\xd3\xb5\xc4\xb6\xee\xb7\xa2\xb8\xf6\xba\xcd0123456789'
line = lineb.decode("GBK")

# Unicode Fixed Width
fieldwidthsU = (13, -13, 4, -4, 5,-5) # Negative width fields is ignored
# ASCII Fixed Width
fieldwidths = (13, -13, 8, -8, 5,-5) # Negative width fields is ignored
# Unicode FixedWidth processing
parse = oprt_parser(fieldwidthsU)
fields = parse(line)
print('Unicode FixedWidth','fields: {}'.format(tuple(map(lambda s: s.encode("GBK"), fields))))
# ASCII FixedWidth processing
parse = oprt_parser(fieldwidths)
fields = parse(lineb)
print('ASCII FixedWidth','fields: {}'.format(fields))
line = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\n'
fieldwidths = (2, -10, 24)
parse = oprt_parser(fieldwidths)
fields = parse(line)
print(f"fields: {fields}")

Produzione:

Unicode FixedWidth fields: (b'abcdefghijklm', b'\xb0\xa1\xb2\xbb\xb4\xd3\xb5\xc4', b'01234')
ASCII FixedWidth fields: (b'abcdefghijklm', b'\xb0\xa1\xb2\xbb\xb4\xd3\xb5\xc4', b'01234')
fields: ('AB', 'MNOPQRSTUVWXYZ0123456789')

oprt_parserè 4x make_parser(comprensione della lista + slice)


Durante la ricerca si è riscontrato che quando la velocità della CPU è maggiore, sembra che l'efficienza del remetodo aumenti più velocemente.
Dato che non ho computer più numerosi e migliori da testare, fornisci il mio codice di test, se qualcuno è interessato, puoi testarlo con un computer più veloce.

Ambiente di esecuzione:

  • os: win10
  • python: 3.7.2
  • PROCESSORE: amd Larousse x3 450
  • HD: seagate 1T
import timeit
import time
import re
from itertools import accumulate
from operator import itemgetter

def eff2(stmt,onlyNum= False,showResult=False):
    '''test function'''
    if onlyNum:
        rl = timeit.repeat(stmt=stmt,repeat=roundI,number=timesI,globals=globals())
        avg = sum(rl) / len(rl)
        return f"{avg * (10 ** 6)/timesI:0.4f}"
    else:
        rl = timeit.repeat(stmt=stmt,repeat=10,number=1000,globals=globals())
        avg = sum(rl) / len(rl)
        print(f"【{stmt}】")
        print(f"\tquick avg = {avg * (10 ** 6)/1000:0.4f} s/million")
        if showResult:
            print(f"\t  Result = {eval(stmt)}\n\t  timelist = {rl}\n")
        else:
            print("")

def upDouble(argList,argRate):
    return [c*argRate for c in argList]

tbStr = "000000001111000002222真2233333333000000004444444QAZ55555555000000006666666ABC这些事中文字abcdefghijk"
tbBytes = tbStr.encode("GBK")
a20 = (4,4,2,2,2,3,2,2, 2 ,2,8,8,7,3,8,8,7,3, 12 ,11)
a20U = (4,4,2,2,2,3,2,2, 1 ,2,8,8,7,3,8,8,7,3, 6 ,11)
Slng = 800
rateS = Slng // 100

tStr = "".join(upDouble(tbStr , rateS))
tBytes = tStr.encode("GBK")
spltArgs = upDouble( a20 , rateS)
spltArgsU = upDouble( a20U , rateS)

testList = []
timesI = 100000
roundI = 5
print(f"test round = {roundI} timesI = {timesI} sourceLng = {len(tStr)} argFieldCount = {len(spltArgs)}")


print(f"pure str \n{''.ljust(60,'-')}")
# ==========================================
def str_parser(sArgs):
    def prsr(oStr):
        r = []
        r_ap = r.append
        stt=0
        for lng in sArgs:
            end = stt + lng 
            r_ap(oStr[stt:end])
            stt = end 
        return tuple(r)
    return prsr

Str_P = str_parser(spltArgsU)
# eff2("Str_P(tStr)")
testList.append("Str_P(tStr)")

print(f"pure bytes \n{''.ljust(60,'-')}")
# ==========================================
def byte_parser(sArgs):
    def prsr(oBytes):
        r, stt = [], 0
        r_ap = r.append
        for lng in sArgs:
            end = stt + lng
            r_ap(oBytes[stt:end])
            stt = end
        return r
    return prsr
Byte_P = byte_parser(spltArgs)
# eff2("Byte_P(tBytes)")
testList.append("Byte_P(tBytes)")

# re,bytes
print(f"re compile object \n{''.ljust(60,'-')}")
# ==========================================


def rebc_parser(sArgs,otype="b"):
    re_Args = "".join([f"(.{{{n}}})" for n in sArgs])
    if otype == "b":
        rebc_Args = re.compile(re_Args.encode("GBK"))
    else:
        rebc_Args = re.compile(re_Args)
    def prsr(oBS):
        return rebc_Args.match(oBS).groups()
    return prsr
Rebc_P = rebc_parser(spltArgs)
# eff2("Rebc_P(tBytes)")
testList.append("Rebc_P(tBytes)")

Rebc_Ps = rebc_parser(spltArgsU,"s")
# eff2("Rebc_Ps(tStr)")
testList.append("Rebc_Ps(tStr)")


print(f"struct \n{''.ljust(60,'-')}")
# ==========================================

import struct
def struct_parser(sArgs):
    struct_Args = " ".join(map(lambda x: str(x) + "s", sArgs))
    def prsr(oBytes):
        return struct.unpack(struct_Args, oBytes)
    return prsr
Struct_P = struct_parser(spltArgs)
# eff2("Struct_P(tBytes)")
testList.append("Struct_P(tBytes)")

print(f"List Comprehensions + slice \n{''.ljust(60,'-')}")
# ==========================================
import itertools
def slice_parser(sArgs):
    tl = tuple(itertools.accumulate(sArgs))
    slice_Args = tuple(zip((0,)+tl,tl))
    def prsr(oBytes):
        return [oBytes[s:e] for s, e in slice_Args]
    return prsr
Slice_P = slice_parser(spltArgs)
# eff2("Slice_P(tBytes)")
testList.append("Slice_P(tBytes)")

def sliceObj_parser(sArgs):
    tl = tuple(itertools.accumulate(sArgs))
    tl2 = tuple(zip((0,)+tl,tl))
    sliceObj_Args = tuple(slice(s,e) for s,e in tl2)
    def prsr(oBytes):
        return [oBytes[so] for so in sliceObj_Args]
    return prsr
SliceObj_P = sliceObj_parser(spltArgs)
# eff2("SliceObj_P(tBytes)")
testList.append("SliceObj_P(tBytes)")

SliceObj_Ps = sliceObj_parser(spltArgsU)
# eff2("SliceObj_Ps(tStr)")
testList.append("SliceObj_Ps(tStr)")


print(f"operator.itemgetter + slice object \n{''.ljust(60,'-')}")
# ==========================================

def oprt_parser(sArgs):
    sum_arg = tuple(accumulate(abs(i) for i in sArgs))
    cuts = tuple(i for i,num in enumerate(sArgs) if num < 0)
    ig_Args = tuple(item for i,item in enumerate(zip((0,)+sum_arg,sum_arg)) if i not in cuts)
    oprtObj =itemgetter(*[slice(s,e) for s,e in ig_Args])
    return oprtObj

Oprt_P = oprt_parser(spltArgs)
# eff2("Oprt_P(tBytes)")
testList.append("Oprt_P(tBytes)")

Oprt_Ps = oprt_parser(spltArgsU)
# eff2("Oprt_Ps(tStr)")
testList.append("Oprt_Ps(tStr)")

print("|".join([s.split("(")[0].center(11," ") for s in testList]))
print("|".join(["".center(11,"-") for s in testList]))
print("|".join([eff2(s,True).rjust(11," ") for s in testList]))

Produzione:

Test round = 5 timesI = 100000 sourceLng = 744 argFieldCount = 20
...
...
   Str_P | Byte_P | Rebc_P | Rebc_Ps | Struct_P | Slice_P | SliceObj_P|SliceObj_Ps| Oprt_P | Oprt_Ps
-----------|-----------|-----------|-----------|-- ---------|-----------|-----------|-----------|---- -------|-----------
     9.6315| 7.5952| 4.4187| 5.6867| 1.5123| 5.2915| 4.2673| 5.7121| 2.4713| 3.9051

@MartijnPieters Funzione più efficiente
notback

0

Mi piace elaborare file di testo contenenti campi a larghezza fissa utilizzando espressioni regolari . Più specificamente, utilizzando i gruppi di acquisizione denominati . È veloce, non richiede l'importazione di librerie di grandi dimensioni ed è abbastanza descrittivo e conveniente (secondo me).

Mi piace anche il fatto che i gruppi di cattura denominati stiano fondamentalmente documentando automaticamente il formato dei dati, agendo come una sorta di specifica dei dati , poiché ogni gruppo di cattura può essere scritto per definire il nome, il tipo di dati e la lunghezza di ogni campo.

Ecco un semplice esempio ...

import re

data = [
    "1234ABCDEFGHIJ5", 
    "6789KLMNOPQRST0"
]

record_regex = (
    r"^"
    r"(?P<firstnumbers>[0-9]{4})"
    r"(?P<middletext>[a-zA-Z0-9_\-\s]{10})"
    r"(?P<lastnumber>[0-9]{1})"
    r"$"
)

records = []

for line in data:
    match = re.match(record_regex, line)
    if match:
        records.append(match.groupdict())

print(records)

... che fornisce un comodo dizionario di ogni record:

[
    {'firstnumbers': '1234', 'lastnumber': '5', 'middletext': 'ABCDEFGHIJ'},
    {'firstnumbers': '6789', 'lastnumber': '0', 'middletext': 'KLMNOPQRST'}
]

Strumenti utili, come il tester e debugger di regex online , sono disponibili se non si ha familiarità (o si è a proprio agio) con le espressioni regolari di Python o con i gruppi di cattura denominati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.