Class 8: Strings plus objects

Objectives for today


We used several functions, like len, that take a string as a parameter. And written some as well. That is we (really the Python development team) have defined a function and then invoked it with different arguments. These can be different values, or in the case of the len function, even parameters of different types. For example len can be used with other sequences, e.g.

>>> len(range(5))

An alternate approach is to invoke different functions on a specific value. Specifically we invoke a method on an object. A method then is a function specific to one type of object, i.e. specific to strings or integers.

Methods are invoked or called with the object.method syntax. For example:

>>> s = "Hi CS150"
>>> s.lower()
'hi cs150'
>>> s.upper()
'HI CS150'

This approach to organizing our code is called “object-oriented programming” (OOP), because the data and the functions are contained within/associated with objects. And for the purposes of this class all objects of a specific type, e.g. all string objects, have the same set of methods.

Methods are very similar to the functions we have been working with: they can accept additional parameters (in addition to the object on which they were invoked, also termed the “receiver”) and you can obtain their docstrings with help, e.g.

>>> help(s.lower)
>>> help(str.lower)

But notice that we need to specify the receiver object (value) itself, or the type (class) of the receiver.

We will learn some more about OOP later in the semester (and you will learn a lot more in CSCI201), for now we want to get started using methods, particularly string methods.

So how can we learn more about the methods for an object?

The dir function will return a list of all the methods available for an object, say a string.

>>> dir(s)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

And because it is good practice, many of the methods have descriptive names. e.g. find

>>> help(s.find)
Help on built-in function find:

find(...) method of builtins.str instance
    S.find(sub[, start[, end]]) -> int
    Return the lowest index in S where substring sub is found,
    such that sub is contained within S[start:end].  Optional
    arguments start and end are interpreted as in slice notation.
    Return -1 on failure.

>>> ALPHABET = "abcdefghijklmnopqrstuvwxyz"
>>> ALPHABET.find("a")
>>> ALPHABET.find("z")

How can we apply a sequence of method calls? Recall that we can use the return value from one function call as the argument to another, e.g.

>>> from random import randint
>>> len(range(randint(1, 5)))

The equivalent pattern for method calls would be the following. You can hopefully see why this is often called “chaining”…

>>> s.lower().capitalize()
'Hi cs150'

What about the receiver? The receiver can be any expression, it is not required to a variable or literal. For example:

>>> ("gI".replace("g","h") + " CS" + str(300//2)).swapcase()
'Hi cs150'

Here we are invoking the swapcase method on the expression inside the parentheses. The expression inside the parentheses is evaluated first to produce the string "hI CS150", and then the swapcase method is invoked on that string to produce the final result. A good strategy for evaluating these types of expressions is to work step-by-step keeping in mind the order in which Python evaluates expressions. In general, Python evaluates expressions from left to right while observing PEMDAS. Remember that a set of parentheses are themselves a (single) expression.

PI Questions (string methods, part 1)1

Strings are immutable

>>> s = "Bruce"
>>> t = s.lower()
>>> t
>>> s

Strings in Python are immutable, that is they can’t be changed. The string methods that look like they are changing the string are actually creating a new string object. Check out the Python Tutor memory model picture for the example above.

We can see the same immutability property in the examples below (the Python Tutor memory module picture):

>>> s = "test"
>>> s.upper()
>>> s
>>> a = "hi"
>>> b = a
>>> a = "bye"
>>> b

PI Questions (string methods, part 2)

Previewing booleans

Earlier we saw that strings have a method startswith. We would guess that method will indicate if the string starts with the supplied parameter:

>>> s = "hello"
>>> s.startswith('h')
>>> s.startswith('b')

What are True and False. They are not strings because they’re not enclosed in quotes, nor are they integers or floating points numbers. They must be values of a new type we haven’t yet encountered. Indeed they are bools, which is short for Boolean. Booleans are a type that can only take on two values True and False. Booleans and Boolean logic are a key programming tool, particularly for decision-making, that we will start learning about next week.