Allow for arbitrary string prefix of strings

We are working on a set of PEPs (spec, tutorial) here for tagstrings, which support custom prefixes - GitHub - jimbaker/tagstr: This repo contains an issue tracker, examples, and early work related to PEP 999: Tag Strings - and there’s a corresponding branch of CPython that @guido wrote that implements most of the functionality that is discussed - I am working in a branch · Issue #1 · jimbaker/tagstr · GitHub, which can be readily be updated. I should get back into gear on this work and just get it done :smile:.

First, tagstrings are a Pythonic version of JavaScript’s tagged template literals. So this is the way to think about it: tags = custom prefixes. In a nutshell, one can write tagstrings in a target DSL - html`, shell, sql, etc - and the interpolations are properly managed for that target (eg quoted). In particular, interpolations can be nested recursively; and the behavior is the same as with f-strings in that lexical scope is respected. (For example, this also opens up being able to work with numexpr, GitHub - pydata/numexpr: Fast numerical array expression evaluator for Python, NumPy, PyTables, pandas, bcolz and more, for example, which currently uses dynamic scope to resolve expressions.)

Examples:

# NOTE: tags are passed raw strings, so the grep works here
run(sh"find {path} -print | grep '\.py$'", shell=True)

or

table_name = 'lang'
name = 'C'
date = 1972

with sqlite3.connect(':memory:') as conn:
    cur = conn.cursor()
    cur.execute(*sql'create table {Identifier(table_name)} (name, first_appeared)')

Some additional things to know are that interpolations are “lambda wrapped” with a no-arg function; and the interface provided seems to be nicely Pythonic - it’s easy IMHO to write your own tag functions to work with your target DSL. Example for implementing the fl tag, a lazy version of f-strings, assuming some imports and definitions like Thunk (see tagstr/fl.py at main · jimbaker/tagstr · GitHub):

def just_like_f_string(*args: str | Thunk) -> str:
    return ''.join((format_value(arg) for arg in decode_raw(*args)))


@dataclass
class LazyFString:
    args: Sequence[str | Thunk]

    def __str__(self) -> str:
        return self.value

    @cached_property
    def value(self) -> str:
        return just_like_f_string(*self.args)


def fl(*args: str | Thunk) -> LazyFString:
    return LazyFString(args)