Python title() with apostrophes

Question:

Is there a way to use .title() to get the correct output from a title with apostrophes? For example:

"john's school".title() --> "John'S School"

How would I get the correct title here, "John's School" ?

Asked By: David542

||

Answers:

If your titles do not contain several whitespace characters in a row (which would be collapsed), you can use string.capwords() instead:

>>> import string
>>> string.capwords("john's school")
"John's School"

EDIT: As Chris Morgan rightfully says below, you can alleviate the whitespace collapsing issue by specifying " " in the sep argument:

>>> string.capwords("john's    school", " ")
"John's    School"
Answered By: Frédéric Hamidi

I think that can be tricky with title()

Lets try out something different :

def titlize(s):
    b = []
    for temp in s.split(' '): b.append(temp.capitalize())
    return ' '.join(b)

titlize("john's school")

// You get : John's School

Hope that helps.. !!

Answered By: Yugal Jindle

This is difficult in the general case, because some single apostrophes are legitimately followed by an uppercase character, such as Irish names starting with “O'”. string.capwords() will work in many cases, but ignores anything in quotes. string.capwords(“john’s principal says,’no'”) will not return the result you may be expecting.

>>> capwords("John's School")
"John's School"
>>> capwords("john's principal says,'no'")
"John's Principal Says,'no'"
>>> capwords("John O'brien's School")
"John O'brien's School"

A more annoying issue is that title itself does not produce the proper results. For example, in American usage English, articles and prepositions are generally not capitalized in titles or headlines. (Chicago Manual of Style).

>>> capwords("John clears school of spiders")
'John Clears School Of Spiders'
>>> "John clears school of spiders".title()
'John Clears School Of Spiders'

You can easy_install the titlecase module that will be much more useful to you, and does what you like, without capwords’s issues. There are still many edge cases, of course, but you’ll get much further without worrying too much about a personally-written version.

>>> titlecase("John clears school of spiders")
'John Clears School of Spiders'
Answered By: Art Taylor

Although the other answers are helpful, and more concise, you may run into some problems with them. For example, if there are new lines or tabs in your string. Also, hyphenated words (whether with regular or non-breaking hyphens) may be a problem in some instances, as well as words that begin with apostrophes. However, using regular expressions (using a function for the regular expression replacement argument) you can solve these problems:

import re

def title_capitalize(match):
    text=match.group()
    i=0
    new_text=""
    capitalized=False
    while i<len(text):
        if text[i] not in {"’", "'"} and capitalized==False:
            new_text+=text[i].upper()
            capitalized=True
        else:
            new_text+=text[i].lower()
        i+=1
    return new_text

def title(the_string):
    return re.sub(r"[w'’‑-]+", title_capitalize, the_string)

s="here's an apostrophe es. this string has multiple         spacesnnewnnlinesnhyphenated words: and non-breaking   spaces, and a non‑breaking hyphen, as well as 'ords that begin with ’strophies; itteventhastttabs."
print(title(s))

Anyway, you can edit this to compensate for any further problems, such as backticks and what-have-you, if needed.

If you’re of the opinion that title casing should keep such as prepositions, conjunctions and articles lowercase unless they’re at the beginning or ending of the title, you can try such as this code (but there are a few ambiguous words that you’ll have to figure out by context, such as when):

import re

lowers={'this', 'upon', 'altogether', 'whereunto', 'across', 'between', 'and', 'if', 'as', 'over', 'above', 'afore', 'inside', 'like', 'besides', 'on', 'atop', 'about', 'toward', 'by', 'these', 'for', 'into', 'beforehand', 'unlike', 'until', 'in', 'aft', 'onto', 'to', 'vs', 'amid', 'towards', 'afterwards', 'notwithstanding', 'unto', 'while', 'next', 'including', 'thru', 'a', 'down', 'after', 'with', 'afterward', 'or', 'those', 'but', 'whereas', 'versus', 'without', 'off', 'among', 'because', 'some', 'against', 'before', 'around', 'of', 'under', 'that', 'except', 'at', 'beneath', 'out', 'amongst', 'the', 'from', 'per', 'mid', 'behind', 'along', 'outside', 'beyond', 'up', 'past', 'through', 'beside', 'below', 'during'}

def title_capitalize(match, use_lowers=True):
    text=match.group()
    lower=text.lower()
    if lower in lowers and use_lowers==True:
        return lower
    else:
        i=0
        new_text=""
        capitalized=False
        while i<len(text):
            if text[i] not in {"’", "'"} and capitalized==False:
                new_text+=text[i].upper()
                capitalized=True
            else:
                new_text+=text[i].lower()
            i+=1
        return new_text

def title(the_string):
    first=re.sub(r"[w'’‑-]+", title_capitalize, the_string)
    return re.sub(r"(^[w'’‑-]+)|([w'’‑-]+$)", lambda match : title_capitalize(match, use_lowers=False), first)
Answered By: Brōtsyorfuzthrāx

IMHO, best answer is @Frédéric’s one. But if you already have your string separated to words, and you know how string.capwords is implemeted, then you can avoid unneeded joining step:

def capwords(s, sep=None):
    return (sep or ' ').join(
        x.capitalize() for x in s.split(sep)
    )

As a result, you can just do this:

# here my_words == ['word1', 'word2', ...]
s = ' '.join(word.capitalize() for word in my_words)
Answered By: MarSoft

If you have to cater for dashes then use:

import string
" ".join(
    string.capwords(word, sep="-")
    for word in string.capwords(
        "john's school at bel-red"
    ).split()
)
# "John's School At Bel-Red"
Answered By: Rabih Kodeih
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.