When to Use Python Object-Oriented Programming

There is a very important special method on the object class that we can take advantage of to represent our characters.

This method, called __str__ (two underscores at each end, like __init__), is used in string-manipulation functions such as print and the str constructor to convert any class to a string.

The default implementation does some boring stuff, such as printing the name of the module and class, and its address in memory.

But if we override it, we can make it print whatever we like.

For our implementation, we could make it prefix characters with special characters to represent whether they are bold, italic, or underlined.

So, we will create a class to represent a character, and here it is:class Character: def __init__(self, character, bold=False, italic=False, underline=False): assert len(character) == 1 self.

character = character self.

bold = bold self.

italic = italic self.

underline = underline def __str__(self): bold = "*" if self.

bold else '' italic = "/" if self.

italic else '' underline = "_" if self.

underline else '' return bold + italic + underline + self.

characterThis class allows us to create characters and prefix them with a special character when the str() function is applied to them.

Nothing too exciting there.

We only have to make a few minor modifications to the Document and Cursor classes to work with this class.

In the Document class, we add these two lines at the beginning of the insert method, as follows:def insert(self, character): if not hasattr(character, 'character'): character = Character(character)This is a rather strange bit of code.

Its basic purpose is to check whether the character being passed in is a Character or a str.

If it is a string, it is wrapped in a Character class so all objects in the list are Character objects.

However, it is entirely possible that someone using our code would want to use a class that is neither a Character nor a string, using duck typing.

If the object has a character attribute, we assume it is a Character-like object.

But if it does not, we assume it is a str-like object and wrap it in Character.

This helps the program take advantage of duck typing as well as polymorphism; as long as an object has a character attribute, it can be used in the Document class.

This generic check could be very useful.

For example, if we wanted to make a programmer’s editor with syntax highlighting, we’d need extra data on the character, such as what type of syntax token the character belongs to.

Note that, if we are doing a lot of this kind of comparison, it’s probably better to implement Character as an abstract base class with an appropriate __subclasshook__.

In addition, we need to modify the string property on Document to accept the new Charactervalues.

All we need to do is call str() on each character before we join it, as demonstrated in the following:@property def string(self): return "".

join((str(c) for c in self.

characters))This code uses a generator expression.

It’s a shortcut to perform a specific action on all the objects in a sequence.

Finally, we also need to check Character.

character, instead of just the string character we were storing before, in the home and end functions when we’re looking to see whether it matches a newline character, as demonstrated in the following:def home(self): while self.

document.

characters[ self.

position-1].

character != '.': self.

position -= 1 if self.

position == 0: # Got to beginning of file before newline break def end(self): while self.

position < len( self.

document.

characters) and self.

document.

characters[ self.

position ].

character != '.': self.

position += 1This completes the formatting of characters.

We can test it to see that it works as follows:>>> d = Document()>>> d.

insert('h')>>> d.

insert('e')>>> d.

insert(Character('l', bold=True))>>> d.

insert(Character('l', bold=True))>>> d.

insert('o')>>> d.

insert('.')>>> d.

insert(Character('w', italic=True))>>> d.

insert(Character('o', italic=True))>>> d.

insert(Character('r', underline=True))>>> d.

insert('l')>>> d.

insert('d')>>> print(d.

string)he*l*lo/w/o_rld>>> d.

cursor.

home()>>> d.

delete()>>> d.

insert('W')>>> print(d.

string)he*l*loW/o_rld>>> d.

characters[0].

underline = True>>> print(d.

string)_he*l*loW/o_rldAs expected, whenever we print the string, each bold character is preceded by a * character, each italicized character by a / character, and each underlined character by a _ character.

All our functions seem to work, and we can modify characters in the list after the fact.

We have a working rich text document object that could be plugged into a proper graphical user interface and hooked up with a keyboard for input and a screen for output.

Naturally, we’d want to display real bold, italic, and underlined fonts in a UI, instead of using our __str__ method, but it was sufficient for the basic testing we demanded of it.

.. More details

Leave a Reply