python – Plural String Formatting

python – Plural String Formatting

Check out the inflect package. It will pluralize things, as well as do a whole host of other linguistic trickery. There are too many situations to special-case these yourself!

From the docs at the link above:

import inflect
p = inflect.engine()

# UNCONDITIONALLY FORM THE PLURAL
print(The plural of , word,  is , p.plural(word))

# CONDITIONALLY FORM THE PLURAL
print(I saw, cat_count, p.plural(cat,cat_count))

For your specific example:

{print(str(count) +   + p.pluralize(string, count)) for string, count in data.items() }

When you have only two forms, and just need a quick and dirty fix, try s[:i^1]:

for i in range(5):
    print(f{i} bottle{s[:i^1]} of beer.)

Output:

0 bottles of beer.
1 bottle of beer.
2 bottles of beer.
3 bottles of beer.
4 bottles of beer.

Explanation:

^ is the bitwise operator XOR (exclusive disjunction).

  • When i is zero, i ^ 1 evaluates to 1. s[:1] gives s.
  • When i is one, i ^ 1 evaluates to 0. s[:0] gives the empty string.
  • When i is more than one, i ^ 1 evaluates to an integer greater than 1 (starting with 3, 2, 5, 4, 7, 6, 9, 8…, see https://oeis.org/A004442 for more information). Python doesnt mind and happily returns as many characters of s as it can, which is s.

My 1 cent 😉

Bonus. For 2-character plural forms (e.g., bush/bushes), use es[:2*i^2]. More generally, for an n-character plural form, replace 2 by n in the previous expression.

Opposite. In the comments, user @gccallie suggests s[i^1:] to add an s to verbs in the third person singular:

for i in range(5):
    print(f{i} bottle{s[:i^1]} of beer lie{s[i^1:]} on the wall.)

Output:

0 bottles of beer lie on the wall.
1 bottle of beer lies on the wall.
2 bottles of beer lie on the wall.
3 bottles of beer lie on the wall.
4 bottles of beer lie on the wall.

Python interprets the first form as [:stop], and the second one as [start:].

Edit. A previous, one-character longer version of the original trick used != instead of ^.

python – Plural String Formatting

Using custom formatter:

import string

class PluralFormatter(string.Formatter):
    def get_value(self, key, args, kwargs):
        if isinstance(key, int):
            return args[key]
        if key in kwargs:
            return kwargs[key]
        if ( in key and key.endswith()):
            key, rest = key.split((, 1)
            value = kwargs[key]
            suffix = rest.rstrip()).split(,)
            if len(suffix) == 1:
                suffix.insert(0, )
            return suffix[0] if value <= 1 else suffix[1]
        else:
            raise KeyError(key)

data = {tree: 1, bush: 2, flower: 3, cactus: 0}
formatter = PluralFormatter()
fmt = {tree} tree{tree(s)}, {bush} bush{bush(es)}, {flower} flower{flower(s)}, {cactus} cact{cactus(i,us)}
print(formatter.format(fmt, **data))

Output:

1 tree, 2 bushes, 3 flowers, 0 cacti

UPDATE

If youre using Python 3.2+ (str.format_map was added), you can use the idea of OP (see comment) that use customized dict.

class PluralDict(dict):
    def __missing__(self, key):
        if ( in key and key.endswith()):
            key, rest = key.split((, 1)
            value = super().__getitem__(key)
            suffix = rest.rstrip()).split(,)
            if len(suffix) == 1:
                suffix.insert(0, )
            return suffix[0] if value <= 1 else suffix[1]
        raise KeyError(key)

data = PluralDict({tree: 1, bush: 2, flower: 3, cactus: 0})
fmt = {tree} tree{tree(s)}, {bush} bush{bush(es)}, {flower} flower{flower(s)}, {cactus} cact{cactus(i,us)}
print(fmt.format_map(data))

Output: same as above.

Leave a Reply

Your email address will not be published. Required fields are marked *