# Python basics: Expressions and strings¶

In this tutorial, I introduce the basics of how to use Python to process text, starting with the concept of expressions and evaluation. I go into particular detail on Python's string manipulation functions.

### A note on Python versions¶

There are two main "branches" of Python in current use: Python 2 and Python 3. Both of these branches have their own versions: the latest version of Python 2 (as of this writing) is Python 2.7.x, and the latest version of Python 3 is Python 3.7.x. The branches and versions all have slightly different capabilities and their syntax and structure are slightly different. Python 2.7.x still has a larger number of users overall, and many new projects continue to support it. But most data scientists and data journalists using Python today use the newer version, and following their lead, we'll be using we're using Python 3.6 or later in this course (specifically, the version included with the latest version of Anaconda).

(The main reason you need to know this information is that you should be careful when looking up Python information on the Internet---make sure whatever tutorial you're looking at is about Python 3, not Python 2.)

## Expressions and evaluation¶

Let's start with a very high-level description of how computer programming works. When you're writing a computer program, you're describing to the computer what you want, and then asking the computer to figure that thing out for you. Your description of what you want is called an expression. The process that the computer uses to turn your expression into whatever that expression means is called evaluation.

Think of a science fiction movie where a character asks the computer, out loud, "What's the square root of nine billion?" or "How many people older than 50 live in Paris, France?" Those are examples of expressions. The process that the computer uses to transform those expressions into a response is evaluation.

When the process of evaluation is complete, you're left with a single "value". Think of it schematically like so:

What makes computer programs powerful is that they make it possible to write very precise and sophisticated expressions. And importantly, you can embed the results of evaluating one expression inside of another expression, or save the results of evaluating an expression for later in your program.

Unfortunately, computers can't understand and intuit your desires simply from a verbal description. That's why we need computer programming languages: to give us a way to write expressions in a way that the computer can understand. Because programming languages are designed to be precise, they can also be persnickety (and frustrating). And every programming language is different. It's tricky, but worth it.

## Arithmetic expressions¶

Let's start with simple arithmetic expressions. The way that you write arithmetic expressions in Python is very similar to the way that you write arithmetic expressions in, say, grade school arithmetic, or algebra. In the example below, 3 + 5 is the expression. You can tell Python to evaluate the expression and display its value simply by typing in the expression in a new notebook cell and typing CTRL+ENTER.

In [1]:
1 + 5

Out[1]:
6

Arithmetic expressions in Python can be much more sophisticated than this, of course. We won't go over all of the details right now, but one thing you should know immediately is that Python arithmetic operations are evaluated using the typical order of operations, which you can override with parentheses:

In [2]:
4 + 5 * 6

Out[2]:
34
In [3]:
(4 + 5) * 6

Out[3]:
54

You can write arithmetic expressions with or without spaces between the numbers and the operators (but usually it's considered better style to include spaces):

In [4]:
10+20+30

Out[4]:
60

Expressions in Python can also be very simple. In fact, a number on its own is its own expression, which Python evaluates to that number itself:

In [5]:
19

Out[5]:
19

If you write an expression that Python doesn't understand, then you'll get an error. Here's what that looks like:

In [6]:
+ 20 19

  File "<ipython-input-6-9234c5659ad2>", line 1
+ 20 19
^
SyntaxError: invalid syntax


## Expressions of inequality¶

You can also ask Python whether two expressions evaluate to the same value, or if one expression evaluates to a value greater than another expression, using a similar familiar syntax. When evaluating such expressions, Python will return one of two special values: either True or False.

The == operator compares the expression on its left side to the expression on its right side. It evaluates to True if the values are equal, and False if they're not equal.

In [7]:
3 * 5 == 9 + 6

Out[7]:
True
In [8]:
20 == 7 * 3

Out[8]:
False

The < operator compares the expression on its left side to the expression on its right side, evaluating to True if the left-side expression is less than the right-side expression, False otherwise. The > does the same thing, except checking to see if the left-side expression is greater than the right-side expression:

In [9]:
17 < 18

Out[9]:
True
In [10]:
17 > 18

Out[10]:
False

The >= and <= operators translate to "greater than or equal" and "lesser than or equal," respectively:

In [11]:
22 >= 22

Out[11]:
True
In [12]:
22 <= 22

Out[12]:
True

Make sure to get the order of the angle bracket and the equal sign right!

In [13]:
22 =< 22

  File "<ipython-input-13-1690e5b95a12>", line 1
22 =< 22
^
SyntaxError: invalid syntax


## Variables¶

You can save the result of evaluating an expression for later using the = operator (called the "assignment operator"). On the left-hand side of the =, write a word that you'd like to use to refer to the value of the expression, and on the right-hand side, write the expression itself. After you've assigned a value like this, whenever you include that word in your Python code, Python will evaluate the word and replace it with the value you assigned to it earlier. Like so:

In [14]:
x = (4 + 5) * 6

In [15]:
x

Out[15]:
54

(Notice that the line x = (4 + 5) * 6 didn't cause Python to display anything. That's because an assignment in Python isn't an expression, it's a "statement"---we'll discuss the difference later.)

Now, whenever you use the variable x in your program, it "stands in" for the result of the expression that you assigned to it.

In [16]:
x / 6

Out[16]:
9.0

You can create as many variables as you want!

In [17]:
another_variable = (x + 2) * 4

In [18]:
another_variable

Out[18]:
224

Variable names can contain letters, numbers and underscores, but must begin with a letter or underscore. There are other, more technical constraints on variable names; you can review them here.

If you attempt to use a the name of a variable that you haven't defined in the notebook, Python will raise an error:

In [19]:
voldemort

---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-19-14cf30a6843d> in <module>()
----> 1 voldemort

NameError: name 'voldemort' is not defined

If you assign a value to a variable, and then assign a value to it again, the previous value of the variable will be overwritten:

In [20]:
x = 15

In [21]:
x

Out[21]:
15
In [22]:
x = 42

In [23]:
x

Out[23]:
42

The fact that variables can be overwritten with new values can be helpful in some contexts (e.g., if you're writing a program and you're using the variable to keep track of some value that changes over time). But it can also be annoying if you use the same variable name twice on accident and overwrite values in one part of your program that another part of your program is using the same variable name to keep track of!

## Types¶

Another important thing to know is that when Python evaluates an expression, it assigns the result to a "type." A type is a description of what kind of thing a value is, and Python uses that information to determine later what you can do with that value, and what kinds of expressions that value can be used in. You can ask Python what type it thinks a particular expression evaluates to, or what type a particular value is, using the type() function:

In [24]:
type(100 + 1)

Out[24]:
int

The word int stands for "integer." ("Integers" are numbers that don't have a fractional component, i.e., -2, -1, 0, 1, 2, etc.) Python has many, many other types, and lots of (sometimes arcane) rules for how those types interact with each other when used in the same expression. For example, you can create a floating point type (i.e., a number with a decimal point in it) by writing a number with a decimal point in it:

In [25]:
type(3.14)

Out[25]:
float

Interestingly, the result of adding a floating-point number and an integer number together is always a floating point number:

In [26]:
type(3.14 + 17)

Out[26]:
float

... and the result of dividing one integer by another integer is a floating point number:

In [27]:
type(4 / 3)

Out[27]:
float

Throwing an expression into the type() function is a good way to know whether or not the value you're working with is the value you were expecting to work with. We'll use it for debugging some example code later.

## Strings¶

Another type of value in Python is called a "string." Strings are a way of representing in our computer programs stretches of text: one or more letters in sequential order. To make an expression that evaluates to a string in Python, simply enclose some text inside of quotes and put it into the interactive interpreter:

In [28]:
"Suppose there is a pigeon, suppose there is."

Out[28]:
'Suppose there is a pigeon, suppose there is.'

Asking Python for the type of a string returns str:

In [29]:
type("Suppose there is a pigeon, suppose there is.")

Out[29]:
str

You can use single quotes or double quotes to enclose strings (I tend to use them interchangeably), as long as the opening quote matches the closing quote:

In [30]:
'Suppose there is a pigeon, suppose there is.'

Out[30]:
'Suppose there is a pigeon, suppose there is.'

(When you ask Python to evaluate a string expression, it will display it with single quotes surrounding it.)

You can assign strings to variables, just like any other value:

In [31]:
roastbeef = "Suppose there is a pigeon, suppose there is."

In [32]:
roastbeef

Out[32]:
'Suppose there is a pigeon, suppose there is.'

In versions of Python previous to Python 3, it could be tedious to use any characters inside of strings that weren't ASCII characters (i.e., the letters, numbers and punctuation used most commonly when writing English). In Python 3, you can easily include whatever characters you want by typing them into the string directly:

In [33]:
cat_message = "我爱猫！😻"

In [34]:
cat_message

Out[34]:
'我爱猫！😻'

### "Escaping" special characters in strings¶

Normally, if there are any characters you want in your string, all you have to do to put them there is type the characters in on your keyboard, or paste in the text that you want from some other source. There are some characters, however, that require special treatment and can't be typed into a string directly.

For example, say you have a double-quoted string. Now, the rules about quoting strings (as outlined above) is that the quoted string begins with a double-quote character and ends with a double-quote character. But what if you want to include a double-quote character INSIDE the string? You might think you could do this:

"And then he said, "I think that's a cool idea," and vanished."



But that won't work:

In [35]:
"And then he said, "I think that's a cool idea," and vanished."

  File "<ipython-input-35-cdda9f568fb9>", line 1
"And then he said, "I think that's a cool idea," and vanished."
^
SyntaxError: invalid syntax


It doesn't work because Python interprets the first double-quote it sees after the beginning of the string as the double-quote that marks the end of the string. Then it sees all of the stuff after the string and says, "okay, the programmer must not be having a good day?" and displays a syntax error. Clearly, we need a way to tell Python "I want you to interpret this character not with the special meaning it has in Python, but LITERALLY as the thing that I typed."

We can do this exact thing by putting a backslash in front of the characters that we want Python to interpret literally, like so:

In [36]:
"And then he said, \"I think that's a cool idea,\" and vanished."

Out[36]:
'And then he said, "I think that\'s a cool idea," and vanished.'

A character indicated in this way is called an "escape" character (because you've "escaped" from the typical meaning of the character). There are several other useful escape characters to know about:

• I showed \" above, but you can also use \' in a single-quoted string.
• Use \n if you want to include a new line in your string.
• Use \t instead of hitting the tab key to put a tab in your string.
• Because \ is itself the character used to escape other characters, you need to type \\ if you actually want a backslash in your string.

### Printing vs. evaluating¶

There are two ways to see the result of an expression in the interactive interpreter. You can either type the expression directly:

In [37]:
7 + 15

Out[37]:
22
In [38]:
"\tA \"string\" with escape\ncharacters."

Out[38]:
'\tA "string" with escape\ncharacters.'

Or you can "print" the expression using the print() function by putting the expression inside the parentheses:

In [39]:
print(7 + 15)

22

In [40]:
print("\tA \"string\" with escape\ncharacters.")

	A "string" with escape
characters.


As you can see, the print() function doesn't make a huge difference when displaying the result of an arithmetic expression. But it does make a difference when displaying a string. When you simply type an expression that evaluates to a string in order to display it, without the print() function, Python won't "interpolate" any special characters in the string. ("Interpolate" is a fancy computer programming term that means "replace symbols in something with whatever those symbols represent.") The print() function, on the other hand, will perform the interpolation.

Typing the expression itself results in Python showing you exactly the code you'd need to copy and paste in order to replicate the vale. Typing the expression into print() tells Python to do its best to make the result of the expression look "nice." (The print() function also sends the result of the expression to standard output, which will be important to know when we're writing our own Python programs on the command line later on.)

Now that we can get some text into our program, let's talk about some of the ways Python allows us to do interesting things with that text.

Let's talk about the len() function first. If you take an expression that evaluates to a string and put it inside the parentheses of len(), you get an integer value that indicates how long the string is. Like so:

In [41]:
len("Suppose there is a pigeon, suppose there is.")

Out[41]:
44

The value that len() evaluates to can itself be used in other expressions (just like any other value!):

In [42]:
len("Camembert") + len("Cheddar")

Out[42]:
16

Next up: the in operator, which lets us check to see if a particular string is found inside of another string.

In [43]:
"foo" in "buffoon"

Out[43]:
True
In [44]:
"foo" in "reginald"

Out[44]:
False

The in operator takes one expression evaluating to a string on the left and another on the right, and returns True if the string on the left occurs somewhere inside of the string on the right.

We can check to see if a string begins with or ends with another string using that string's .startswith() and .endswith() methods, respectively:

In [45]:
"foodie".startswith("foo")

Out[45]:
True
In [46]:
"foodie".endswith("foo")

Out[46]:
False

The .isdigit() method returns True if Python thinks the string could represent an integer, and False otherwise:

In [47]:
"foodie".isdigit()

Out[47]:
False
In [48]:
"4567".isdigit()

Out[48]:
True

The .isdigit() method (along with many of the other methods discussed in this section) works not just for ASCII characters but generally across Unicode. For example, it returns True for a full-width digit:

In [52]:
"７".isdigit()

Out[52]:
True

And the .islower() and .isupper() methods return True if the string is in all lower case or all upper case, respectively (and False otherwise).

In [54]:
"foodie".islower()

Out[54]:
True
In [55]:
"foodie".isupper()

Out[55]:
False
In [56]:
"YELLING ON THE INTERNET".islower()

Out[56]:
False
In [57]:
"YELLING ON THE INTERNET".isupper()

Out[57]:
True

The in operator discussed above will tell us if a substring occurs in some other string. If we want to know where that substring occurs, we can use the .find() method. The .find() method takes a single parameter between its parentheses: an expression evaluating to a string, which will be searched for within the string whose .find() method was called. If the substring is found, the entire expression will evaluate to the index at which the substring is found. If the substring is not found, the expression evaluates to -1. To demonstrate:

In [58]:
"Now is the winter of our discontent".find("win")

Out[58]:
11
In [59]:
"Now is the winter of our discontent".find("lose")

Out[59]:
-1

The .count() method will return the number of times a particular substring is found within the larger string:

In [60]:
"I got rhythm, I got music, I got my man, who could ask for anything more".count("I got")

Out[60]:
3

Finally, remember the == operator that we discussed earlier? You can use that in Python to check to see if two strings contain the same characters in the same order:

In [61]:
"pants" == "pants"

Out[61]:
True
In [62]:
"pants" == "trousers"

Out[62]:
False

### Simple string transformations¶

Python strings have a number of different methods which, when called on a string, return a copy of that string with a simple transformation applied to it. These are helpful for normalizing and cleaning up data, or preparing it to be displayed.

Let's start with .lower(), which evaluates to a copy of the string in all lower case:

In [63]:
"ARGUMENTATION! DISAGREEMENT! STRIFE!".lower()

Out[63]:
'argumentation! disagreement! strife!'

The converse of .lower() is .upper():

In [64]:
"e.e. cummings is. not. happy about this.".upper()

Out[64]:
'E.E. CUMMINGS IS. NOT. HAPPY ABOUT THIS.'

The method .title() evaluates to a copy of the string it's called on, replacing every letter at the beginning of a word in the string with a capital letter:

In [65]:
"dr. strangelove, or, how I learned to love the bomb".title()

Out[65]:
'Dr. Strangelove, Or, How I Learned To Love The Bomb'

The .strip() method removes any whitespace from the beginning or end of the string (but not between characters later in the string):

In [66]:
" got some random whitespace in some places here     ".strip()

Out[66]:
'got some random whitespace in some places here'

Finally, the .replace() method takes two parameters: a string to find, and a string to replace that string with whenever it's found. You can use this to make sad stories.

In [67]:
"I got rhythm, I got music, I got my man, who could ask for anything more".replace("I got", "I used to have")

Out[67]:
'I used to have rhythm, I used to have music, I used to have my man, who could ask for anything more'

The .replace() method works with non-ASCII characters as well, of course:

In [76]:
"我爱猫！".replace("猫", "狗")

Out[76]:
'我爱狗！'

### Reading in the contents of a file as a string¶

So far we've just been typing our strings directly into the interactive interpreter by writing string literals (i.e., characters in between quotation marks). This is nice but for larger chunks of text it's desirable to be able to read files from your file system directly. Fortunately, Python makes it easy to do this! The code below will read the contents of the file sea_rose.txt into a variable called text:

In [69]:
text = open("sea_rose.txt").read()


You can change the name of the variable to whatever you want, of course, and you can choose a different file name as well. Once the text is loaded, it's just a regular string, and you can do whatever you want with it! You could just print it out:

In [70]:
print(text)

Rose, harsh rose,
marred and with stint of petals,
meagre flower, thin,
spare of leaf,

more precious
than a wet rose
single on a stem --
you are caught in the drift.

Stunted, with small leaf,
you are flung on the sand,
you are lifted
in the crisp sand
that drives in the wind.

Can the spice-rose
drip such acrid fragrance
hardened in a leaf?



In [71]:
text.count("you")

Out[71]:
3

Or you can transform it:

In [72]:
print(text.replace("a", "aaaa"))

Rose, haaaarsh rose,
maaaarred aaaand with stint of petaaaals,
meaaaagre flower, thin,
spaaaare of leaaaaf,

more precious
thaaaan aaaa wet rose
single on aaaa stem --
you aaaare caaaaught in the drift.

Stunted, with smaaaall leaaaaf,
you aaaare flung on the saaaand,
you aaaare lifted
in the crisp saaaand
thaaaat drives in the wind.

Caaaan the spice-rose
drip such aaaacrid fraaaagraaaance
haaaardened in aaaa leaaaaf?



Some caveats:

• The file you specify must be located in the same directory as the interactive interpreter.
• The file needs to be in plain text format. More information on plain text
• The file needs to be in either ASCII or UTF-8 encoding. (We'll talk more about encodings later, but if the text you want to work with isn't in UTF-8 format, most text editors will allow you to modify the encoding of a file when you save it.)

## Functions and methods¶

Okay, we're getting somewhere together! But I've still been using a lot of jargon when explaning this stuff. One thing that might confuse you: what's a "function" and what's a "method"?

We've talked about two "functions" so far: len() and type(). A function is a special word that you can use in Python expressions that runs some pre-defined code: you put your expression inside the parentheses, and Python sends the result of evaluating that expression to the code in the function. That code operates on the value that you gave it, and then itself evaluates to another value. Using a function in this way is usually called "calling" it or "invoking" it. The stuff that you put inside the parentheses is called a "parameter" or "argument"; the value that the function gives back is called its "return value."

The len() and type() functions are two of what are called "built-in functions," i.e. functions that come with Python and are available whenever you're writing Python code. In Python, built-in functions tend to be able to take many different types of value as parameters. (There are a lot of other built-in functions, not just len() and type()! We'll discuss them as the need arises.)

NOTE: You can also write your own functions---we'll learn how to do this later in the class. Writing functions is a good way to avoid repetition in your code and to compartmentalize it.)

"Methods" work a lot like functions, except in how it looks when you use them. Instead of putting the expression that you want to use them with inside the parentheses, you put the call to the method directly AFTER the expression that you want to call it on, following a period (.). Methods, unlike built-in functions, are usually only valid for one type of value; e.g., values of the string type have a .strip() method, but integer values don't.

It's important to remember that methods can be called both on an expression that evaluates to a particular value AND on a variable that contains that value. So you can do this:

In [73]:
"hello".find('e')

Out[73]:
1

...and this:

In [74]:
s = "hello"
s.find('e')

Out[74]:
1

## Getting help in the interactive interpreter¶

The interactive interpreter has all kinds of nuggets to help you program in Python. The first one worth mentioning is the help() function. Pass any function or method as a parameter to help() and you'll get a handy description of the method or function and what it does:

In [75]:
>>> help(len)

Help on built-in function len in module builtins:

len(obj, /)
Return the number of items in a container.



Remember above when we were talking about how certain types of value have certain "methods" that you can only use with that type of value? Sometimes it's helpful to be reminded of exactly which methods an object supports. You can find this out right in the interactive interpreter without having to look it up in the documentation using the dir() built-in function. Just pass the value that you want to know more about to dir():

In [76]:
>>> dir("hello")

Out[76]:
['__add__',
'__class__',
'__contains__',
'__delattr__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattribute__',
'__getitem__',
'__getnewargs__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__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']

This is a list of all of the methods that the string type supports. (Ignore anything that begins with two underscores (__) for now---those are special weird built-in methods that aren't very useful to call on their own.) If you want to know more about one method in particular, you can type this (note again that you need to NOT include the parentheses after the method):

In [80]:
help("hello".swapcase)

Help on built-in function swapcase:

swapcase(...) method of builtins.str instance
S.swapcase() -> str

Return a copy of S with uppercase characters converted to lowercase
and vice versa.



Hey awesome! We've learned something about another string method. Let's try this method out:

In [81]:
"New York University".swapcase()

Out[81]:
'nEW yORK uNIVERSITY'

EXERCISE: Use dir() and help() to find and research a string method that isn't mentioned in the notes. Then write an expression using that method.

## String indexing¶

Python has some powerful language constructions that allow you to access parts of the string by their numerical position in the string. You can get an individual character of a string by putting square brackets ([]) right after an expression that evaluates to a string, and putting inside the square brackets the number that represents which character you want. Here's an example:

In [85]:
"bungalow"[2]

Out[85]:
'n'

You can also do this with variables that contain string values, of course:

In [86]:
message = "bungalow"
message[2]

Out[86]:
'n'

If we were to say this expression out loud, it might read, "I have a string, consisting of the characters b, u, n, g, a, l, o and w, in that order. Give me back the second item in that string." Python evaluates that expression to n, which is indeed the second letter in the word "bungalow."

### The second letter? Am I seeing things. "u" is clearly the second letter.¶

You're right---good catch. But for reasons too complicated to go into here, Python (along with many other programming languages!) starts counting at 0, instead of 1. So what looks like the third letter of the string to human eyes is actually the second letter to Python. The first letter of the string is accessed using index 0, like so:

In [87]:
message[0]

Out[87]:
'b'

The way I like to conceptualize this is to think of list indexes not as specifying the number of the item you want, but instead specifying how "far away" from the beginning of the list to look for that value.

If you attempt to use a value for the index of a list that is beyond the end of the list (i.e., the value you use is higher than the last index in the list), Python gives you an error:

In [88]:
message[17]

---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-88-58216cf19efe> in <module>()
----> 1 message[17]

IndexError: string index out of range

An individual character from a string still has the same type as the string it came from:

In [89]:
type(message[3])

Out[89]:
str

And, of course, a string containing an individual character has a length of 1:

In [90]:
len(message[3])

Out[90]:
1

### Indexes can be expressions too¶

The thing that goes inside of the index brackets doesn't have to be a number that you've just typed in there. Any Python expression that evaluates to an integer can go in there.

In [91]:
message[2 * 3]

Out[91]:
'o'
In [1]:
x = 3
message[x]

---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-1-f83737398be9> in <module>()
1 x = 3
----> 2 message[x]

NameError: name 'message' is not defined
In [93]:
message[message.find("a")]

Out[93]:
'a'

### Negative indexes¶

If you use -1 as the value inside of the brackets, something interesting happens:

In [94]:
message[-1]

Out[94]:
'w'

The expression evaluates to the last character in the string. This is essentially the same thing as the following code:

In [95]:
message[len(message) - 1]

Out[95]:
'w'

... except easier to write. In fact, you can use any negative integer in the brackets, and Python will count that many items from the end of the string, and the expression evaluates to that item.

In [96]:
message[-3]

Out[96]:
'l'

If the value in the brackets would "go past" the beginning of the list, Python will raise an error:

In [97]:
message[-987]

---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-97-58ed85624280> in <module>()
----> 1 message[-987]

IndexError: string index out of range

## String slices¶

The index bracket syntax explained above allows you to write an expression that evaluates to a character in a string, based on its position in the string. Python also has a powerful way for you to write expressions that return a section of a string, starting from a particular index and ending with another index. In Python parlance we'll call this section a slice.

Writing an expression to get a slice of a string looks a lot like writing an expression to get a single character. The difference is that instead of putting one number between square brackets, we put two numbers, separated by a colon. The first number tells Python where to begin the slice, and the second number tells Python where to end it.

In [98]:
message[1:4]

Out[98]:
'ung'

Note that the value after the colon specifies at which index the slice should end, but the slice does not include the value at that index. I would translate the expression above as saying "give me characters one through four of the string in the "message" variable, NOT INCLUDING character four."

The fact that slice indexes aren't inclusive means that you can tell how long the slice will be by subtracting the value before the colon from the value after it:

In [99]:
message[1:4]

Out[99]:
'ung'
In [100]:
len(message[1:4])

Out[100]:
3
In [101]:
4 - 1

Out[101]:
3

Also note that---as always!---any expression that evaluates to an integer can be used for either value in the brackets. For example:

In [102]:
x = 3
message[x:x+2]

Out[102]:
'ga'

Finally, note that the type of a slice is still str:

In [103]:
type(message[5:7])

Out[103]:
str

### Omitting slice values¶

Because it's so common to use the slice syntax to get a string that is either a slice starting at the beginning of the string or a slice ending at the end of the string, Python has a special shortcut. Instead of writing:

In [104]:
message[0:3]

Out[104]:
'bun'

You can leave out the 0 and write this instead:

In [105]:
message[:3]

Out[105]:
'bun'

Likewise, if you wanted a slice that starts at index 4 and goes to the end of the string, you might write:

In [106]:
message[4:]

Out[106]:
'alow'

### Negative index values in slices¶

Now for some tricky stuff: You can use negative index values in slice brackets as well! For example, to get a slice of a string from the fourth-to-last element of the string up to (but not including) the second-to-last element of the string:

In [107]:
message[-4:-2]

Out[107]:
'al'

(Even with negative slice indexes, the numbers have the property that subtracting the first from the second yields the length of the slice, i.e. -2 - (-4) is 2).

To get the last three elements of the string:

In [108]:
message[:-3]

Out[108]:
'bunga'

EXERCISE: Write an expression, or a series of expressions, that prints out "Sea Rose" from the first occurence of the string sand up until the end of the poem. (Hint: Use the .find() method, discussed above.)

## Putting strings together¶

Earlier, we discussed how the + operator can be used to create an expression that evaluates to the sum of two numbers. E.g.:

In [109]:
17 + 92

Out[109]:
109

The + operator can also be used to create a new string from two other strings. This is called "concatenation":

In [110]:
"Spider" + "man"

Out[110]:
'Spiderman'
In [111]:
part1 = "Nickel, what is nickel, "
part2 = "it is originally rid of a cover."
part1 + part2

Out[111]:
'Nickel, what is nickel, it is originally rid of a cover.'

You can combine as many strings as you want this way, using the + operator multiple times in the same expression:

In [112]:
"bas" + "ket" + "ball"

Out[112]:
'basketball'

EXERCISE: Write an expression that evaluates to a string containing the first fifty characters of "Sea Rose" followed by the last fifty characters of "Sea Rose."

### Strings and numbers¶

It's important to remember that a string that contains what looks like a number does not behave like an actual integer or floating point number does. For example, attempting to subtract one string containing a number from another string containing a number will cause an error to be raised:

In [113]:
"15" - "4"

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-113-f9037fd5edf2> in <module>()
----> 1 "15" - "4"

TypeError: unsupported operand type(s) for -: 'str' and 'str'

The "unsupported operand type(s)" error means that you tried to use an operator (in this case +) with two types that the operator in question doesn't know how to work with. (Python is saying: "You asked me to subtract a string from another string. That doesn't make sense to me.")

Attempting to add an integer or floating-point number to a string that has (what looks like) a number inside of it will raise a similar error:

In [114]:
16 + "8.9"

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-114-0ba8b111bb4f> in <module>()
----> 1 16 + "8.9"

TypeError: unsupported operand type(s) for +: 'int' and 'str'

Fortunately, there are built-in functions whose purpose is to convert from one type to another; notably, you can put a string inside the parentheses of the int() and float() functions, and it will evaluate to (what Python interprets as) the integer and floating-point values (respectively) of the string:

In [115]:
type("17")

Out[115]:
str
In [116]:
int("17")

Out[116]:
17
In [117]:
type(int("17"))

Out[117]:
int
In [118]:
type("3.14159")

Out[118]:
str
In [119]:
float("3.14159")

Out[119]:
3.14159
In [120]:
type(float("3.14159"))

Out[120]:
float

If you give a string to one of these functions that Python can't interpret as an integer or floating-point number, Python will raise an error:

In [121]:
int("shumai")

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-121-74631335efe3> in <module>()
----> 1 int("shumai")

ValueError: invalid literal for int() with base 10: 'shumai'

### Strings with multiple lines¶

Sometimes we want to work with strings that have more than one "line" of text in them. The problem with this is that Python interprets your having pressed "Enter" with your having finished your input, so if you try to cut-and-paste in some text with new line characters, you'll get an error:

In [122]:
poem = "Rose, harsh rose,
marred and with stint of petals,
meagre flower, thin,
spare of leaf,"

  File "<ipython-input-122-17f1398fad0d>", line 1
poem = "Rose, harsh rose,
^
SyntaxError: EOL while scanning string literal


(EOL while scanning string literal is Python's way of saying "you hit enter too soon.") One way to work around this is to include \n (newline character) inside the string when we type it into our program:

In [123]:
poem = "Rose, harsh rose,\nmarred and with stint of petals,\nmeagre flower, thin,\nspare of leaf,"
print(poem)

Rose, harsh rose,
marred and with stint of petals,
meagre flower, thin,
spare of leaf,


This works, but it's kind of inconvenient! A better solution is to use a different way of quoting strings in Python, the triple-quote. It looks like this:

In [124]:
poem = """Rose, harsh rose,
marred and with stint of petals,
meagre flower, thin,
spare of leaf,"""
print(poem)

Rose, harsh rose,
marred and with stint of petals,
meagre flower, thin,
spare of leaf,


When you use three quotes instead of one, Python allows you to put new line characters directly into the string. Nice! We'll be using this for some of the examples below.

Exercise: Create a variable called poem and assign the text of "Sea Rose" to that variable. Use the len() function to find out how many characters are in it. Then, use the count() method to find out how many times the string rose occurs within it.

## Conclusion¶

This section introduces many of the basic building blocks you'll need in order to use computer programs to write poems. We've talked about how to use the interactive interpreter, and about expressions and values, and about the distinction between functions and methods; and we've discussed the details of how strings work and how to manipulate them.