заменить точку с запятой на новую строку в коде python

Я хотел бы разобрать код Python, содержащий точки с запятой ; для разделения команд и создания кода, который заменяет их новыми строками n. Е. Г., от

def main():
    a = "a;b"; return a

Я хотел бы произвести

def main():
    a = "a;b"
    return a

какие-то намеки?

2 ответов


использовать tokenize библиотека искать token.OP маркеры, где второй элемент-это ; *. Замените эти маркеры на token.NEWLINE маркер.

вам нужно будет настроить смещения токенов и сгенерировать соответствующий отступ; поэтому после NEWLINE вам нужно будет настроить номера строк (увеличение на смещение, которое вы увеличиваете для каждого NEWLINE вы вставляете) и "следующая" строка (оставшаяся часть текущей строки) должны быть скорректированы индексы в соответствии с текущим уровнем отступа:

import tokenize

TokenInfo = getattr(tokenize, 'TokenInfo', lambda *a: a)  # Python 3 compat

def semicolon_to_newline(tokens):
    line_offset = 0
    last_indent = None
    col_offset = None  # None or an integer
    for ttype, tstr, (slno, scol), (elno, ecol), line in tokens:
        slno, elno = slno + line_offset, elno + line_offset
        if ttype in (tokenize.INDENT, tokenize.DEDENT):
            last_indent = ecol  # block is indented to this column
        elif ttype == tokenize.OP and tstr == ';':
            # swap out semicolon with a newline
            ttype = tokenize.NEWLINE
            tstr = '\n'
            line_offset += 1
            if col_offset is not None:
                scol, ecol = scol - col_offset, ecol - col_offset
            col_offset = 0  # next tokens should start at the current indent
        elif col_offset is not None:
            if not col_offset:
                # adjust column by starting column of next token
                col_offset = scol - last_indent
            scol, ecol = scol - col_offset, ecol - col_offset
            if ttype == tokenize.NEWLINE:
                col_offset = None
        yield TokenInfo(
            ttype, tstr, (slno, scol), (elno, ecol), line)

with open(sourcefile, 'r') as source, open(destination, 'w') as dest:
    generator = tokenize.generate_tokens(source.readline)
    dest.write(tokenize.untokenize(semicolon_to_newline(generator)))

обратите внимание, что я не удосужился исправить line value; это только информативно, данные, которые были прочитаны из файла, фактически не используются при un-tokenizing.

демо:

>>> from io import StringIO
>>> source = StringIO('''\
... def main():
...     a = "a;b"; return a
... ''')
>>> generator = tokenize.generate_tokens(source.readline)
>>> result = tokenize.untokenize(semicolon_to_newline(generator))
>>> print(result)
def main():
    a = "a;b"
    return a

и немного сложнее:

>>> source = StringIO('''\
... class Foo(object):
...     def bar(self):
...         a = 10; b = 11; c = 12
...         if self.spam:
...             x = 12; return x
...         x = 15; return y
...
...     def baz(self):
...         return self.bar;
...         # note, nothing after the semicolon
... ''')
>>> generator = tokenize.generate_tokens(source.readline)
>>> result = tokenize.untokenize(semicolon_to_newline(generator))
>>> print(result)
class Foo(object):
    def bar(self):
        a = 10
        b = 11
        c = 12
        if self.spam:
            x = 12
            return x
        x = 15
        return y

    def baz(self):
        return self.bar

        # note, nothing after the semicolon

>>> print(result.replace(' ', '.'))
class.Foo(object):
....def.bar(self):
........a.=.10
........b.=.11
........c.=.12
........if.self.spam:
............x.=.12
............return.x
........x.=.15
........return.y

....def.baz(self):
........return.self.bar
........
........#.note,.nothing.after.the.semicolon

* версия Python 3 tokenize выдает более информативное TokenInfo кортежей, которые есть лишний exact_type атрибут, который можно использовать вместо текстового соответствия:tok.exact_type == tokenize.SEMI. Однако я сохранил вышеуказанную совместимость с Python 2 и 3.


вот решение для pyparsing - см. комментарии в коде ниже:

from pyparsing import Literal, restOfLine, quotedString, pythonStyleComment, line

SEMI = Literal(';')
patt = SEMI + restOfLine
patt.ignore(quotedString)
patt.ignore(pythonStyleComment)

def split_at(s, locs):
    """
    break up s into pieces, given list of break locations
    """
    current = 0
    ret = []
    for loc in locs:
        ret.append(s[current:loc].lstrip())
        current = loc+1
    ret.append(s[current:].lstrip())
    return ret

def split_on_semicolon(s,l,tokens):
    """
    parse time callback, when finding first unquoted ';' on a line
    """
    current_line = line(l,s)
    line_body = current_line.lstrip()
    indent = current_line.index(line_body)
    indent = current_line[:indent]

    # may be more than one ';' on this line, find them all
    # (the second token contains everything after the ';')
    remainder = tokens[1]
    if remainder.strip():
        all_semis = [s for _,s,_ in SEMI.scanString(remainder)]

        # break line into pieces
        pieces = split_at(remainder, all_semis)

        # rejoin pieces, with leading indents
        return '\n'+'\n'.join(indent+piece for piece in pieces)
    else:
        return ''

patt.addParseAction(split_on_semicolon)

sample = """
def main():
    this_semi_does_nothing();
    neither_does_this_but_there_are_spaces_afterward();   
    a = "a;b"; return a # this is a comment; it has a semicolon!

def b():
    if False:
        z=1000;b("; in quotes");  c=200;return z
    return ';'

class Foo(object):
    def bar(self):
        '''a docstring; with a semicolon'''
        a = 10; b = 11; c = 12

        # this comment; has several; semicolons
        if self.spam:
            x = 12; return x # so; does; this; one
        x = 15;;; y += x; return y

    def baz(self):
        return self.bar
"""
print(patt.transformString(sample))

выдает:

def main():
    this_semi_does_nothing()
    neither_does_this_but_there_are_spaces_afterward()
    a = "a;b"
    return a # this is a comment; it has a semicolon!

def b():
    if False:
        z=1000
        b("; in quotes")
        c=200
        return z
    return ';'

class Foo(object):
    def bar(self):
        '''a docstring; with a semicolon'''
        a = 10
        b = 11
        c = 12

        # this comment; has several; semicolons
        if self.spam:
            x = 12
            return x # so; does; this; one
        x = 15
        y += x


        return y

    def baz(self):
        return self.bar