Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explore if the data model method __format__ could be of use #127

Closed
Batalex opened this issue Jan 13, 2024 · 4 comments
Closed

Explore if the data model method __format__ could be of use #127

Batalex opened this issue Jan 13, 2024 · 4 comments
Labels
Priority: Medium Size: Medium Status: Wont Fix This will not be worked on Type: New Feature New feature or request

Comments

@Batalex
Copy link

Batalex commented Jan 13, 2024

Hey there,

I just thought of something that could complement #114.
You know how you can use https://docs.python.org/3/library/string.html#formatspec to change an object representation inside an f-string or a string template like this:

date = datetime.time.now()
number = 0.1234567

f"{date:%Y-%m-%d}"
#  '2024-01-13'

f"{number:.2f}"
#  '0.12'

This behavior is defined by the __format__ dunder method. Maybe this could be used in sciform to change a few formatting options on the fly, such as the decimal precision or the unit.

@jagerber48
Copy link
Owner

@Batalex thanks for the suggestion.

__format__ is already used with the sciform FSML to format SciNum objects. This makes the proposed FormattedValue feel more like the SciNum object. A long time ago I considered attaching Formatters to SciNum objects. Then there's this kind of hierarchy of options, there are the global options, then there are the options attached to the object, then there are the options selected at format time (in this case by using a FSML and __format__).

At the time I found handling these multiple layers of options complicated. That said, I still don't think I've wrapped my head around the value of "late/lazy" formatting. I need to code up and test an implementation of #114 and then I think I'll have a better idea about this.

@jagerber48
Copy link
Owner

@Batalex Actually, reading this again, I would say that this is already exactly covered by the combination of configuring global options + using the FSML on the SciNum object like in these examples. In those examples this is done using GlobalDefaultsContext, but it could be done instead using set_global_defaults:

from sciform import SciNum, set_global_defaults
set_global_defaults(
    ndigits=6,
    upper_separator=" ",
    decimal_separator=",",
    lower_separator="_",
)

snum = SciNum(12345.54321)

print(f"{snum}")
# 12 345,5
print(f"{snum:.6f}")
# 12 345,543_210
print(f"{snum:+!2r}")
# +12e+03

What is missing is (1) the ability to use a non-global formatter to format SciNum objects and (2) to use the FSML to modify the formatter behavior on-the-fly. If this behavior is desired, I imagine it would be accomplished by optionally passing a formatter in during SciNum construction and then using that for formatting instead of the global formatter. If no formatter is passed in then during SciNum.__init__() a new blank formatter would be constructed using Formatter(). This has not options populated so it will always be populated with global defaults at format time.

The reason I stopped pursuing the above approach is it's just a bit confusing to mentally keep track of 3 sets of formatting rules and their override behaviors: the global rules, the local formatter rules, and the FSML rules. I feel like using the global formatting + FSML rules to control SciNum formatting should suffice. If users need SciNum-specific formatters they can open an issue and we can discuss further.

@Batalex what do you think?

@jagerber48
Copy link
Owner

The FormattedNumber exists. On the latest version of main (not relased yet) it stores the value, uncertainty, and options that were used to generate it. We can define __format__:


class FormattedNumber(str):
    """
    Representation of a formatted value of value/uncertainty pair.

    ...
    """

    __slots__ = {
        "value": "The value that was formatted to generate the "
        ":class:`FormattedNumber`.",
        "uncertainty": "The uncertainty that was formatted to generate the "
        ":class:`FormattedNumber`.",
        "populated_options": "Record of the :class:`PopulatedOptions` used to "
        "generate the :class:`FormattedNumber`.",
    }

    ...

    def __format__(self: FormattedNumber, fmt_spec: str) -> str:
        options_dict = self.populated_options.as_dict()
        fmt_spec_options_dict = format_options_from_fmt_spec(fmt_spec).as_dict()
        options_dict.update(fmt_spec_options_dict)
        new_options = InputOptions(**options_dict)
        return format_from_options(self.value, self.uncertainty, new_options)

This allows us to do

from sciform import Formatter

formatter = Formatter(
    exp_mode="engineering",
    ndigits=2,
)

formatted = formatter("123456 +/- 72356")
print(formatted)
# (123 ± 72)e+03

print(f"{formatted:!4r}")
# (123.46 ± 72.36)e+03

I think this is basically what you're suggesting. I'm trying to decide how I feel about it. It is really starting to blur the lines between the SciNum class, which is a class that basically holds a value/uncertainty to be formatted using the FSML and the FormattedNumber class which is the result of performing formatting via Formatter or FSML formatting of a SciNum.

I'm trying to wrap my head around what workflows are going to be useful for users. In my mind I imagine something like:

  • do some math/science and get some outputs
  • Pass them into sciform
  • Get formatted strings
  • Possibly cast to latex/html
  • display

It's hard for me to imagine users wanting repeated rounds of formatting of the same results, but my imagination is limited.

My intuition is telling me there is probably an elegant way to combine the SciNum and FormattedNumber class in a way that allows this idea to work and which also feels useful?

The thing I'm nervous about is that it can get confusing when there are a lot of different options running around. E.g. the global options, the option that were used to generate a FormattedNumber and also the new options passed in as the format specification. I worry it will be hard to keep track of what is expected.

@jagerber48
Copy link
Owner

I'm going to close this for now. I think global options and SciNum cover the main thing that is requested here.

If there is interest in having some kind of round tripping behavior between SciNum and FormattedNumber then I can look into this further. Feel free to re-open this issue if you have more you'd like to discuss.

@jagerber48 jagerber48 added Status: Wont Fix This will not be worked on and removed Status: To Investigate labels Mar 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Priority: Medium Size: Medium Status: Wont Fix This will not be worked on Type: New Feature New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants