Variables

A fundamental tenet of programming is that programs must store information in memory to use and manipulate it. Variables are the means for doing that in almost all computer languages. They provide a way to label data with a descriptive name that helps us and other readers understand a program. Consider variables as labels that identify particular objects your program creates and uses.

We'll use a few "functions" in this chapter to ensure the code works as intended. Don't worry too much about how functions work. All you need to know for this chapter is:

  • The def statement creates a function.
  • You "call" a function by appending () to the function name.
  • A function can be called multiple times; each call executes the code in the function body.

We'll learn more about functions later. For now, please don't put too much effort into understanding them.

Variables and Variable Names

A variable is a label attached to a specific value stored in a program's memory. Typically, variables can be changed; we can connect the variable to a different value somewhere else in memory. For brevity, we often talk of assigning or reassigning an object to a variable. We also speak of variables as containing data, though that's not entirely accurate, as we'll learn later in this book.

Consider this code:

answer = 41
print(answer)       # 41

answer = 42
print(answer)       # 42

When Python sees line 1 of this code, it sets aside a bit of memory and stores the value 41 in that area. It also creates a variable named answer that we can use to access that value.

On line 4, we reassign the value 42 to the variable named answer. That is, Python makes answer refer to a new object. In particular, you must understand that we're not changing the object that represents 41 -- we're assigning an entirely new value to the answer variable.

Variable Naming

There are two hard problems in computer science: cache invalidation, naming things, and off-by-one errors.

-- Author unknown

Properly naming variables is traditionally viewed as one of the most challenging tasks in computer programming. If you're new to programming, this might seem odd. After all, how tough can it be to choose a name? As it turns out, it is challenging. Consider how much effort many new parents require to select a name for their baby.

A variable name must accurately and concisely describe the variable's data. In large programs, it's challenging to remember what each variable contains. If you use non-descriptive names like x, i, and dct, you may forget what data that variable represents. Other readers -- programmers -- must suss out the meaning for themselves. Therefore, when naming variables, think hard about the names. Ensure they are accurate, descriptive, and understandable to other readers. That might be you when you revisit the program a few months or years later.

Variable names are often referred to by the broader term identifiers. In Python, identifiers refer to several things:

  • Variable and constant names
  • Function and method names
  • Function and method parameter names
  • Class and module names

You'll meet all these things as you move through the Launch School Core curriculum. We'll often use the term identifier when discussing names in general: variables, constants, functions, classes, etc. We'll use one of the more specific terms when we need to limit the scope of a discussion.

Naming Conventions

Closely related to the stylistic conventions discussed in the Using Python chapter are the Python naming conventions. Names that follow these conventions are idiomatic. In contrast, those that do not are non-idiomatic (valid but not idiomatic) or illegal (your code will either raise a syntax error or do something unexpected).

  • Naming conventions for most identifiers (excluding constant and class names):

    • Use snake_case formatting for these names.
    • Names may contain lowercase letters (a-z), digits (0-9), and underscores (_).
    • Names should begin with a letter.
    • If the name has multiple words, separate the words with a single underscore (_).
    • Names that begin or end with one or two underscores have special meaning under the naming conventions. Don't use them until you understand how they are used.
    • Names may only use letters and digits from the standard ASCII character set.
    Idiomatic Names
    foo
    answer_to_ultimate_question
    eighty_seven
    index_2
    index2
  • Constant names (unchanging named values):

    • Use SCREAMING_SNAKE_CASE formatting for these names.
    • Names may contain uppercase letters (A-Z), digits (0-9), and underscores (_).
    • Names should begin with a letter.
    • If the name has multiple words, separate the words with a single underscore (_).
    • Names that begin or end with one or two underscores have special meaning under the naming conventions. Don't use them until you understand how they are used.
    • Names may only use letters and digits from the standard ASCII character set.
    Idiomatic Names
    FOO
    ANSWER_TO_ULTIMATE_QUESTION
    EIGHTY_SEVEN
    INDEX_2
    INDEX2

    The constant naming convention is advisory only. Python has no way to ensure that constants are never changed. Whether a constant ever changes is entirely up to the programmer, not Python.

  • Class names:

    • Use PascalCase formatting for these names. PascalCase is sometimes called CamelCase (with both Cs capitalized).
    • Names may contain uppercase and lowercase letters (A-Z, a-z) and digits (0-9).
    • Names should begin with an uppercase letter.
    • If the name has multiple words, capitalize each word.
    Idiomatic Names
    Foo
    UltimateQuestion
    FourLeggedPets
    PythonVersion2Rules

    We'll meet a few class names in this book but will leave a full discussion for our Object Oriented Programming in Python book.

Note that many non-idiomatic names are still legal (valid) Python identifiers. Sometimes, it makes sense to break the rules. However, not all identifiers are legal; here are some things you must do:

  • You can use letters, digits, and underscores in Python identifiers. Extended ASCII and Unicode letters and digits are allowed.
  • You may not use punctuation characters, most special characters, or whitespace.
  • You may not start identifiers with a digit.
  • You may not use Python's reserved words such as if, def, while, return, and pass as names.
Non-Idiomatic Names Explanation
fourWayIntersection camelCase
Schön Extended ASCII
Illegal Names Explanation
pass Reserved word
3xy Starts with digit
ultimate-question Hyphen
one two three Whitespace
is_lowercase? Punctuation
is+lowercase Special character

Most illegal names will raise an error. However, if the illegal name looks like valid Python, you won't get an error:

first,last = ['Mary', 'Wonder']

That example creates two variables, first and last, and assigns them to Mary and Wonder, respectively.

Creating and Reassigning Variables

We create (initialize) most variables by simply giving them a value. That happens as part of an assignment statement:

forename = 'Clare'       # initialization (also called assignment)

Initializations are also said to provide a definition for the variable.

No special keywords are required to define variables. We can also give new values to variables by simply reassigning them:

forename = 'Clare'       # initialization (also called assignment)
# omitted code
forename = 'Victor'      # reassignment

An assignment like foo = bar can be described in two main ways:

  1. The variable foo is assigned the value of bar.
  2. The value of bar is assigned to the variable foo.

If bar is a literal value, you can skip mentioning "value of".

Both phrases mean the same thing, but phrase 1 is usually preferred. However, phrase 2 is perfectly acceptable. Think of it like this:

  • Kim has been assigned the task of building safety coordinator.
  • The task of building safety coordinator has been assigned to Kim.

Both statements mean the same thing, and so it is with variables and values.

Note that the same phrasing can be used when talking about reassignment: a variable can be reassigned to a new value, or a value can be reassigned to a variable.

At Launch School, we mainly use phrase 1. However, don't be confused if we sometimes use phrase 2. Sometimes, phrase 2 just reads better.

How Initialization and Reassignment Work

When you initialize a variable, Python gives it an initial value and sticks that value somewhere in the computer's memory. It also allocates a small amount of memory for the variable itself, then places the value's memory address into the variable's spot in memory.

For instance, consider this code:

foo = 'abcdefghi'

When Python encounters this code, it will create the string 'abcdefghi' somewhere in memory. Let's assume Python stores the string at memory address 73420. Next, it creates a variable somewhere else in memory. Let's suppose the variable is at address 10230. It then associates address 10230 with the name foo. Python stores the address of the string (73420) at address 10230. Thus, we get a situation that looks like this:

Assigned variables

Consider what happens when we later run this reassignment:

foo = 'Hello'

Python creates the new string 'Hello' somewhere in memory. We can assume that 'Hello' is stored at memory address 87160. Since we already have a foo variable, Python simply replaces the value at address 10230 with 87160. This breaks the connection with the original string and establishes a new one with the new string. Things now look like this:

Reassigned variables

Creating Constants

Constants are created (initialized) in the same way as variables: by giving them a value. That happens as part of an assignment statement:

PINING_FOR = 'fjords'         # initialization

As with variable creation, no keywords are required to create constants. However, you should use SCREAMING_SNAKE_CASE in observation of the constant naming convention.

Since constants should be unchanging, you should never reassign a constant.

Constants are usually defined in the global scope. The definitions are usually written at the top of the program file, just below any imports. You can use constants anywhere, but they should be defined globally. Thus, you can use constants in functions, but you shouldn't create a constant inside a function.

Constants aren't constant in Python. Python does not support true constants. Instead, the SCREAMING_SNAKE_CASE naming convention is solely for programmers. For instance, the name FIRST_BASE meets the naming conventions for constants, so you should think of FIRST_BASE as a constant. However, since Python places no constraints on such names, you can easily change the value assigned to FIRST_BASE. Changing a constant's value is poor practice and may make you unpopular at work.

Expressions and Assignment

Assignment and reassignment are often more complex than simply assigning a literal value to a variable. Assignment and reassignment often use expressions on the right side of the = to determine the desired value. For instance:

left_side = 5
right_side = 32
total = left_side + right_side  # total = 37
print(total)                    # prints 37

This code starts with two simple assignments that initialize the left_side and right_side variables to 5 and 32, respectively. Next, we add the values of left_side and right_side together and assign the result to total, which we then print.

Here's an example that uses a function call to compute the resulting value for the assignment:

def square(number):
    return number * number

forty_two_squared = square(42)
print(forty_two_squared)                # 1764

The expression on the right side of the = operator can be any valid expression. It can be as simple or complex as needed, though you should strive to keep them readable and easy to understand.

The variable to the left of the = operator is always the target variable for the resulting value. That is, the expression's value will be assigned to the variable.

It's worth noting that the right side of an assignment is always completely evaluated before assigning the result to the variable. That means you can write code like this:

foo = 42            # foo is 42
foo = foo - 2       # foo is now 40
foo = foo * 3       # foo is now 120
foo = foo + 5       # foo is now 125
foo = foo // 25     # foo is now 5
foo = foo / 2       # foo is now 2.5
foo = foo**3        # foo is now 15.625
print(foo)          # prints 15.625

In lines 2-7, the right side of each assignment is computed first using whatever value foo had most recently. Thus, foo was 42 on line 2, 40 on line 3, and so on. On each line, a computation is performed using the current value of foo. Python then reassigns the newly computed value to foo.

You can also use expressions to initialize constants. However, this is less common than evaluating expressions for a variable.

Augmented Assignment

The assignments at the end of the previous section all follow a similar pattern: they take the current value of a variable, perform an arithmetic operation on the variable's value, and then reassign the variable to the newly computed value. This sort of operation is so common that most languages have a shorthand notation called augmented assignment or assignment operators:

foo = 42            # foo is 42
foo -= 2            # foo is now 40
foo *= 3            # foo is now 120
foo += 5            # foo is now 125
foo //= 25          # foo is now 5
foo /= 2            # foo is now 2.5
foo **= 3           # foo is now 15.625
print(foo)          # prints 15.625

Each statement is noticeably shorter than the original, but the results are identical. While there's little or no performance benefit to augmented assignment, most developers find the syntax more readable, and you're less likely to misspell one of the variable names.

Augmented assignment also works with string concatenation and repetition. In fact, it works with any type that supports the various operators.

bar = 'xyz'          # bar is 'xyz'
bar += 'abc'         # bar is now 'xyzabc'
bar *= 2             # bar is now 'xyzabcxyzabc'
print(bar)           # prints xyzabcxyzabc
bar = [1, 2, 3]     # bar is [1, 2, 3]
bar += [4, 5]       # + with lists appends
                    # bar is now [1, 2, 3, 4, 5]
print(bar)          # prints [1, 2, 3, 4, 5]
bar = {1, 2, 3}     # bar is {1, 2, 3}
bar |= {2, 3, 4, 5} # | performs set union
                    # bar is now {1, 2, 3, 4, 5}
bar -= {2, 4}       # - performs set difference
                    # bar is now {1, 3, 5}
print(bar)          # prints {1, 3, 5}

Augmented assignment also works with constants. However, as with constant reassignment, it is poor practice.

Note that augmented assignment is a statement, not an expression. Thus, you can't use augmented assignment as a function argument or return value:

def foo(bar):
    print(bar)

a = 3
foo(a *= 2)
#     ^^
# SyntaxError: invalid syntax
def foo():
    a = 3
    return a *= 2
#            ^^
# SyntaxError: invalid syntax

If you study the above examples, you may get the impression that a += b is equivalent to a = a + b. If the left-side variable (e.g., a) is immutable, that's true. However, if a is mutable, the expression may not be equivalent to a = a + b. For instance, if a and b are both lists, then a += b is actually equivalent to a.extend(b). The resulting values are the same, but we'll see later why this behavior is fundamentally different from a = a + b.

Reassignment vs. Mutation

There are two ways to change things in Python and most other programming languages. You can change the binding of the variable by making it reference a new object, or you can change the value of the object assigned (bound) to the variable. The former is known as reassignment while the latter is known as mutation.

A variable is a name that refers to an object at a specific place in memory. Reassignment makes that name refer to a different object somewhere else in memory. Mutation, on the other hand, does not change which object the variable refers to. Instead, it changes the object itself. After mutating an object assigned to a specific variable, the variable continues to refer to the same object (albeit altered) at the same memory location.

This distinction is crucial in any language that permits reassignment and mutation, as it significantly impacts how those languages work. One subtle but important distinction is the difference between reassigning a variable and reassigning an element in a list, dict, or other mutable collection. Reassigning an element of a mutable collection doesn't reassign the variable; it mutates the collection.

Here are some examples illustrating the differences between reassignment and mutation. Pay close attention, as this is a crucial concept.

num = 3               # assignment (initialization)
my_list = [1, 2, 3]   # assignment (initialization)
my_dict = {           # assignment (initialization)
    'a': 1,
    'b': 2,
}

num = 42              # Reassignment
my_list[1] = 42       # Reassignment of element,
                      # my_list is mutated!
my_dict['b'] = 3      # Reassignment of dict pair
                      # my_dict is mutated!

# You can still reassign the variables
my_list = [2, 3, 4]   # Reassignment
my_dict = { 'x': 0 }  # Reassignment

Things get a little fuzzy when using augmented assignment. As mentioned earlier, a += b is equivalent to a = a + b if a is immutable. In that case, then augmented assignment is a form of reassignment.

However, if a is mutable, it may be (and frequently is) mutated in augmented assignments. As we mentioned earlier, a += b is equivalent to a.extend(b) if a and b are lists. Thus, a += b is not reassignment; it is a mutating operation. This distinction becomes crucial when we discuss variables as pointers.

As much as possible, we use the terms mutation or mutate to describe any mutating operation. We'll talk more about these concepts throughout this book and in almost every course at Launch School. For now, keep the distinction between reassignment and mutation in mind.

Summary

In this chapter, we've learned how to use variables and constants to store information for later use. In particular, we've learned what variables and constants are and how we use and manipulate them. We've also learned how to assign and reassign variables and how to use augmented assignment.

Mutation and reassignment are two more fundamental concepts. Understanding mutation will help you catch, fix, and avoid an entire class of bugs.

Let's apply that knowledge in some exercises.

Exercises

  1. Classify the following potential non-constant variable names as idiomatic, non-idiomatic, or illegal. For the non-idiomatic and illegal names, explain your choice.

    Name
    index
    CatName
    lazy_dog
    quick_Fox
    1stCharacter
    operand2
    BIG_NUMBER
    π

    Solution

    Name Status Explanation
    index Idiomatic
    CatName Non-idiomatic Should not use uppercase letters
    lazy_dog Idiomatic
    quick_Fox Non-idiomatic Should not use uppercase letters
    1stCharacter Illegal Should not begin with a digit
    operand2 Idiomatic
    BIG_NUMBER Non-idiomatic Should not use uppercase letters
    π Non-idiomatic π is not an ASCII character

    Video Walkthrough

    Please register to play this video

  2. Classify the following potential function names as idiomatic, non-idiomatic, or illegal. For the non-idiomatic and illegal names, explain your choice.

    Name
    index
    CatName
    lazy_dog
    quick_Fox
    1stCharacter
    operand2
    BIG_NUMBER
    π

    Solution

    Since function names in Python follow the same conventions as variable names, the answer to this question is the same as the previous question.

    Video Walkthrough

    Please register to play this video

  3. Classify the following potential constant names as idiomatic, non-idiomatic, or illegal. For the non-idiomatic and illegal names, explain your choice.

    Name
    index
    CatName
    snake_case
    LAZY_DOG3
    1ST
    operand2
    BIG_NUMBER
    Π

    Solution

    Name Status Explanation
    index Non-idiomatic Should not use lowercase letters
    CatName Non-idiomatic Should not use lowercase letters
    snake_case Non-idiomatic Should not use lowercase letters
    LAZY_DOG3 Idiomatic
    1ST Illegal Should not begin with a digit
    operand2 Non-idiomatic Should not use lowercase letters
    BIG_NUMBER Idiomatic
    Π Non-idiomatic Π is not an ASCII character

    Video Walkthrough

    Please register to play this video

  4. Classify the following potential class names as idiomatic, non-idiomatic, or illegal. For the non-idiomatic and illegal names, explain your choice.

    Name
    index
    CatName
    Lazy_Dog
    1ST
    operand2
    BigNumber3
    Πi

    Solution

    Name Status Explanation
    index Non-idiomatic Should not begin with a lowercase letter
    CatName Idiomatic
    Lazy_Dog Non-idiomatic Should not use underscores
    1ST Illegal Should not begin with a digit
    operand2 Non-idiomatic Should not begin with a lowercase letter
    BigNumber3 Idiomatic
    Πi Non-idiomatic Π is not an ASCII character

    Video Walkthrough

    Please register to play this video

  5. Write a program named greeter.py that greets 'Victor' three times. Your program should use a variable and not hard code the string value 'Victor' in each greeting. Here's an example run of the program:

    $ python greeter.py
    Good Morning, Victor.
    Good Afternoon, Victor.
    Good Evening, Victor.
    

    Solution

    # This solution uses concatenation
    name = 'Victor'
    print('Good Morning, ' + name + '.')
    print('Good Afternoon, ' + name + '.')
    print('Good Evening, ' + name + '.')
    
    # This solution uses f-string interpolation
    name = 'Victor'
    print(f'Good Morning, {name}.')
    print(f'Good Afternoon, {name}.')
    print(f'Good Evening, {name}.')
    

    First, we create a variable that holds the value 'Victor'. With a variable, we don't need to hard code 'Victor' each time we greet him. Instead, we can use the variable as part of an expression. In our first solution, we use string concatenation; in the second, we use f-string interpolation.

    Video Walkthrough

    Please register to play this video

  6. Write a program named age.py that includes someone's age and then calculates and reports the future age 10, 20, 30, and 40 years from now. Here's the output for someone who is 22 years old.

    You are 22 years old.
    In 10 years, you will be 32 years old.
    In 20 years, you will be 42 years old.
    In 30 years, you will be 52 years old.
    In 40 years, you will be 62 years old.
    

    Solution

    age = 22
    print(f'You are {age} years old.')
    print(f'In 10 years, you will be {age + 10} years old.')
    print(f'In 20 years, you will be {age + 20} years old.')
    print(f'In 30 years, you will be {age + 30} years old.')
    print(f'In 40 years, you will be {age + 40} years old.')
    

    Video Walkthrough

    Please register to play this video

  7. What happens when you run the following code? Why?

    NAME = 'Victor'
    print('Good Morning, ' + NAME)
    print('Good Afternoon, ' + NAME)
    print('Good Evening, ' + NAME)
    
    NAME = 'Nina'
    print('Good Morning, ' + NAME)
    print('Good Afternoon, ' + NAME)
    print('Good Evening, ' + NAME)
    

    Solution

    The program first greets Victor 3 times. It then greets Nina 3 times.

    Unfortunately, Python doesn't have real constants. There's no way to prevent the reassignment of NAME. If this faux-constant really needs to be changed, you should use a regular variable instead:

    name = 'Victor'
    print('Good Morning, ' + name)
    print('Good Afternoon, ' + name)
    print('Good Evening, ' + name)
    
    name = 'Nina'
    print('Good Morning, ' + name)
    print('Good Afternoon, ' + name)
    print('Good Evening, ' + name)
    

    Video Walkthrough

    Please register to play this video

  8. Challenge: This program uses a bit of math. Don't let that scare you away -- try it anyway.

    Assume you have $1,000.00 in the bank, and you've somehow managed to find a bank that pays you 5% (this is a wish-fulfillment fantasy) compounded interest every year. After one year, you will have $1,050 ($1,000 times 1.05). After two years, you will have $1,050 times 1.05, or $1102.50. Create a variable named balance that contains the amount of money you will have after 5 years, then print the result. Use a single expression if you can to set balance to the correct value.

    Solution

    balance = (1000.00 * 1.05 * 1.05 * 1.05
                       * 1.05 * 1.05)
    print(balance)
    

    The program prints 1276.2815625000003 as the final balance. That's not precisely correct. The actual value should be 1276.2815625. That's close enough when you consider the imprecision of using floats.

    Video Walkthrough

    Please register to play this video

  9. Repeat the previous question but, this time, use augmented assignment to compute the final result, one year at a time.

    Solution

    balance = 1000.00
    balance *= 1.05
    balance *= 1.05
    balance *= 1.05
    balance *= 1.05
    balance *= 1.05
    print(balance)
    

    This program also prints 1276.2815625000003 as the final balance. Once again, that's close enough.

    Video Walkthrough

    Please register to play this video

  10. Assume that obj already has a value of 42 when the code below starts running. Which of the subsequent statements reassign the variable? Which statements mutate the value of the object that obj references? Which statements do neither? If necessary, you can read the documentation.

    obj = 'ABcd'
    obj.upper()
    obj = obj.lower()
    print(len(obj))
    obj = list(obj)
    obj.pop()
    obj[2] = 'X'
    obj.sort()
    set(obj)
    obj = tuple(obj)
    

    Solution

    The comments in the following code show whether reassignment, mutation, or neither took place on each line of code:

    obj = 'ABcd'      # Reassignment
    obj.upper()       # Neither
    obj = obj.lower() # Reassignment
    print(len(obj))   # Neither
    obj = list(obj)   # Reassignment
    obj.pop()         # Mutation
    obj[2] = 'X'      # Mutation
    obj.sort()        # Mutation
    set(obj)          # Neither
    obj = tuple(obj)  # Reassignment
    

    A simple assignment like var = something is always either an initialization or a reassignment. Since obj has already been initialized (it has a value of 42 before this code was reached), lines 1, 3, 5, and 10 perform reassignment. In a few situations, mutation and reassignment can happen in the same statement. None of the above statements do both.

    obj.upper does not mutate the caller, so line 2 does neither. Likewise, since print, len, and set don't mutate their arguments, lines 4 and 9 are neither.

    The remaining statements all mutate the object referenced by obj. pop removes the last element of the list. obj[2] = 'X' reassigns the element at index 2, but it mutates obj itself. Finally, sort mutates the object when it performs an in-place sort.

    Video Walkthrough

    Please register to play this video