Formato de cadena plural

Dado un diccionario de int s, estoy tratando de formatear una cadena con cada número y una pluralización del elemento.

Ejemplo de entrada dict :

 data = {'tree': 1, 'bush': 2, 'flower': 3, 'cactus': 0} 

Salida de muestra:

 'My garden has 1 tree, 2 bushes, 3 flowers, and 0 cacti' 

Necesita trabajar con una cadena de formato arbitrario.

La mejor solución que he encontrado es una clase PluralItem para almacenar dos atributos, n (el valor original) y s (la cadena 's' si es plural, cadena vacía '' si no). Subclasificado por diferentes métodos de pluralización.

     class PluralItem(object): def __init__(self, num): self.n = num self._get_s() def _get_s(self): self.s = '' if self.n == 1 else 's' class PluralES(PluralItem): def _get_s(self): self.s = 's' if self.n == 1 else 'es' class PluralI(PluralItem): def _get_s(self): self.s = 'us' if self.n == 1 else 'i' 

    Luego haz un nuevo dict través de la comprensión y un mapeo de classes :

     classes = {'bush': PluralES, 'cactus': PluralI, None: PluralItem} plural_data = {key: classes.get(key, classes[None])(value) for key, value in data.items()} 

    Por último, la cadena de formato, y la implementación:

     formatter = 'My garden has {tree.n} tree{tree.s}, {bush.n} bush{bush.s}, {flower.n} flower{flower.s}, and {cactus.n} cact{cactus.s}' print(formatter.format(**plural_data)) 

    Salidas de lo siguiente:

     My garden has 1 tree, 2 bushes, 3 flowers, and 0 cacti 

    Para una necesidad tan común, indudablemente, vacilo en tirar la toalla con una solución tan complicada.

    ¿Hay alguna forma de formatear una cadena como esta utilizando el método de format incorporado y un código adicional mínimo? Pseudocódigo podría ser algo como:

     "{tree} tree{tree(s)}, {bush} bush{bush(es)}, {flower} flower{flower(s)}, {cactus} cact{cactus(i,us)}".format(data) 

    donde los paréntesis devuelven el contenido si el valor es plural, o si el contenido tiene una coma, significa plural / singular

    Usando un formateador personalizado :

     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)) 

    Salida:

     1 tree, 2 bushes, 3 flowers, 0 cacti 

    ACTUALIZAR

    Si está utilizando Python 3.2+ (se agregó str.format_map ), puede usar la idea de OP (ver comentario) que usa dict personalizado.

     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)) 

    Salida: igual que el anterior.

    Echa un vistazo al paquete de inflexión . Pluralizará las cosas, así como una gran cantidad de otros trucos lingüísticos. ¡Hay demasiadas situaciones especiales para este caso!

    De los documentos en el enlace de arriba:

     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)) 

    Para su ejemplo específico:

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

    Si ya usas Django, es fácil: pluralize es una función que.

    A menudo se utiliza en plantillas:

     You have {{ num_messages }} message{{ num_messages|pluralize }}. 

    Sin embargo, también puedes usarlo en tu código de Python:

     f'You have {num_messages} message{pluralize(num_messages)}.' 

    En Python2 esto leería:

     'You have {} message{}.'.format(num_messages, pluralize(num_messages)) 

    o:

     'You have %d message%s' % (num_messages, pluralize(num_messages)) 

    Django pluralize docs: https://docs.djangoproject.com/en/2.0/ref/templates/builtins/#pluralize

    Yo iria con algo asi

     class Pluralizer: def __init__(self, value): self.value = value def __format__(self, formatter): formatter = formatter.replace("N", str(self.value)) start, _, suffixes = formatter.partition("/") singular, _, plural = suffixes.rpartition("/") return "{}{}".format(start, singular if self.value == 1 else plural) "There are {:N thing/s} which are made of {:/a cactus/N cacti}".format(Pluralizer(10), Pluralizer(1)) #>>> 'There are 10 things which are made of a cactus' 

    El formato es always/singular/plural , cuyo singular (entonces plural ) es opcional.

    Asi que

     "xyz/foo/bar".format(Pluralizer(1)) == "xyzfoo" "xyz/foo/bar".format(Pluralizer(2)) == "xyzbar" "xyz/bar".format(Pluralizer(1)) == "xyz" "xyz/bar".format(Pluralizer(2)) == "xyzbar" "xyz".format(Pluralizer(1)) == "xyz" "xyz".format(Pluralizer(2)) == "xyz" 

    Entonces para tu ejemplo uno solo hace:

     data = {'tree': 1, 'bush': 2, 'flower': 3, 'cactus': 0} string = 'My garden has {tree:N tree/s}, {bush:N bush/es}, {flower:N flower/s}, and {cactus:N cact/us/i}' string.format_map({k: Pluralizer(v) for k, v in data.items()}) #>>> 'My garden has 1 tree, 2 bushes, 3 flowers, and 0 cacti' 

    Me inspiré en las respuestas anteriores, particularmente en @ Veedrac’s, para crear una utilidad de pluralidad:

    https://gist.github.com/elidchan/40baea13bb91193a326e3a8c4cbcaeb9

    caracteristicas:

    • Plantillas personalizables indexadas por números (por ejemplo, vea “vago” a continuación)
    • Números y soporte para fichas de plantilla de $ n
    • Formas singulares / plurales (por ejemplo, ‘cact / us / i’) y soporte para tokens de plantilla $ thing / $ things
    • Capacidad de artículo indefinido (inspirado en https://stackoverflow.com/a/20337527/4182210 ) y soporte para $ un token de plantilla
    • Concatenación de cuerdas izquierda / derecha
    • Partiales con cualquier subconjunto de números, formularios y plantillas.
    • Finalización parcial mediante call () o cadena de formato

    De la cadena de documentos:

     """ Usage: >>> from utils.verbiage import Plurality >>> f"We have {Plurality(0, 'g/oose/eese')}." 'We have 0 geese.' >>> f"We have {Plurality(1, 'g/oose/eese')}." 'We have 1 goose.' >>> f"We have {Plurality(2, 'g/oose/eese')}." 'We have 2 geese.' >>> oxen = Plurality('ox/en') >>> oxen.template_formatter '1=$n $thing;n=$n $things' >>> f"We have {oxen(0)}." 'We have 0 oxen.' >>> f"We have {oxen(1)}." 'We have 1 ox.' >>> f"We have {oxen(2)}." 'We have 2 oxen.' >>> cows = Plurality('/cow/kine', '0=no $things', '1=$a $thing') >>> cows.template_formatter '0=no $things;1=a $thing;n=$n $things' >>> f"We have {cows(0)}." 'We have no kine.' >>> f"We have {cows(1)}." 'We have a cow.' >>> f"We have {cows(2)}." 'We have 2 kine.' >>> 'We have {:0=no $things;0.5=half $a $thing}.'.format(Plurality(0, 'octop/us/odes')) 'We have no octopodes.' >>> 'We have {:octop/us/odes;0=no $things;0.5=half $a $thing}.'.format(Plurality(0.5)) 'We have half an octopus.' >>> 'We have {:4;octop/us/odes;0=no $things;0.5=half $a $thing}.'.format(Plurality()) 'We have 4 octopodes.' >>> data = {'herb': 1, 'bush': 2, 'flower': 3, 'cactus': 0} >>> s = "We have {herb:herb/s}, {bush:bush/es}, {flower:flower/s}, and {cactus:cact/us/i}." >>> s.format_map({k: Plurality(v) for k, v in data.items()}) 'We have 1 herb, 2 bushes, 3 flowers, and 0 cacti.' >>> vague = Plurality('0=no $things;1=$a $thing;2=a couple $things;n=some $things') >>> s.format_map({k: vague(v) for k, v in data.items()}) 'We have an herb, a couple bushes, some flowers, and no cacti.' """