Basic Operations

Almost all Python data types provide a variety of operations that you can perform on objects of that type. In the previous section, we learned about Python's basic data types. However, we haven't done anything that shows how they are used. We'll do something about that in this chapter.

A remarkable feature of Python is how much functionality the different types share. For instance:

  • You can compare almost any pair of objects with the same type for equality and, in many cases, for less than or greater than operations.
  • You can determine the length of any collection object or text sequence by calling the len() function on that object.
  • You can convert nearly any object into a human-readable string form.
  • You can iterate over different collection types without changing your syntax.

This feature brings considerable flexibility and ease of programming to the language.

In this chapter, we'll begin to distinguish between three main kinds of types:

  • The built-in types are part of Python. They are available in every program if you want them; just start using them. Most types we'll encounter in this book are built-in types.
  • The standard types aren't automatically loaded when running python. Instead, they are available from modules you can import into your programs. You don't have to download the standard types, but you do need to import them. We'll see some examples of importing modules in this chapter and learn more about importing modules later in the book.
  • The non-standard types come from either you and your colleagues or as downloadable modules available on the Internet.

Arithmetic Operations

First up, let's turn our attention to the elementary arithmetic operations. All built-in and standard numeric data types work with all or most of these operations:

Operator Operation
+ Addition
- Subtraction
* Multiplication
/ Division
// Integer division
% Modulo
** Exponentiation

These operators work with the built-in integer and float types and other types like the complex, decimal, and fractional number types. We'll focus on integers and floats here, but working with other data types is nearly identical.

Let's look at some examples of the basic operations in action:

  • Addition

    print(38 + 4)      # 42
    print(38.4 + 41.9) # 80.3
    
    # mixing integers & floats
    print(38 + 41.5)   # 79.5
    
  • Subtraction

    print(38 - 4)      # 34
    print(38.4 - 41.9) # -3.5
    
    # mixing integers & floats
    print(38 - 41.5)   # -3.5
    
  • Multiplication

    print(38 * 4)      # 152
    print(38.4 * 41.1) # 1578.24
    
    # mixing integers & floats
    print(38 * 41.5)   # 1577.0
    
  • Division with /

    print(16 / 4)      # 4.0
    print(16 / 2.5)    # 6.4
    

    When you divide two integers, two floats, or one of each with /, the result is always a float, even if both operands are integers.

  • Integer division with //

    print(16 // 3)     # 5
    print(16 // -3)    # -6
    print(16 // 2.3)   # 6.0
    print(-16 // 2.3)  # -7.0
    

    The // operator returns the largest whole number less than or equal to the floating point result. That is, it rounds the result down to the nearest whole number. Thus, 16 // 3 returns 5, not 5.333333333333333. Likewise, 16 // -3 returns -6. If either operand is a float, the return value is also a float, but it's still rounded down to a whole number.

    Note: The // operator doesn't work with the built-in complex numbers.

  • Exponentiation (powers)

    The expression a**b tells Python to raise a to the power of b. Thus, something like 3**4 means 3 to the 4th power, or 3 * 3 * 3 * 3 or 81.

    print(16**3)     # 4096
    
  • Modulo

    The % operator is called the modulo operator. It is sometimes called the modulus operator or the remainder operator, both of which are technically incorrect. (There are some subtle differences between modulo and remainder operations). This confusion is understandable since the % operator is usually used to calculate the remainder of dividing two integers:

    print(15 % 3)   # 0
    print(16 % 3)   # 1
    print(17 % 3)   # 2
    print(18 % 3)   # 0
    

    We won't get into the differences between modulo, modulus, and remainder in this book. What's important right now is that % is easiest to understand when the operands are both positive integers.

    When both a and b are positive integers, a % b returns the non-negative remainder of dividing a by b. Of special interest is that a % b returns 0 if, and only if, a is exactly divisible by b. If either a or b is negative, things get weird, as can be seen in the following table:

    a b a % b remainder
    14 7 0 0
    17 7 3 3
    17 -7 -4 3
    -17 7 4 -3
    -17 -7 -3 -3

    The final column in the above table shows what a remainder operator would return if Python had one. You can compute the remainder in Python using the math module's remainder function:

    from math import remainder
    
    print(int(remainder(14, 7)))    # 0
    print(int(remainder(17, 7)))    # 3
    print(int(remainder(17, -7)))   # 3
    print(int(remainder(-17, 7)))   # -3
    print(int(remainder(-17, -7)))  # -3
    

    In general, you don't need to know the difference between modulo and remainder; just be aware that they are different. You also don't need to worry about how they work with negative numbers if you can avoid using negative numbers. Since most use cases of % only care about divisibility, that will happen naturally. If a % b == 0, then a is evenly divisible by b, no matter the signs of a and b.

    Note: The % operator doesn't work with the built-in complex numbers.

Floating Point Imprecision

Floating point numbers have some precision issues you must be aware of. For instance, if you add 0.1 and 0.2 and compare the result for equality with 0.3, you'll learn that they are not equal:

print(0.1 + 0.2 == 0.3)       # False

This can be a serious problem in some applications, such as finance. It's an artifact of how real numbers are stored on most computers; you'll encounter this problem in almost every language. One way around the problem in Python is to use the math.isclose function:

import math
math.isclose(0.1 + 0.2, 0.3)  # True

You can also use the decimal.Decimal type to make precise computations:

from decimal import Decimal
Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
# True

Note that you should always use strings with decimal.Decimal. You can use float values. However, you will lose the benefit of precise computation if you do.

Equality Comparison

You sometimes want to determine whether two values are identical or different. The == and != operators can help with that. == compares two operands for equality and returns True or False as appropriate. In contrast, != returns True if they are not equal, False otherwise.

Let's try some comparisons in Python:

print(42 == 42)       # True
print(42 == 43)       # False
print('foo' == 'foo') # True (works with strings)
print('FOO' == 'foo') # False (Case matters)
print(42 != 42)       # False
print(42 != 43)       # True
print('foo' != 'foo') # False (works with strings)
print('FOO' != 'foo') # True (Case matters)

The operators == and != work with almost all data types. Assuming that the values represented by a and b below are instances of the built-in data types, then:

  • If a and b have different data types, a == b usually returns False while a != b returns True. However, numbers are an exception: all built-in and standard number types can be compared for equality without regard to their specific types. Thus, 1 == 1.0 is True.

  • If a and b have the same data type, a == b almost always returns True if the two objects have the same value, while a != b returns False.

When working with standard and non-standard types, all bets are off. While most non-builtin types obey the same rules, not all do. The only way to be sure is to study the documentation, look at the source code, or try to determine the behavior from tests.

Ordered Comparisons

As you might expect, Python supports several other comparison operations: you can check for less than (<), less than or equal to (<=), greater than (>), and greater than or equal to (>=):

print(42 < 41)           # False
print(42 < 42)           # False
print(42 <= 42)          # True
print(42 < 43)           # True

print('abcdf' < 'abcef') # True
print('abc' < 'abcdef')  # True
print('abcdef' < 'abc')  # False
print('abc' < 'abc')     # False
print('abc' <= 'abc')    # True
print('abd' < 'abcdef')  # False
print('A' < 'a')         # True
print('Z' < 'a')         # True

print('3' < '24')        # False
print('24' < '3')        # True

Note the comparisons involving strings. Strings are compared lexicographically, which means they are compared character-by-character from left-to-right.

For instance, on line 6, we first compare the first character of each string ('a'). Since they are the same, we move on to the next pair of characters ('b'), then the pair after that ('c'). Everything is equal up to this point.

However, when we compare 'd' against 'e', we finally see a difference between the two strings. Since 'd' < 'e', the final result is True. Python doesn't bother looking at the remaining character in these strings ('f').

This lexicographic comparison also explains why '3' < '24' on line 15 is False and '24' < '3' on line 16 is True. Only the first character of each string gets compared.

It's also worth looking at 'abc' < 'abcdef' on line 7. While the first 3 characters of both strings are the same, the first string is shorter than the second. When Python compares two strings that are equal up to the length of the shorter string, then the shorter string is deemed to be less than the longer string.

When comparing strings, Python stops as soon as it makes a decision. For instance, in 'abd' < 'abcdef', Python only needs to check the first 3 characters in both strings. When it reaches the 3rd character, it can see that 'abd' is not less than 'abcdef'.

Finally, lines 12 and 13 help demonstrate that uppercase alphabetic letters are considered to be less than lowercase alphabetic letters. Every uppercase letter is less than every lowercase letter. This behavior often confuses new programmers, but almost all languages work this way.

print(42 > 41)           # True
print(42 > 42)           # False
print(42 >= 42)          # True
print(42 > 43)           # False

print('abcdf' > 'abcef') # False
print('abc' > 'abcdef')  # False
print('abcdef' > 'abc')  # True
print('abc' > 'abc')     # False
print('abc' >= 'abc')    # True
print('abcdef' > 'abd')  # False
print('A' > 'a')         # False
print('Z' > 'a')         # False

print('3' > '24')        # True
print('24' > '3')        # False

Let's look more closely at 'abcdef' > 'abc'. In this example, the strings have unequal sizes. Furthermore, the longer string is identical up to the shorter string's length. Python returns True here; when it can no longer take characters from the shorter string, it concludes that the longer string has the greater value. Similar behaviors occur with the other ordered comparison operators.

It's also worth noting that even numeric strings are compared character by character. Thus, '3' > '24' returns True since the character 3 is greater than the character 2.

In general, numeric characters in a string are less than alphabetic characters, and uppercase letter characters are less than lowercase letters. Other characters appear at different points. For instance, '#' < '5' < ';' < 'A' < '^' < 'a'. (You don't have to memorize this.)

If the precise ordering of character values becomes sufficiently significant to matter, look them up in a standard ASCII table.

As with == and !=, many other types besides numbers and strings work with the ordered comparison operators. For instance, you can compare sets with these operators to determine if set a is a subset or superset of set b. You can also compare lists and tuples: like string comparisons, list and tuple comparison goes element by element to determine which object is less than or greater than the other:

print({3, 1, 2} < {2, 4, 3, 1})         # True
print({3, 1, 2} > {2, 4, 3, 1})         # False
print({2, 4, 3, 1} > {3, 1, 2})         # True
print([1, 2, 3] < [1, 2, 3, 4])         # True
print([1, 4, 3] < [1, 3, 3])            # False
print([1, 3, 3] < [1, 4, 3])            # True

String Concatenation

String concatenation looks like numeric addition and multiplication, but the result is entirely different. It uses the + and * operators to join strings.

Back to Python:

>>> 'foo' + 'bar'
'foobar'

That looks simple enough.

There is at least one surprise you may encounter. What will the following code return? Try answering before you try it.

'1' + '2'

If you thought it would return 3, that makes sense. However, since '1' and '2' are both strings, Python performs concatenation instead. That's why the result is '12'.

You can also use the * operator to perform repetitive concatenation. For instance, if you want the string 'abcabcabc', you can use * to generate it:

print('abc' * 3)              # 'abcabcabc'
print(3 * 'abc')              # 'abcabcabc'

Coercion

Suppose you have two string values in your program, '1' and '2', that you want to add mathematically and get 3. You can't do that directly since + performs concatenation when its operands are both strings. Somehow, we need to coerce the strings '1' and '2' to the numbers 1 and 2: we want to perform a type coercion.

Strings to Numbers

The int and float functions coerce a string to a number. int coerces a string to an integer, while float coerces a string to a float.

print(int('5'))               # 5
print(float('3.141592'))      # 3.141592

These functions take a string or another number and attempt to convert it to an integer or a float. You can then perform arithmetic operations on the result.

Numbers to Strings

You can also coerce numbers into strings. The str function provides this capability. Let's see an example:

print(str(5))                 # '5'
print(str(3.141592))          # '3.141592'

In reality, str can convert most Python values to a valid string. We'll see more situations where you can use str later.

Implicit Coercion

Using functions like str, int, etc., to coerce values from one type to another is sometimes called explicit coercion. It's required for many conversions and a good idea in others where clarity is needed.

Python also has implicit coercions. Implicit coercions can be handy; they can save a lot of typing and often lead to more readable code. For instance, when you use print() to print an object -- any object -- print will implicitly coerce it to a string before printing it. That saves considerable typing:

# (Unnecessary) Explicit coercion
print(str(3))           # 3
print(str(False))       # False
print(str([1, 2, 3]))   # [1, 2, 3]
print(str({4, 5, 6}))   # {4, 5, 6}

# Implicit coercion
print(3)                # 3
print(False)            # False
print([1, 2, 3])        # [1, 2, 3]
print({4, 5, 6})        # {4, 5, 6}

Implicit coercion also occurs when mixing numbers of different types in an expression:

Type A Type B Result type
int float float
int Decimal Decimal
int Fraction Fraction
float Decimal --error--
float Fraction float
Decimal Fraction --error--

These results aren't surprising. Mixing different number types is handy, and most people expect it.

Gotchas sometimes arise with implicit coercion. One such gotcha occurs when you use a Boolean in an arithmetic expression. Python implicitly coerces True to the integer value 1 and False to 0. The resulting integer may be coerced further based on the types of the other subexpressions.

print(True + True + True)     # 3
print(True + 1 + 1.0)         # 3.0
print(False * 5000)           # 0

If there's any chance that one of your variables contains a Boolean value, be careful. While Python won't mind, the result may not be what you expect. Even if you expect it, will the next person who looks at your code?

One last implicit coercion is the truthiness coercion. Python can use any value, regardless of type, in a conditional expression in an if or while statement. We'll discuss this in the Truthiness topic of the Flow Control chapter.

Determining Types

As we've seen, Python data types give the programmer many choices. However, when things go wrong and you need to debug your code, you may have to determine what classes you're working with. Knowing how to view type information in the REPL is worthwhile even if you aren't debugging code. You can even make programmatic decisions based on type. However, that's usually a sign of a problem with your design.

With that said, let's see some of the ways we can determine types. First up is the type function, which can be called with any object:

print(type(1))         # <class 'int'>
print(type(3.14))      # <class 'float'>
print(type(True))      # <class 'bool'>
print(type('abc'))     # <class 'str'>
print(type([1, 2, 3])) # <class 'list'>
print(type(None))      # <class 'NoneType'>

foo = 42               # Variables work, too
print(type(foo))       # <class 'int'>

Note that the return value usually includes more information than you need. If you just want the class name, you can access the __name__ property from the result:

print(type('abc').__name__)   # str
print(type(False).__name__)   # bool
print(type([]).__name__)      # list

Finally, you can use type with the is operator.

print(type('abc') is str)     # True
print(type('abc') is int)     # False
print(type(False) is bool)    # True
print(type([]) is list)       # True
print(type([]) is set)        # False

Note that all 3 of the above approaches discount the effects of inheritance. Since we don't discuss inheritance in this book, that doesn't matter for now. However, you may want to consider using the isinstance function, which determines whether an object is an instance of a particular type. It takes inheritance into account. We'll return to this in our Object Oriented Programming book.

print(isinstance('abc', str))    # True
print(isinstance([], set))       # False

class A:
    pass

class B(A):
    pass

b = B()

print(type(b).__name__) # B
print(type(b) is B)     # True
print(type(b) is A)     # False (b's type is
                        # not A)
print(isinstance(b, B)) # True
print(isinstance(b, A)) # True (b is instance of A and B)

String Representations

You can use the str and repr functions with any object. They each return a string representation of an object. The str function returns a string intended to be something that humans can read. It is often used when printing an object. Python implicitly calls str() when it needs to print or interpolate a value. The repr function is a bit lower-level. It returns a string that you can, in theory, use to create a new instance of the object.

For most built-in types, str and repr return the same value. However, that is not always the case. Strings are one of the more obvious types with different str and repr return values:

my_str = 'abc'
print(my_str)       # abc
print(str(my_str))  # abc (same as print(my_str))
print(repr(my_str)) # 'abc' (note the quotes)

Collection and String Lengths

All built-in collection types (strings, sequences, mappings, and sets) have lengths. The length of a string is the number of characters in the string, while the length of other collections is the number of elements in the collection. You can easily determine the length of a collection using the len function:

print(len('Launch School'))       # 13 (string)
print(len(range(5, 15)))          # 10 (range)
print(len(range(5, 15, 3)))       # 4 (range)
print(len(['a', 'b', 'c']))       # 3 (list)
print(len(('d', 'e', 'f', 'g')))  # 4 (tuple)
print(len({'foo': 42, 'bar': 7})) # 2 (dict)
print(len({'foo', 'bar', 'qux'})) # 3 (set)

Indexing and Key Access

Strings, ranges, lists, and tuples all support indexed access to individual elements in the collection. Indices begin at 0 and run through 1 less than the length of the string or collection. Any index used must be in this range, or you will get an IndexError:

Indices may also be negative in the range -1 to -len(seq). We'll discuss this later.

my_str = "abc"                # string
print(my_str[0])              # 'a'
print(my_str[1])              # 'b'
print(my_str[2])              # 'c'
print(my_str[3])
# IndexError: string index out of range
my_range = range(5, 8)         # range
print(my_range[0])             # 5
print(my_range[1])             # 6
print(my_range[2])             # 7
print(my_range[3])
# IndexError: range object index out of range
my_list = [4, 5, 6]           # list
print(my_list[0])             # 4
print(my_list[1])             # 5
print(my_list[2])             # 6
print(my_list[3])
# IndexError: list index out of range
tup = (8, 9, 10)              # tuple
print(tup[0])                 # 8
print(tup[1])                 # 9
print(tup[2])                 # 10
print(tup[3])
# IndexError: tuple index out of range

Dictionaries use keys that work similarly to indexes. However, it is incorrect to describe dictionary keys as indexes: they are keys. As with indexes, using a key that does not exist in the dictionary produces an error:

my_dict = {'a': 1, 'b': 2, 'c': 3}
print(my_dict['a'])           # 1
print(my_dict['b'])           # 2
print(my_dict['c'])           # 3
print(my_dict['d'])           # KeyError: 'd'

Using [] to Update Elements

Since they are mutable, lists and dictionaries let you use the [] operator to replace collection elements. As you might expect, lists use indexes to update elements, while dictionaries use keys. You cannot use [] to create new list elements, but you can do so with dictionaries.

Note that strings, ranges, tuples, and frozen sets are immutable, so they do not support using [] for updates. Sets are mutable, but they don't support indexing.

my_list = [1, 2, 3, 4]
my_list[2] = 6
print(my_list)          # [1, 2, 6, 4]
my_list[4] = 10
# IndexError: list assignment index out of range
my_dict = {
    'dog': 'barks',
    'cat': 'meows',
    'pig': 'oinks',
}

my_dict['pig'] = 'snorts'
print(my_dict)
# Pretty printed for clarity
# {
#     'dog': 'barks',
#     'cat': 'meows',
#     'pig': 'snorts'
# }

my_dict['fish'] = 'glub glub'
print(my_dict)
# Pretty printed for clarity
# {
#     'dog': 'barks',
#     'cat': 'meows',
#     'pig': 'snorts',
#     'fish': 'glub glub'
# }

Expressions and Statements

The distinction between statements and expressions in Python is fundamental and affects how you write and structure your code. Let's define them now.

An expression combines values, variables, operators, and calls to functions to produce a new object. Expressions must be evaluated to determine the expression's value. Examples of expressions include:

  • Literals: 5, 'Karl', 3.141592, True, None
  • Variable references: foo or name when these variables have been previously defined.
  • Arithmetic operations: x + y or a * b - 5.
  • Comparison operations: 'x' == 'x' or 'x' < 'y'.
  • String operations: 'x' + 'y' or 'x' * 32.
  • Function calls: print('Hello') or len('Python').
  • Any valid combination of the above that evaluates to a single object.

You can think of an expression as something that produces a value that can be assigned to a variable, passed to a function or method, or returned by a function or a method.

A statement, on the other hand, is an instruction that tells Python to perform an action of some kind. Unlike expressions, statements don't return values. They do something but don't produce a value as expressions do.

Examples of statements include:

  • Assignment: like x = 5. This doesn't evaluate as a value; it assigns a value to a variable.
  • Control flow: such as if, else, while, for, and so on. These determine the flow of your program but don't evaluate as a value themselves.
  • Function and class definitions: using def or class.
  • Return statements: like return x, which tells a function to exit and return a value. return itself doesn't return a value; it informs the function what value it should return.
  • Import statements: such as import math.

There are some key differences to keep in mind:

  • Expressions always return a value; statements do not.
  • Expressions are often part of statements. For example, in the statement y = x + 5, x + 5 is an expression.
  • Statements often represent bigger chunks of functionality like loops or conditionals; expressions deal with determining values.

Some expressions are stand-alone. They aren't part of an ordinary statement, but their return values are ignored. These stand-alone expressions fall into a gray area; the return values are ignored, making them seem more like statements. In this book and elsewhere at Launch School, we consider these stand-alone expressions as both expressions and statements.

3 + 4            # Simple expression
print('Hello')   # Function call; returns None
my_list.sort()   # Method call; returns None

Expression Evaluation

By default, Python evaluates most expressions from left to right. This is fine if all of the operators in an expression are the same operator:

>>> 1 + 2 + 3 + 4 + 5
15

Here, Python first evaluates 1 + 2, which is 3. It then adds 3 to the first 3, which yields 6. Next, we add 4 to get 10 and, finally, we add 5 to get 15.

However, what happens if the operators are mixed?

>>> 4 * 5 - 1 + 2 * 3
??

Should that expression be 63? Is the result possibly 72? 25? -8? Maybe it's something else altogether? No, this isn't one of those silly math puzzles you see on social media.

Python evaluates that expression as 25: the multiplications occur first (4 * 5 is 20 and 2 * 3 is 6), followed by the additions and subtractions from left to right (20 - 1 + 6 is 25). Python dictates this order of evaluation through its precedence rules, which we discuss in more detail in the Core Curriculum.

You shouldn't rely on the precedence rules even if you've memorized them. Use parentheses to tell Python explicitly how you want to evaluate the expression:

print(((4 * 5) - 1) + (2 * 3))   # 25
print((4 * ((5 - 1) + 2)) * 3)   # 72
print(((((4 * 5) - 1) + 2) * 3)) # 63
print(4 * (5 - (1 + (2 * 3))))   # -8

Parenthesized sub-expressions are usually evaluated before any non-parenthesized sub-expressions. So, on line 1 above, Python evaluates (4 * 5) and (2 * 3) before it performs any addition or subtraction operations.

Nested parenthesized sub-expressions are evaluated before the parenthesized expressions they are contained in. Thus, on line 1 above, (4 * 5) and (2 * 3) get evaluated first. Next, 1 gets subtracted from the result of (4 * 5), then the result of (2 * 3) gets added to that value.

In general, you should not depend on the precedence rules in Python. No matter how well you know them, you will eventually forget one of the trickier cases, and readers of your code may not understand the code. You should always use parentheses to inform Python and readers of your code of your intent.

Output vs. Return Values

Inexperienced programmers are often confused with the difference between returning or outputting a value. When we invoke the print function, we're telling Python to write some output to the display. In Python, that is usually your screen. The term log is often used as a synonym for printing or showing something on the display. Sometimes, we even use log, the noun, as a synonym for your terminal screen. For example, in the following code, the print function simply prints the string 'abc'. It doesn't return a useful value; it's sole purpose is to print something.

print('abc')

In many cases, a function or expression doesn't print anything, but simply returns a value that gets assigned to a variable, evaluated as a condition, or passed to another function. For example, the following code first calls the range function, which returns a range object. That value is subsequently passed to the list function which, in turn, returns a list object.

list(range(3))

In the Python REPL, the return value can also be displayed on the screen. However, take care not to confuse these return values with actual output. It's simply the REPL's way of showing you that an expression returned a value. If you run the code from a file, you won't see these return values.

Summary

This chapter covered the basic building blocks of Python. You learned how to perform basic operations with Python objects. You also learned more about collections and how to use them to hold and access data. We'll go much deeper into these topics in the coming chapters. It's time to learn with our fingers and do some exercises to deepen our understanding of the basics.

Exercises

  1. Concatenate two strings, one with your first name and one with your last, to create a new string with your full name as its value. For example, if your name is John Doe, you should combine 'John' and 'Doe' to get 'John Doe'.

    Solution

    print('John' + ' ' + 'Doe')   # John Doe
    

    Video Walkthrough

    Please register to play this video

  2. This question may be a little challenging if your math skills are rusty. Don't be afraid to take advantage of the hints. Try your best to solve the problem, but don't feel compelled to complete it if you become frustrated.

    Use the REPL and the arithmetic operators to extract the individual digits of 4936:

    1. One place is 6.
    2. Tens place is 3.
    3. Hundreds place is 9.
    4. Thousands place is 4.

    Each digit may require multiple Python statements.

    It's easier to extract the digits right-to-left rather than left-to-right.

    number % 10 returns the rightmost digit of a number. You can use this repeatedly to extract all of the digits.

    Once you have the rightmost digit, how do you remove that digit from the number? If we start with 4936 and extract the 6, how do we now reduce our number to 493?

    You can remove the rightmost digit by using integer division.

    Solution

    >>> number = 4936
    >>> ones = number % 10
    >>> ones
    6
    
    >>> number = number // 10
    493
    
    >>> tens = number % 10
    >>> tens
    3
    
    >>> number = number // 10
    49
    
    >>> hundreds = number % 10
    >>> hundreds
    9
    
    >>> thousands = number // 10
    >>> thousands
    4
    

    Note that you don't need to use % for the thousands digit.

    Video Walkthrough

    Please register to play this video

  3. What does the following code do? Why?

    print('5' + '10')
    

    Solution

    The code prints 510. When used with string operands, + performs concatenation and returns a new string. We've merely joined '5' and '10' to produce '510'.

    Video Walkthrough

    Please register to play this video

  4. Refactor the code from the previous exercise to use coercion to print 15 instead.

    Solution

    print(int('5') + int('10'))
    

    Video Walkthrough

    Please register to play this video

  5. Will an error occur if you try to access a list element with an index greater than or equal to the list's length? For example:

    foo = ['a', 'b', 'c']
    print(foo[3])      # will this result in an error?
    

    Solution

    Yes, an error will occur: list index out of range. When you use an index value with no corresponding element, Python raises an IndexError error.

    Video Walkthrough

    Please register to play this video

  6. To what value does the following expression evaluate?

    'foo' == 'Foo'
    

    Solution

    It evaluates as False since the case matters when comparing strings.

    Video Walkthrough

    Please register to play this video

  7. What will the following code do? Why?

    int('3.1415')
    

    Solution

    It raises a ValueError since the string value 3.1415 does not represent a valid integer.

    Video Walkthrough

    Please register to play this video

  8. To what value does the following expression evaluate?

    '12' < '9'
    

    Solution

    The expression evaluates as True since the operands are strings, not numbers. Python performs a character-by-character comparison from left to right when comparing string values. Thus, on the first comparison, it determines that '1' < '9', so '12' must be less than '9'.

    If you used numbers instead of strings, the expression would be False.

    Video Walkthrough

    Please register to play this video