The __format__() method

The __format__() method is used by f-strings, the str.format() method, as well as the format() built-in function. All three of these interfaces are used to create presentable string versions of a given object.

The following are the two ways in which arguments will be presented to the __format__() method of a someobject object:

  • someobject.__format__(""): This happens when an application uses a string such as f"{someobject}", a function such as format(someobject), or a string formatting method such as "{0}".format(someobject). In these cases, there was no : in the format specification, so a default zero-length string is provided as the argument value. This should produce a default format.
  • someobject.__format__(spec): This happens when an application uses a string such as f"{someobject:spec}", a function such as format(someobject, spec), or something equivalent to the "{0:spec}".format(someobject) string method.

Note that an f-string f"{item!r}" with !r or a "{0!r}".format(item) format method with !r doesn't use the object's __format__() method. The portion after ! is a conversion specification.

A conversion specification of !r uses the repr() function, which is generally implemented by an object's __repr__() method. Similarly, a conversion specification of !s uses the str() function, which is implemented by the __str__() method. The !a conversion specification uses the ascii() function. The ascii() function generally depends on the __repr__() method to provide the underlying representation. 

With a specification of "", a sensible implementation is return str(self). This provides an obvious consistency between the various string representations of an object.

The format specification provided as the argument value to __format__() will be all the text after ":" in the original format string. When we write f"{value:06.4f}", 06.4f is the format specification that applies to the item value in the string to be formatted.

Section 2.4.3 of the Python Language Reference defines the formatted string (f-string) mini-language. Each format specification has the following syntax:

[[fill]align][sign][#][0][width][grouping_option][.precision][type]

We can parse these potentially complex standard specifications with a regular expression (RE), as shown in the following code snippet:

re.compile( 
    r"(?P<fill_align>.?[<>=^])?" 
    r"(?P<sign>[-+ ])?" 
    r"(?P<alt>#)?" 
    r"(?P<padding>0)?" 
    r"(?P<width>d*)" 
    r"(?P<grouping_option>,)?" 
    r"(?P<precision>.d*)?" 
    r"(?P<type>[bcdeEfFgGnosxX%])?") 

This RE will break the specification into eight groups. The first group will have both the fill and alignment fields from the original specification. We can use these groups to work out the formatting for the attributes of classes we've defined.

In some cases, this very general format specification mini-language might not apply to the classes that we've defined. This leads to a need to define a format specification mini-language and process it in the customized __format__() method.

As an example, here's a trivial language that uses the %r character to show us the rank and the %s character to show us the suit of an instance of the Card class. The %% character becomes % in the resulting string. All other characters are repeated literally.

We could extend our Card class with formatting, as shown in the following code snippet:

def __format__(self, format_spec: str) -> str:
if format_spec == "":
return str(self)
rs = (
format_spec.replace("%r", self.rank)
.replace("%s", self.suit)
.replace("%%", "%")
)
return rs

This definition checks for a format specification. If there's no specification, then the str() function is used. If a specification was provided, a series of replacements is done to fold rank, suit, and any % characters into the format specification, turning it into the output string.

This allows us to format cards as follows:

print( "Dealer Has {0:%r of %s}".format( hand.dealer_card) ) 

The format specification ("%r of %s") is passed to our __format__() method as the format parameter. Using this, we're able to provide a consistent interface for the presentation of the objects of the classes that we've defined.

Alternatively, we can define things as follows:

    default_format = "some specification" 
    def __str__(self) -> str: 
        return self.__format__(self.default_format) 
    def __format__(self, format_spec: str) -> str: 
        if format_spec == "":  
format_spec = self.default_format # process using format_spec.

This has the advantage of putting all string presentations into the __format__() method instead of spreading them between __format__() and __str__(). This does have a disadvantage, because we don't always need to implement __format__(), but we almost always need to implement __str__().

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset