18

I'm looking to try and keep pluralisation of existing strings as simple as possible, and was wondering if it was possible to get str.format() to interpret a default value when looking for kwargs. Here's an example:

string = "{number_of_sheep} sheep {has} run away"
dict_compiled_somewhere_else = {'number_of_sheep' : 4, 'has' : 'have'}

string.format(**dict_compiled_somewhere_else)
# gives "4 sheep have run away"

other_dict = {'number_of_sheep' : 1}
string.format(**other_dict)
# gives a key error: u'has'
# What I'd like is for format to somehow default to the key, or perhaps have some way of defining the 'default' value for the 'has' key 
# I'd have liked: "1 sheep has run away"

Cheers

dreftymac
  • 31,404
  • 26
  • 119
  • 182
Patrick
  • 2,769
  • 2
  • 25
  • 29

3 Answers3

30

As PEP 3101, string.format(**other_dict) is not available.

If the index or keyword refers to an item that does not exist, then an IndexError/KeyError should be raised.

A hint for solving the problem is in Customizing Formatters, PEP 3101. That uses string.Formatter.

I improve the example in PEP 3101:

from string import Formatter

class UnseenFormatter(Formatter):
    def get_value(self, key, args, kwds):
        if isinstance(key, str):
            try:
                return kwds[key]
            except KeyError:
                return key
        else:
            return Formatter.get_value(key, args, kwds)

string = "{number_of_sheep} sheep {has} run away"
other_dict = {'number_of_sheep' : 1}

fmt = UnseenFormatter()
print fmt.format(string, **other_dict)

The output is

1 sheep has run away
notpeter
  • 1,046
  • 11
  • 16
emesday
  • 6,078
  • 3
  • 29
  • 46
  • 1
    I think this is the clearest method, other than those shown in [yesterday's thread](https://stackoverflow.com/questions/21872366/plural-string-formatting). In my comment I made a few alterations to make it a bit more DRY – Patrick May 02 '14 at 14:38
  • 2
    PS a link to PEP 3101 would have been nice, and warranted a +1 ;-) – Patrick May 02 '14 at 14:39
  • 1
    `return` is missing before `Formatter.get_value(key, args, kwds)`, positional arguments don't work without that. Also, I don't think you need to implement `__init__` at all, since it only calls parent's `__init__`. – previous_developer Mar 22 '17 at 09:24
  • may i suggest checking isinstance(key, basestring) so that this still works if the key is a unicode – Derwent Sep 04 '17 at 06:40
2

Can't see the advantage. You have to check the plurality anyway, cause normally you don't have a fixed number of sheep

class PluralVerb(object):
    EXCEPTIONS = {'have': 'has'}
    def __init__(self, plural):
        self.plural = plural

    def __format__(self, verb):
        if self.plural:
            return verb
        if verb in self.EXCEPTIONS:
            return self.EXCEPTIONS[verb]
        return verb+'s'

number_of_sheep = 4
print "{number_of_sheep} sheep {pl:run} away".format(number_of_sheep=number_of_sheep, pl=PluralVerb(number_of_sheep!=1))
print "{number_of_sheep} sheep {pl:have} run away".format(number_of_sheep=number_of_sheep, pl=PluralVerb(number_of_sheep!=1))
Daniel
  • 42,087
  • 4
  • 55
  • 81
  • This is nice and DRY (singular/plurals are defined beforehand), but personally I'd prefer to define the singular in my format string, and have the plural substituted in if need be. Shows another method to mskimm's – Patrick May 02 '14 at 14:27
1

Based on mskimm and Daniel answer, here's a solution that predefines the singular/plural words (whilst correcting a couple of typos in mskimm's).

The only downside is the hard coding of the keyword arg number (so I can no longer use number_of_sheep)

from string import Formatter

class Plural(Formatter):
    PLURALS = {'has' : 'have'}
    def __init__(self):
        super(Plural, self).__init__()

    def get_value(self, key, args, kwds):
        if isinstance(key, str):
            try:
                return kwds[key]
            except KeyError:
                if kwds.get('number', 1) == 1:
                    return key
                return self.PLURALS[key]
        return super(Plural, self).get_value(key, args, kwds)

string = "{number} sheep {has} run away"
fmt = Plural()
print fmt.format(string, **{'number' : 1})
print fmt.format(string, **{'number' : 2})
Patrick
  • 2,769
  • 2
  • 25
  • 29
  • 1
    A nice solution. I thought that the question was about `safe` substitution not `plurals`. – emesday May 03 '14 at 00:01
  • I guess you're right, I worded it to refer to safe substitution, but my case was for the use of plurals. Still, your answer got the check mark :) – Patrick May 03 '14 at 09:55