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:
def
statement creates a function.
()
to the function name.
We'll learn more about functions later. For now, please don't put too much effort into understanding them.
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.
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:
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.
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):
_
).
_
).
Idiomatic Names |
---|
foo |
answer_to_ultimate_question |
eighty_seven |
index_2 |
index2 |
Constant names (unchanging named values):
_
).
_
).
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:
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:
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.
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:
foo
is assigned the value of bar
.
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:
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.
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:
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:
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.
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.
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
.
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.
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.
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 |
π |
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
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 |
π |
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
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 |
Π |
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
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 |
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
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.
# 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
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.
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
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)
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
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.
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
Repeat the previous question but, this time, use augmented assignment to compute the final result, one year at a time.
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
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)
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