0
\$\begingroup\$

I've written my custom implementation of an f"" / .format(...). I started the project thinking it was going to be longer than three lines.

from typing import List, Union, AnyStr

def format_string(string: str, variables: List[Union[str, int, float, bool, complex]]) -> str:
    """
    Formats the passed string with the passed list of variables

    >>> format_string("Hello, [*]", ["Ben"])
    Hello, Ben

    :param string -> str: String to be formatted
    :param variables -> List[Union[str, int, float, bool, complex]]: List of variables to format into string

    :return str: Formatted string
    """
    for index, value in enumerate(variables):
        string = string.replace("[*]", str(value), 1)
    return string

My main question is if it's possible to make this a one-liner. It absolutely infuriates me that I have to use three. I spent a long time trying a mixture of * and ''.join to no avail. The code works, I would just like to shorten it up to one line. Of course, any and all feedback is appreciated and considered.

A secondary question is the method header. To represent variables, I have a List that can contain any types of variables. How would I go about representing this instead of having to list each type separately?

\$\endgroup\$
0

1 Answer 1

3
\$\begingroup\$

For the second point, since it's easier, typing has an Any:

from typing import List, Any
. . ., variables: List[Any], . . .

For the first, you're just doing a reduction over variables:

from typing import List, Any
from functools import reduce


def format_string(string: str, variables: List[Any]) -> str:
    return reduce(lambda s, val: s.replace("[*]", str(val), 1), variables, string)

Although really, in a real use case, I'd still split this over three lines for clarity:

def format_string(string: str, variables: List[Any]) -> str:
    return reduce(lambda s, val: s.replace("[*]", str(val), 1),
                  variables, 
                  string)

And honestly, I might just make that function var-arg instead of grouping things in a list to make it consistent with other format functions:

def format_string(string: str, *variables: Any) -> str:
    return reduce(lambda s, val: s.replace("[*]", str(val), 1), variables, string)

>>> format_string("[*] Hello [*]", 1, 2)
'1 Hello 2'

Note that when annotating a a var-arg parameter, you annotate the type of each element and ignore the type of the wrapping container (a tuple iirc). That means it's *variables: Any, not *variables: Tuple[... Any].


Of course though, whether or not this is better is a matter of taste, but this is the ideal use-case for reduce. Whenever you want to constantly reassign one thing in a simple loop, reduce is likely a good tool to look at.

\$\endgroup\$
1
  • \$\begingroup\$ What a beautiful solution, I knew there was a way to one line this. Thanks! \$\endgroup\$
    – Linny
    Commented Nov 18, 2019 at 0:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.