Jab
Jab

Reputation: 27515

Making sentence/word plural based on value

I'm wondering if there is a preferred or at least more readable/pretty/pythonic way to make a sentence plural based on data being put into it.

Here's how I'm doing it now

ret = f'Done! {deleted} entr{"y was" if deleted == 1 else "ies were"} removed.'

I know this works, but it just doesn't sit well when I go and read it. I've thought of either making a function or using a dict-switch (which is something I kinda like) but maybe someone knows a better way.

Solutions I've tried:

using a dict

plural = {1:'y was'}
ret = f'Done! {deleted} entr{plural.get(deleted, "ies were")} removed.'

using a function

def plural(num: int):
    return 'y was' if num == 1 else 'ies were'

using boolean operators

ret = f'Done! {deleted} entr{deleted != 1 and "ies were" or "y was"} removed.'

Those are really the only other ways I can think of right now that makes sense, also the reason I want this is because I have multiple places where I'd need to make words plural. The issue is, I may want to use different wording in places and would like to keep from repeating myself with making word(s) plural for human-readable format.

Upvotes: 5

Views: 1743

Answers (2)

Jab
Jab

Reputation: 27515

While I found @sophros answer very knowledgeable and informative, I decided it was more than I needed. I decided to write my own custom class as I needed something cheap and easy and that I can reuse for different words/sentences. Feel free to use yourself if you like it!

class Plural:
    __slots__ = 'word', 'value', 'singular', 'plural', 'zero', 'ignore_negatives'

    def __init__(self, value: int, word: str = "", **kwargs):
        """
        Parameters
        ----------
        value : int
            The determining value
        word : str, optional
            The word to make plural. (defaults to "")
        singular : str, optional
            Appended to `word` if `value` == 1. (defaults to '')
        plural : str, optional
            Appended to `word` if `value` > 1. (defaults to 's')
        zero : str, optional
            Replaces `value` if `value` == 0. (defaults to 0)
        ignore_negatives : bool, optional
            This will raise ValueError if `value` is negative. (defaults to False)
            Set to True if you don't care about negative values.
        """

        self.value, self.word = value, word
        self.singular = kwargs.pop('singular', '')
        self.plural = kwargs.pop('plural', 's')
        self.zero = kwargs.pop('zero', 0)
        self.ignore_negatives = kwargs.pop('ignore_negatives', False)

    def __str__(self):
        v = self.value
        pluralizer = self.plural if abs(v) > 1 else self.singular

        if v < 0 and not self.ignore_negatives:
            raise ValueError

        return f"{v or self.zero} {self.word}{pluralizer}"

Test that it works

print(Plural(-2, singular="entry", plural="entries", ignore_negatives = True))
#-2 entries
print(Plural(-1, singular="entry", plural="entries", ignore_negatives = True))
#-1 entry
print(Plural(0, singular="entry", plural="entries"))
#0 entries
print(Plural(1, singular="entry", plural="entries"))
#1 entry
print(Plural(2, singular="entry", plural="entries"))
#2 entries

With Negative Value

print(Plural(-1, singular="entry", plural="entries"))
#Traceback (most recent call last):                                                                                            
#File "/home/main.py", line 53, in <module>                                                                                  
#    print(Plural(-1, singular="entry", plural="entries"))                                                                     
#  File "/home/main.py", line 43, in __str__                                                                                   
#    raise ValueError                                                                                                          
#ValueError

Other use cases

print(Plural(1, "entr", singular="y", plural="ies"))
#1 entry
print(Plural(2, "entr", singular="y", plural="ies"))
#2 entries
print(Plural(0, "value", zero="No"))
#No value
print(Plural(1, "value"))
#1 Value
print(Plural(2, "value"))
#2 Values

If you just want a quick and dirty fix

Either use one of my examples in the question or make a method like in my question but here's a tweaked version as @Ev. Kounis and @9769953 suggested in the comments (hope ya'll don't mind me putting ur suggestions in the answer)

def plural(num: int, word: str = "", single: str = "", mult: str = 's'):
    return f"{num} {(plural, singular)[abs(num) == 1]}"

Upvotes: 2

sophros
sophros

Reputation: 16700

Maybe in this single instance (if we are talking a few strings to be declinated) then such approaches are sufficient.

But in general case, localization (L10N) and internationalization (I12N) is best handled with suitable libraries.

In case of python we have a standard library gettext that can be used for this purpose. It is effectively a python API to GNU gettext which is a neat option for a start. There are supplementary resources:

  1. Step-by-step guide how to localize your python script.
  2. A more in-depth introduction to GNU gettext in the context if python.
  3. Or another one if the previous does not suit you.
  4. And finally, much more involved but comprehensive python internationalization guide.

In short, there is much more to L10N and I12N than plurals...

Upvotes: 2

Related Questions