Python: convert old style formatting pattern to new style formatting pattern

Question:

Is there some "built-in" way to convert old style formatting pattern strings to new style formatting. Or its better to just use good old regex for it?

For example, we have pattern '%(a)s - %(b)s' and want to convert to '{a} - {b}'

Asked By: Andrius

||

Answers:

You can use f-strings, just make sure you have python3.6+

mystring = f'{a} - {b}'
Answered By: MercifulSory

Did not found any already built int solutions for converting formatting. Here is how I implemented using regex.

import re    

ODD_REPEAT_PATTERN = r'((?<!{c}){c}({c}{c})*(?!{c}))'
EVEN_REPEAT_PATTERN = r'(?<!{c})({c}{c})+(?!{c})'

def __to_new_format(fmt: str, named=True):
    def to_named_fmt(fmt):
        pattern = rf'{odd_perc_pattern}((.*?))s'
        match = re.search(pattern, fmt)
        while match:
            # Only care about placeholder group here.
            __, __, placeholder = match.groups()
            fmt = fmt.replace(
                f'%({placeholder})s',
                f'{{{placeholder}}}'
            )
            match = re.search(pattern, fmt)
        return fmt

    def to_pos_fmt(fmt):
        even_perc_pattern = EVEN_REPEAT_PATTERN.format(c='%')
        pattern = rf'{even_perc_pattern}s'
        # When positional placeholder has even amount of percents, it
        # will be treated as not having enough arguments passed.
        if re.search(pattern, fmt):
            raise TypeError(
                'not all arguments converted during string formatting'
            )
        return fmt.replace('%s', '{}')

    odd_perc_pattern = ODD_REPEAT_PATTERN.format(c='%')
    # Escape `{` and `}`, because new formatting uses it.
    fmt = fmt.replace('{', '{{').replace('}', '}}')
    fmt = to_named_fmt(fmt) if named else to_pos_fmt(fmt)
    # If we find odd number of occurring percentage symbols, it means
    # those were not escaped and we can't finish conversion.
    if re.search(odd_perc_pattern, fmt):
        raise ValueError('incomplete format')
    return fmt.replace('%%', '%')


def to_new_named_format(fmt: str) -> str:
    """Convert old style named formatting to new style formatting.

    For example: '%(x)s - %%%(y)s' -> '{x} - %{y}'

    Args:
        fmt: old style formatting to convert.

    Returns:
        new style formatting.

    """
    return __to_new_format(fmt, named=True)


def to_new_pos_format(fmt: str) -> str:
    """Convert old style positional formatting to new style formatting.

    For example: '%s - %%%s' -> '{} - %{}'

    Args:
        fmt: old style formatting to convert.

    Returns:
        new style formatting.

    """
    return __to_new_format(fmt, named=False)
Answered By: Andrius

Here is a variant of @Andrius’s answer, adapted to handle specifiers other than s:

import re

ODD_REPEAT_PATTERN = r'((?<!{c}){c}({c}{c})*(?!{c}))'
EVEN_REPEAT_PATTERN = r'(?<!{c})({c}{c})+(?!{c})'
SPECIFIER_PATTERN = r'[^(]*[diouxXeEfFgGcs]'  # TODO: handle r

def __to_new_format(fmt: str, named=True):
    def to_named_fmt(fmt):
        pattern = rf'{odd_perc_pattern}((.*?))({SPECIFIER_PATTERN})'
        match = re.search(pattern, fmt)
        while match:
            # Only care about placeholder group here.
            __, __, placeholder, specifier = match.groups()
            fmt = fmt.replace(
                f'%({placeholder}){specifier}',
                f'{{{placeholder}:{specifier}}}'
            )
            match = re.search(pattern, fmt)
        return fmt

    def to_pos_fmt(fmt):
        even_perc_pattern = EVEN_REPEAT_PATTERN.format(c='%')
        pattern = rf'{even_perc_pattern}s'
        # When positional placeholder has even amount of percents, it
        # will be treated as not having enough arguments passed.
        if re.search(pattern, fmt):
            raise TypeError(
                'not all arguments converted during string formatting'
            )
        return re.sub(
                rf"%({SPECIFIER_PATTERN})",
                lambda sub_match: f'{{:{sub_match.group(1)}}}',
                fmt
            )

    odd_perc_pattern = ODD_REPEAT_PATTERN.format(c='%')
    # Escape `{` and `}`, because new formatting uses it.
    fmt = fmt.replace('{', '{{').replace('}', '}}')
    fmt = to_named_fmt(fmt) if named else to_pos_fmt(fmt)
    # If we find odd number of occurring percentage symbols, it means
    # those were not escaped and we can't finish conversion.
    if re.search(odd_perc_pattern, fmt):
        raise ValueError('incomplete format')
    return fmt.replace('%%', '%')

def to_new_named_format(fmt: str) -> str:
    """Convert old style named formatting to new style formatting.
    For example: '%(x)s - %%%(y)s' -> '{x} - %{y}'
    Args:
        fmt: old style formatting to convert.
    Returns:
        new style formatting.
    """
    return __to_new_format(fmt, named=True)

def to_new_pos_format(fmt: str) -> str:
    """Convert old style positional formatting to new style formatting.
    For example: '%s - %%%s' -> '{} - %{}'
    Args:
        fmt: old style formatting to convert.
    Returns:
        new style formatting.
    """
    return __to_new_format(fmt, named=False)
Answered By: joeln
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.