Variables - Naming, Assignment, and Rebinding
Context
A variable is a name attached to a value, so the value can be referred to later by that name instead of being written out again. Attaching a name is a single action, and the same name can be pointed at a different value afterwards.
Names are how a script stays readable while doing something long. A cleaning pass gives a name to the raw rows it read, another to the rows that survived filtering, and another to the count it will report, and each of those names says what the value means at that point. When a later step is wrong, the names are what let you find which stage produced the bad value.
Without names, every intermediate result has to be recomputed or pasted in place, and a script becomes a wall of repeated expressions nobody can follow or correct. Rebinding is the specific trap, because a name pointed at a new value forgets the old one completely and silently, so a total accumulated across a loop can be wiped out by one careless reuse of the same name. The previous lesson established what values are, this lesson gives them somewhere to live.
Goal
- Store a value under a name using the
=assignment statement. - Rebind an existing name to a new value and predict what the name holds afterward.
- Explain why Python needs no type declaration, in contrast to languages that do.
- Choose names that obey Python's legal-identifier rules and follow its readability conventions.
- Demonstrate that one name can hold values of different types over its lifetime.
Prerequisites
Neither of these is required and nothing below assumes you opened them. They are here only if you would rather arrive with the picture already in mind, and Theory builds it from nothing either way.
- YouTube - "Python Variables - Python Tutorial for Beginners with Examples | Mosh" by Programming with Mosh. Seven minutes on creating variables and naming them well. Watch for one mental picture, a name as a label attached to a value.
- Official docs - Python Language Reference, "Identifiers and keywords". The precise rules for what text makes a legal name, plus the reserved-word list.
Table of Contents
- Variable
- Assignment
- Identifier and Naming Rules
- Rebinding
- Dynamic Typing
Theory
Variable
What. A variable is a name that refers to a value, a label you attach to a piece of data so you can reach it again later by that name. Why. Without names you would have to retype every value everywhere you need it, and you could never store a computed result. Names are how a program remembers and reuses its data. When. Create a variable the moment a value needs to be reused, stored for later, or given a meaningful name that explains what it is.
The crucial mental model: a Python variable is not a box that holds the value. It is a name tag pointing at a value that lives elsewhere in memory. The name and the value are separate things, and the name simply points at the value. This picture explains everything else in the lesson.
# 'batch_size' is a NAME that now points at the integer value 64.
batch_size = 64
# Using the name anywhere stands in for the value it points at.
print(batch_size)The name is a convenience for you, the value is the real data, and the link between the two is what a variable is.
Because the name only points at the value, several names can point at the same value, and a name can be moved to point at a different value later without disturbing the original. That flexibility is the whole reason variables are useful, and it is what the rest of this lesson builds on.
DON'T:
- DON'T picture a variable as a container "holding" the value, picture a sticky label pointing at it, this avoids many later confusions.
- DON'T use a name before assigning it,
print(undefined_name)raises an error, because no such label exists yet.
Assignment
What. Assignment is the = statement that binds a name to a value: the name on the left becomes a label for the value the right-hand side produces.
Why. It is the most common way a variable comes into existence, and the way a computed result you keep gets its name.
When. Use = whenever you want to give a value a name, to store an input, capture a calculation, or set up a starting value.
Assignment is the canonical example of a statement from Lesson 1.1.1: it performs an action (the binding) and produces no usable value of its own. The right-hand side is evaluated first, it can be a literal, or a whole expression, and the resulting value is what the name is bound to.
# The right side is a literal, 'epochs' is bound to the value 10.
epochs = 10
# The right side is an EXPRESSION, it is evaluated first to 0.005, then bound.
learning_rate = 0.01 / 2
# The right side can reference earlier names, '10 * 100' = 1000 is bound to 'steps'.
steps = epochs * 100The direction is always right-to-left: evaluate the right-hand side completely, then attach the left-hand name to the result. Reading = as "becomes" rather than "equals" keeps this straight.
This right-then-bind order is why = is not the == of mathematics. x = x + 1 is not a contradiction, it means "evaluate x + 1, then make x point at that new result." The single = performs an action, the double == (a later lesson) asks a question.
DON'T:
- DON'T read
=as mathematical equality, it is the action "bind this name to this value," not a claim that two sides are equal. - DON'T reverse the sides,
64 = batch_sizeis an error, the name to be bound must be on the left.
Identifier and Naming Rules
What. An identifier is the actual text of a variable's name. The naming rules are the hard limits on what text is legal, plus the soft conventions on what text is readable. Why. Break the hard rules and your code will not run at all, and a hyphen fails in a misleading way, because Python reads it as a subtraction. Ignore the soft conventions and your code runs but becomes unreadable, and clear names are the cheapest documentation you will ever write. When. Every single time you invent a name, which is constantly, so internalizing these rules now pays off on every line you write afterward.
The hard rules, enforced by the interpreter, are short. A legal identifier must start with a letter or an underscore _, may then contain letters, digits, and underscores, contains no spaces or hyphens, and may not be one of Python's reserved keywords (words like if, for, True, None, class that the language reserves for itself). Names are case-sensitive, so Rate and rate are two different names.
# Legal: starts with a letter, only letters/digits/underscores after.
learning_rate = 0.01
# Legal: may start with an underscore.
_private_count = 0
# Legal: digits are allowed AFTER the first character.
layer2_units = 128
# ILLEGAL examples (do NOT run these, they are syntax errors):
# 2nd_layer = 5 # cannot START with a digit
# learning-rate = 5 # hyphen is not allowed (it reads as subtraction)
# class = 5 # 'class' is a reserved keywordThe three commented-out lines are the classic violations, and the hyphen is the one worth a second look: Python does not reject learning-rate as a malformed name at all, it reads it as the expression learning minus rate, which is why the error complains about assigning to an expression instead of naming a bad name. You can see the full keyword list at any time with the standard-library keyword module, which ships with Python, so no installation is needed.
# 'keyword' is part of the standard library, no install needed, just import it.
import keyword
# 'kwlist' is a list of every reserved word you may NOT use as a name.
print(keyword.kwlist)The printed list holds every reserved word, False, None, and, def, for, if, return and the rest, and if a name you want appears there, pick a different one. Beyond the hard rules the convention for a variable's name is snake_case: all lowercase, words joined by underscores, and descriptive, so learning_rate rather than lr or x. A name like n_correct_predictions documents itself, which is exactly what the data-cleaning scripts ahead will depend on. A class is one exception, named in CapWords, as Dataset is in Lesson 1.8.1, and a constant is the other, written in UPPER_CASE, as LEARNING_RATE is in Lesson 1.3.3.
DON'T:
- DON'T start a name with a digit or include a hyphen or space,
2nd,learn-rate, andlearn rateare all illegal. - DON'T name things
x,data,temp, ordf1when a descriptive name fits, vague names are debt you pay back later with interest.
Rebinding
What. Rebinding is pointing an existing name at a different value with another assignment, the old value is forgotten by that name, and the name now refers to the new value. Why. Programs change state as they run: a counter increments, a "best score so far" improves, a placeholder gets filled. Rebinding is how a name keeps up with the latest value. When. Rebind whenever a variable's value should be updated, accumulating a total, replacing a placeholder, or overwriting an old result with a new one.
Because a variable is a name tag, not a box, rebinding simply moves the tag from one value to another. The original value is untouched, the name just stops pointing at it. If nothing else points at the old value, Python eventually cleans it up, but that is automatic and not your concern.
# 'best_score' starts pointing at 0.0.
best_score = 0.0
# Rebinding: the SAME name now points at 0.85, the old 0.0 is forgotten by this name.
best_score = 0.85
# Rebinding can use the name's own current value on the right side.
best_score = best_score + 0.05
print(best_score)That last line prints 0.9, and it is the pattern to hold on to. The right-hand side best_score + 0.05 is evaluated first, using the current value 0.85, and only then is best_score rebound to the result. This "read the old value, compute the new one, then move the name" sequence is everywhere in real code, in counters, running totals and accumulators alike.
This pattern is so common that Python offers a shorthand called augmented assignment: for a number, best_score += 0.05 gives the same result as best_score = best_score + 0.05, though on a list += changes the list in place, so every other name for it sees the change. The += does the read-add-rebind in one compact step, and there are matching -=, *=, and /= forms.
DON'T:
- DON'T think rebinding
best_score = 0.85alters the old0.0, it does not, it only moves the name to a new value. - DON'T read
best_score = best_score + 0.05as circular, the right side is fully evaluated before the name is rebound.
Dynamic Typing
What. Dynamic typing means a name is not tied to any type, it can point at an int now and a str later, and the value's type is tracked by the value, not by the name.
Why. It is why Python needs no type declarations: you never announce "this name will hold an integer." You just assign, and the name takes on whatever you give it. This makes Python fast to write and flexible.
When. This is always in effect, every assignment you write relies on it. You especially notice it when a name's type changes across rebindings.
Many languages (C, Java) are statically typed: you must declare a variable's type up front, and it can never hold anything else. Beginners often arrive expecting this. Python is the opposite, the value carries the type (as you saw with type() last lesson), and the name is just a flexible label that can be moved to a value of any type at all.
# 'result' points at an int, type() would report <class 'int'>.
result = 64
print(type(result))
# Rebinding the SAME name to a string, its type is now str, no declaration needed.
result = "converged"
print(type(result))
# Rebinding again to None, the name happily takes on NoneType.
result = None
print(type(result))The single name result held three different types in turn, and at no point did you declare anything, you simply assigned. The type travels with the value, so a name rebound to a new value inherits that value's type automatically.
This flexibility is useful but also a responsibility. Because nothing stops a name from changing type, a name that means "a count" should keep holding counts, silently turning n_samples from an integer into a string mid-script is legal but is a recipe for confusing bugs. Use the freedom deliberately, not accidentally.
DON'T:
- DON'T expect a "declaration" step, there is none in Python, the first assignment both creates the name and gives it a value.
- DON'T change a name's type carelessly mid-program, it is allowed, but a
countthat suddenly holds text will confuse you and everyone reading the code.
Lab Exercises
Both problems first, then both solutions. Do not peek.
Lab Exercise 1: Names You May Write, and Names You May Not
Covers the first half (Variable, Assignment, Identifier and Naming Rules).
In the REPL, bind three variables with descriptive names: a learning rate of 0.001, a batch size of 32, and a model name of "transformer". Print each one. Then use batch_size inside an expression by printing batch_size * 4, to confirm that using a name stands in for the value it points at. Note that you never declared a single type.
Then decide, for each of these candidate names, whether it is a legal Python identifier, and say why for each illegal one: n_epochs, 2nd_model, learning_rate, model-name, _cache, for, Accuracy. Finally, import keyword and use keyword.kwlist to confirm which one of those seven is a reserved word rather than merely badly formed.
Lab Exercise 2: The Accumulator and the Shapeshifter
Covers the second half (Rebinding, Dynamic Typing).
Create accumulate.py. Bind total_loss = 0.0, then three times in a row rebind it to add a new batch loss, first 0.5, then 0.3, then 0.2, writing each one out in full as total_loss = total_loss + .... Print total_loss after each rebind so you can watch it grow, and confirm the final value is 1.0. In a comment, explain why the right-hand side has to be evaluated before the name is rebound.
Then, in the same file, show that a name is not tied to a type. Bind value = 100 and print it with its type(), then rebind value in turn to 3.14, to "loss", to True, and to None, printing the value and its type() after each. Finally, bind steps = 0 and apply steps += 1 three times before printing it, and state in a comment what the += line does that the accumulator's long form was doing in three separate pieces.
Solution 1: Names You May Write, and Names You May Not
# Descriptive names, and no type declaration is ever written.
learning_rate = 0.001
batch_size = 32
model_name = "transformer"
# Using a name retrieves the value it points at.
print(learning_rate) # 0.001
print(batch_size) # 32
print(model_name) # transformer
# A name can be used inside an expression, 'batch_size' stands in for 32.
print(batch_size * 4) # 128Now the identifiers:
# Legal: starts with a letter, only letters/digits/underscores follow.
n_epochs = 10
# Legal: lowercase, descriptive, underscores.
learning_rate = 0.01
# Legal: a name may start with an underscore.
_cache = None
# Legal: names are case-sensitive, so a capital 'A' is allowed.
Accuracy = 0.9
# ILLEGAL (do NOT run, each is an error):
# 2nd_model = 0 # cannot START with a digit
# model-name = 0 # the hyphen reads as subtraction, not as part of a name
# for = 0 # 'for' is a reserved keyword
# 'keyword' is standard-library, no install needed.
import keyword
# Is 'for' reserved, or just badly formed like the other two?
# 'for' is in the printed list, 'model' is not.
print(keyword.kwlist)n_epochs, learning_rate, _cache and Accuracy are all legal. The three illegal ones fail for three different reasons, and the difference matters. 2nd_model breaks the rule about the first character, and model-name is not a malformed name at all, it is a subtraction between two names. Only for is illegal because the word itself is reserved, which is why for appears in the printed keyword.kwlist and model does not.
DON'T:
- DON'T rely on
Accuracywith a capital just because it is legal, the convention is lowercaseaccuracy, and mixing cases invites typo bugs that read as missing names. - DON'T guess whether a word is reserved, check
keyword.kwlistwhen unsure. - DON'T quote the number,
batch_size = "32"makes it text, andbatch_size * 4would then repeat the text instead of multiplying it.
Solution 2: The Accumulator and the Shapeshifter
# Start the running total at zero, a float, since losses are decimals.
total_loss = 0.0
# Each line evaluates the RIGHT side first, then rebinds 'total_loss'.
total_loss = total_loss + 0.5 # right side: 0.0 + 0.5, then rebind
print(total_loss) # 0.5
total_loss = total_loss + 0.3 # right side: 0.5 + 0.3, then rebind
print(total_loss) # 0.8
total_loss = total_loss + 0.2 # right side: 0.8 + 0.2, then rebind
print(total_loss) # 1.0
# The right side is evaluated using the name's CURRENT value, and only then is
# the name moved to the result. If it happened the other way round the old value
# would be gone before it could be read, and the total could never accumulate.
# A name freely takes on whatever type the value has, with no declaration.
value = 100
print(value, type(value)) # 100 <class 'int'>
value = 3.14
print(value, type(value)) # 3.14 <class 'float'>
value = "loss"
print(value, type(value)) # loss <class 'str'>
value = True
print(value, type(value)) # True <class 'bool'>
value = None
print(value, type(value)) # None <class 'NoneType'>
# Augmented assignment: 'steps += 1' is the read, the add and the rebind at once.
steps = 0
steps += 1
steps += 1
steps += 1
print(steps) # 3Run it:
# Accumulate three losses, then walk one name through five types.
python3 accumulate.pyThe accumulator prints 0.5, 0.8, 1.0, and the five type lines show value holding int, float, str, bool and NoneType in turn with nothing declared anywhere. steps += 1 reaches 3, and what it compacts is exactly the three-part move the accumulator spelled out in full: read the current value, apply the operation, rebind the name. Same mechanism, one line instead of one line and a mental note.
DON'T:
- DON'T expect
total_loss = total_loss + 0.5to be circular or to error, the old value is read before the new one is bound. - DON'T start the total at an integer
0when every addition is a float, it works, but0.0makes the intent obvious at a glance. - DON'T change a name's type this freely in real code, here it demonstrates dynamic typing, in practice a name should keep one consistent meaning.
Theory Assessment
Conceptual reinforcement, not a coding section. All four questions with their hints first, then all four answers.
Q1: The Name, the Value, and the Equals Sign
Two parts. First, after you run batch_size = 64, explain the difference between the name batch_size and the value 64, using the label mental model. Second, a beginner reads learning_rate = 0.01 / 2 as a mathematical equation and is confused by it, explain what = actually does here and in what order the line executes.
Hint: For the first part, one of the two is data living in memory and the other is a tag pointing at it. For the second, the right side is finished completely before anything at all happens to the name on the left.
Q2: Spot the Illegal Names
Of these, which are illegal Python identifiers, and why is each one illegal: 3layers, hidden_units, model name, _temp, return?
Hint: Check four things in order, the first character, spaces, hyphens, and the reserved-keyword list. The illegal ones do not all fail for the same reason.
Q3: Trace the Rebinds, Then Change the Type
Two parts on the same mechanism. First, trace the value of score line by line, say what it holds at the end, and explain why the second line is not circular:
score = 10
score = score + 5
score = score * 2Second, after count = 5 and then count = "five", state what type(count) reports and what that reveals about where Python tracks a type.
Hint: Each line evaluates its right side using the current value before rebinding. For the second part, ask whether the name or the value was ever carrying the type.
Q4: Two Tags on One Value
Read this carefully before answering:
base_lr = 0.01
current_lr = base_lr
current_lr = current_lr / 10State what base_lr and current_lr each hold after the third line, and explain the result in terms of the label model rather than by describing what you think happened. Then say what the answer would have to be if a variable really were a box holding a value, whether that reading gets this example right, and what it has no way to express. Finally, if the fourth line were current_lr = "scheduled", say what type(base_lr) would report afterwards and why.
Hint: Line 2 does not copy anything. Ask precisely what it points the second name at, then ask what line 3 moves and what it leaves alone.
Answer 1:
After batch_size = 64, the value 64 is the actual integer data living in memory, and batch_size is a name, a label, that points at that value. They are separate things: the value exists independently, and the name is merely a tag you use to reach it, so using batch_size anywhere substitutes in the value it currently points at.
= is not mathematical equality, it is the assignment action "bind the name on the left to the value the right-hand side produces". The line executes right-to-left: Python first evaluates the expression 0.01 / 2 to get 0.005, and only then makes learning_rate point at that result. Reading = as "becomes" keeps this clear.
DON'T picture the name as a box holding 64, it is a tag pointing at a value stored elsewhere. And DON'T read = as a claim that both sides are equal, that is what == is for.
Answer 2:
3layers is illegal because it starts with a digit, and a legal identifier must begin with a letter or an underscore. model name is illegal because it contains a space, so Python reads it as two separate things rather than one name. return is illegal for a different reason entirely: it is perfectly well-formed text, but the word is a reserved keyword that the language has claimed for itself.
hidden_units and _temp are both legal, each starting with a letter or an underscore and containing only letters, digits and underscores.
DON'T assume a space or a hyphen is allowed "if it reads nicely", both break the identifier rules, and a hyphen breaks them in the confusing way, by turning into a subtraction rather than an obviously bad name.
Answer 3:
# Bind 'score' to 10.
score = 10 # score -> 10
# Right side 10 + 5 = 15 is computed first, then 'score' is rebound.
score = score + 5 # score -> 15
# Right side 15 * 2 = 30 is computed first, then 'score' is rebound.
score = score * 2 # score -> 30score ends at 30. The second line is not circular because the right side score + 5 is fully evaluated using the current value 10 to get 15, and only then is score rebound to that new value. Read first, then rebind, never both at once.
type(count) reports <class 'str'>. Rebinding pointed the same name at a string value, and because Python tracks the type on the value rather than on the name, count simply took on the new value's type. A name's type is always just whatever its current value's type is.
DON'T treat score = score + 5 as a contradiction, and DON'T expect count to "stay an int" after being rebound, the name follows the value it points at.
Answer 4:
After the third line, base_lr holds 0.01 and current_lr holds 0.001.
In the label model this falls out immediately. Line 2 does not copy the value, it points a second tag at the very same 0.01, so two names now label one value. Line 3 then evaluates current_lr / 10 to 0.001 and moves only that one tag onto the new value. Nothing was ever done to the value 0.01 itself, and base_lr was never touched, so it still labels exactly what it labeled before.
If a variable really were a box holding a value, line 2 would have to mean "put what is in the first box into the second box", and line 3 would then be changing the contents of the second box. That reading gets this example right by accident, but it has no way to express "the same value, labeled twice", and that gap starts producing wrong predictions once a shared value can be changed, the case Lesson 1.4.1 shows with a list. The name-tag picture never has to make that exception.
If the fourth line rebound current_lr to the string "scheduled", type(base_lr) would still report <class 'float'>. Rebinding moves one tag and nothing else, and in any case the type was never on the name to begin with, it was on the value, and base_lr still points at the same float it always did.
DON'T conclude that two names sharing a value are somehow linked, they are two independent tags, and moving one never moves the other, though a change made in place to a shared list, Lesson 1.4.1, shows through both.
Assignment
One assignment. The problem and its hints first, then the solution.
Assignment: The Counter Suite
Create counters.py that tracks training progress across several rebound counters. Bind steps = 0, correct = 0 and total = 0. Then simulate processing three batches, and for each batch use augmented assignment to do all three updates: steps += 1, add that batch's correct count to correct (say correct += 8), and add the batch size to total (say total += 10). Use 8, 9 and 7 correct out of 10 for the three batches.
After the three batches, compute accuracy = correct / total and print all four values on one line. Then, using parallel assignment, bind two names in a single statement with prev, curr = 0.0, accuracy and print both.
In comments, settle three things: how augmented assignment compacts the read-operate-rebind pattern, why the parallel assignment reads both values before it binds either name, and what prev, curr = curr, prev would do if you added it as a final line, along with why that works without a temporary name.
Hints:
correct += 8is exactlycorrect = correct + 8, so the running totals are nothing more than repeated rebinds of the same three names.prev, curr = 0.0, accuracybuilds the whole right-hand pair first, then binds both left-hand names from it, which is the same reason the right side of any assignment is finished before the name on the left moves.
Solution:
# Three counters, all starting at zero.
steps = 0
correct = 0
total = 0
# Batch 1: one step taken, 8 of 10 correct.
steps += 1 # steps = steps + 1 -> 1
correct += 8 # correct = correct + 8 -> 8
total += 10 # total = total + 10 -> 10
# Batch 2: another step, 9 of 10 correct.
steps += 1 # -> 2
correct += 9 # -> 17
total += 10 # -> 20
# Batch 3: another step, 7 of 10 correct.
steps += 1 # -> 3
correct += 7 # -> 24
total += 10 # -> 30
# Compute accuracy from the accumulated counters.
accuracy = correct / total # 24 / 30
print(steps, correct, total, accuracy) # 3 24 30 0.8
# Parallel assignment: build the pair (0.0, 0.8) first, then bind BOTH names.
prev, curr = 0.0, accuracy
print(prev, curr) # 0.0 0.8
# Swap them, with no temporary name anywhere.
prev, curr = curr, prev
print(prev, curr) # 0.8 0.0
# '+=' folds read, operate and rebind into one statement, so 'correct += 8' reads
# 'correct', adds 8, and moves the name to the result, exactly as the long form did.
# The parallel form reads both values first because Python evaluates the entire
# right side into a pair before it touches either name on the left.
# The swap works for that same reason: the pair (0.8, 0.0) is fully built from the
# CURRENT values first, so neither name has moved yet when the values are read.Run it:
# Accumulate counters across three batches, then report and reorder them.
python3 counters.pyThe script prints 3 24 30 0.8, then 0.0 0.8, then 0.8 0.0.
The swap on the last line is where the lesson's central rule pays off. Two ordinary assignments cannot do it, because prev = curr moves the first name before the second line has read what it needed, and the original value of prev is gone by then, leaving both names on the same value. The parallel form never has that problem, because the right-hand side is finished completely before any name on the left is rebound, which is the same right-to-left order that made total_loss = total_loss + 0.5 work rather than being circular. One rule, two places it shows up.
DON'T mix correct += 8 up with a comparison, += rebinds the name and never asks a question the way == does. And DON'T reach for a temporary name to swap two values, the pair form says what you mean in one line and cannot get the order wrong.
Additional Resources
Optional, and nothing later depends on either one. Worth opening only if this lesson left you wanting more than it needed to give you.
- Official docs - Programming FAQ, "Why did changing list 'y' also change list 'x'?". The name-as-label idea one step past this lesson: two names on one value, why rebinding an integer leaves the other name where it was, and why a change made in place to a list (a changeable sequence, Section 1.4) through one name shows through the other.
- Official docs - PEP 8, "Naming Conventions". The style guide behind
snake_case, and the conventions for the names this lesson only had room to summarize.
Summary
This lesson was about the quietest yet most constant act in all of programming: giving a value a name. Underneath every variable sits one stubborn idea, the name is a label, not a box. Once that picture is firm, everything else falls out of it. Rebinding is just moving the label. The read-then-rebind order of score = score + 1 stops being a paradox and becomes obvious. And the fact that one name can hold an integer today and a string tomorrow is simply the label being free to point anywhere, because the type was never on the name to begin with, it was always on the value.
That freedom is the gift of dynamic typing, and it is why Python feels so direct compared to languages that make you announce every type in advance. You assign, and the name takes on whatever you gave it. But freedom carries a duty: nothing in the language stops you from naming a count x or letting n_samples quietly become text, and both will eventually cost you an afternoon of confusion. The hard rules keep your code running, the soft conventions keep it readable, and readable names are the cheapest documentation you will ever write.
You will feel that last point most acutely soon. The data-cleaning and modeling scripts ahead are long chains of named, rebound, accumulating values, running totals, best scores, configuration blocks, placeholders waiting to be filled. Whether those scripts are a pleasure or a nightmare to read and debug is decided almost entirely by the names you choose right now, while the stakes still feel low. The mechanism is simple, a name points at a value, but it is the foundation that every line you write from here forward quietly rests on.
Cheat Sheet
Part A - Reference Table
| Concept | Syntax | What it does | When to avoid |
|---|---|---|---|
| Variable | name = value |
Points a name at a value | Using a name before assigning it |
| Assignment | x = 0.01 / 2 |
Evaluate right side, bind to left | Reversing the sides |
| Naming rule | n_epochs, _cache |
Letter/underscore first, then letters/digits/underscores | Digits-first, spaces, hyphens, keywords |
| Rebinding | x = x + 1 |
Move the name to a new value | Thinking the old value changes |
| Dynamic typing | x = 5 → x = "hi" |
Name takes the value's type | Changing a name's meaning carelessly |
| Augmented assign | x += 1 |
Compact read-add-rebind | When clarity needs the full form |
Part B - Compact Code Block
learning_rate = 0.001 # bind: name points at a value
batch_size = 64 # snake_case, descriptive, legal name
batch_size = 128 # rebinding: same name, new value
total = 0.0 # start an accumulator
total = total + 0.5 # read old value, compute, rebind
total += 0.5 # same thing, augmented-assignment shorthand
value = 5 # dynamic typing: name follows the value...
value = "loss" # ...now type(value) is str, no declaration
a, b = 1, 2 # right side first, then both names
a, b = b, a # one-line swap, no temp needed