[ANN] PyFormattedStrings.jl: Python-like formatted interpolation

Several months ago I asked here (Formatted string interpolation) about string interpolation with formatting options, and didn’t find any solutions. So I developed a small package for this, which uses Python f-string syntax, e.g. f"result = {1.23^5:.2f}" gives "result = 2.82". I’ve been using this package myself for quite a while already and didn’t have any issues - so decided to make it available: https://github.com/aplavin/FormatInterp.jl. I really don’t get how one can live (and generate strings) without such a thing :slight_smile:
Currently it has a single string macro, which parses a given string and generates an expression which uses Formatting.jl to format individual values, and concatenates them afterwards.

Bug reports are welcome! As for adding something new - I have no plans on that, but am open to PRs.

14 Likes

A README.md file would be nice.

3 Likes

I’m sure people with a python background will appreciate your macro as it is, but would you consider extending it to also allow a more julian syntax for the rest of us? Something like this:

f"result = $(1.23^5:.2f)"

For me at least that would definitely be worth typing an extra character.

2 Likes

I personally hate using $ as the interpolation character, because it complicates generating latex strings (which I do quite often) more than { does. However, I’m open to PRs in this regard. Changing { to some other single character is almost trivial, but replacing it with a pair like $( requires some care.

3 Likes

A trivial comment, but the package name doesn’t really conform to the package naming guidelines (see especially #4)

Why not FormattedInterpolation.jl or FStringInterpolation.jl?

3 Likes

A related package is StrLiterals.jl. For string-related issues, I recommend to watch JuliaString and @ScottPJones.

1 Like

Oh, indeed. I didn’t notice that section in the docs.

1 Like

Yes, I know about StrLiterals, but it’s not maintained at all - last commit and activity was 1.5 years ago, and now this package doesn’t work on reasonably recent Julia. Also StrLiterals doesn’t have python-like string syntax as I understand (cannot try because it doesn’t work). So for me it was easier to just roll out my own package to parse the input string and pass its parts to Formatting.jl than to fix and modify StrLiterals.

StrLiterals works for me on julia 1.2 (passes all tests) and StrFormat supports python-like string syntax. I don’t think these packages are unmaintained. What is not working for you?

Ok, I stand corrected - the package itself works. And even formatting of expressions works after installing StrFormat.jl. I confused StrLiterals and https://github.com/JuliaString/StringLiterals.jl thinking they are the same package - and it is the latter one that does not work.
So, taking this into account, StrLiterals + StrFormat have only one disadvantage I can see - what these packages call “python-like” syntax is not the syntax python or any other language actually uses, and this is done for no apparent reason.

So for me it was easier to just roll out my own package…

That is totally fine, and I did not want to disparage your work. The lack of format modifiers in Base julia string interpolation is an eternal gripe of mine, and I thought that you might not have been aware of the JuliaStrings “shadow ecosystem” for strings. This group of packages seems to be less well known than deserved.

Even when rolling my own, it is always nice to have working code and API design for related functionality to learn from.

StringLiterals.jl was written back for Julia v0.4
StrLiterals.jl is part of the whole set of string related packages (you can just do using Strs to get pretty much everything), and it most definitely is maintained for Julia v1.0 and above

I am not a python programmer, and most of the python-like formatting came from the Format.jl package I used under the hood.
I documented in the README that I wanted to have it allow a truly python like syntax, however I only use the C printf style myself.
If you wish to contribute to make it actually work more closely to the Python syntax (where the argument is part of the string, not positional), I would greatly appreciate it.

2 Likes

Yes, now I see. It was just really confusing for me that there are two packages with almost exactly the same name (StrLiterals vs StringLiterals), and I didn’t notice they are in fact different.

I also use Formatting.jl (almost the same as Format.jl, as I understand), my package just parses the string and generates a call to Formatting.jl for each interpolated argument.

I have recently become familiar with registering in General, and decided to finally publish this formatting package in that registry. Following suggestions in this thread, I renamed the package and added a brief README. So, the package is now called PyFormattedStrings and registered: JuliaHub.

It uses the built-in Printf stdlib under the hood: I switched to that from Formatting.jl. Arbitrary Julia syntax is supported within interpolated expressions, and formatting options syntax mirrors Python as closely as possible without reimplementing formatting from scratch. See test/runtests.jl · master · Alexander Plavin / PyFormattedStrings.jl · GitLab for some more examples in addition to the README. Generally, there should be no additional runtime overhead compared to a manually-written corresponding @printf call.
As far as I’m aware, there is no other package with the same interpolation capabilities and syntax yet.

4 Likes

Have you looked at the Strs package yet?
That has supported both Python-like and C-like formatted interpolated strings, for a number of years.
As far as I am aware, it was the first to support formatted interpolated strings in Julia.

2 Likes

I agree that Strs.jl brought string interpolation to Julia earlier/first and never argued with this. Btw, my package is also quite old: it exists for about two years, and was published in this very thread more than 1.5 years ago.

Yes, I did: you pointed me to that package earlier in this thread (:

Originally, I wrote PyFormattedStrings because I didn’t find any reasonable string interpolation in Julia at all - and I asked in this forum first, see link in the first post.
Actually, I would probably just overlook Strs.jl if noone directly pointed towards it. Its README suggests that the package is, first and foremost, a kind of “alternative Strings” implementation - definitely not something I’m looking for.

In the past two years I mostly made small fixes/improvements to PyFormattedStrings that I stumbled across. Recently, I checked that there is still no other package with the same syntax, written a README, and finally registered PyFormattedStrings in General.

As for comparison to Strs.jl at the current state of both packages, I can immediately point the following differences - maybe there are others as well:

  • Obvious: syntax is different. I definitely like f"{123:5d}" (Python or PyFormattedStrings) more than f"\{5d}(123)" (Strs.jl). Also, after multiple tries I couldn’t find how to generate literally the string "\{5d}(123)" in Strs.jl - that is, how to escape the control sequence. PyFormattedStrings follows the Python escaping rule: f"{{123:5d}}" == "{123:5d}".
  • PyFormattedStrings performance is better: my cursory testing with @btime shows that it is 2-10 times faster than Strs.jl. Loading time is also shorter.
  • Support of (some) formatting options is better in PyFormattedStrings: e.g., g and a float modifiers work; f"{123.45:.0f}" == "123", while Strs.jl gives "123.". Some others are probably better supported in Strs.jl, but I didn’t find any.
  • PyFormattedStrings doesn’t reimplement formatting and relies on the Printf stdlib. So, any bugfixes and improvements to the latter would benefit the former, and the package itself is very lightweight.
1 Like

When I replied, I had forgotten about the earlier comments from a few years ago :slight_smile:
Strs.jl is really more of a convenience package, pulling together a number of other things, like the alternate strings (faster, and with validated UTF-8 format strings as well as UTF-16 & UTF-32 among others), but also incorporating the formatted interpolation support I’d done earlier.

PyFormattedStrings.jl does look nice, I was responding to the last line of your comment:

As far as I’m aware, there is no other package with the same interpolation capabilities and syntax yet.

which sounded like you’d missed that using StrLiterals, StrFormat would provide the same capabilities (with some differences in syntax, as I wasn’t trying to recreate exactly a Python f-format string [which wasn’t even part of Python when I implemented this], but rather, something that allowed using keyword arguments and full Julia expressions in the interpolation). I was more going for having the formatting part be the same as Python instead (but depended on what Formatting.jl had implemented as far as parsing that), more to make it easier for people coming from Python, but not for direct copying of format strings.

I also prefer being able to have these in otherwise totally normal strings (i.e. without having to use special quoting syntax {{ and }} in the string, like Python).

I am interested in the performance gains you got by using the code in stdlib/Printf, but I’d rather change Format.jl to have that speedup, as well as write a formatting core that supports C style, Python style, and extra keywords for better format control. Right now, Formatting.jl (and Format.jl) have different code for different languages (and then there’s also the formatting code in Printf.jl, as well as the recent Fmt.jl.

1 Like

I’m all for using arbitrary expressions in interpolation as well - otherwise it’s closer to just “formatting” than proper “interpolation”. Of course, PyFormattedStrings.jl also support any julia syntax within strings.
Not completely sure what you mean by keyword arguments here, but I do see (from the docs) that Strs.jl is designed to be extensible.

“Normal strings” - that is, without the f-prefix? I personally don’t see the single-character prefix as any kind of inconvenience, just the opposite - it immediately tells the reader that there is some executable code within the string.

Well, I just convert the f-string to the corresponding printf format string, and then generate the same code as would be created by @sprintf:

# format_str - the resulting printf format string
format = Printf.Format(format_str)
# arguments - list of interpolated expressions, in their occurrence order
return :(Printf.format($format, $(esc.(arguments)...)))

Using Printf puts some limits on possible extensibility, but provides clear benefits in simplicity.