Variables & Data Types
1 · The lesson
readIn Python, a variable is not a box that holds a value. A variable is a name that refers to an object stored somewhere in memory. This difference may seem small now. It becomes important when two names refer to the same list, which you will see in section 6.
This lesson covers the binding model, the naming conventions most Python programmers follow, the built-in types you will use from the start, and the difference between values you can mutate in place and values you can't.
1. Binding, Not Boxing
x = 42Python creates the integer object 42, then makes the name x refer to it. The name lives in the current scope, and the object lives in memory. If you assign a new value, the name refers to a different object. If nothing else refers to the old object, Python deletes it (this is called garbage collection).
x = 42 x = "now I'm a string" # also allowed, because x is only a label x = [1, 2, 3] # still allowed, because a label has no fixed type
This is dynamic typing: values have types, names don't. The type lives on the object, not the variable.
2. Naming Rules and PEP 8
Python's rules for what's a legal name:
- Letters, digits, and underscores only
- Cannot start with a digit
- Cannot be a reserved keyword (
if,for,class,lambda,True, etc.) - Case-sensitive:
scoreandScoreare different names
user_age = 30 # fine _internal = "ok" # a leading underscore means "private" (by convention only) 2nd_place = "bronze" # SyntaxError: a name can't start with a digit class = "Physics 101" # SyntaxError: class is a keyword (a reserved word)
PEP 8 is Python's official style guide. The interpreter does not enforce its conventions, but almost every team expects you to follow them:
| Kind | Convention | Example |
|---|---|---|
| Variable, function | snake_case | user_age, parse_input |
| Constant | UPPER_SNAKE | MAX_RETRIES, PI |
| Class | PascalCase | HttpClient, User |
| "Private" | leading _ | _internal_cache |
Names should describe what the value means, not what type it is. users is better than user_list. count is better than n.
3. The Built-in Types You'll See Every Day
The five primitives:
age = 30 # int whole numbers of any size pi = 3.14159 # float decimal numbers, accurate to about 15-17 digits name = "Ada" # str text is_active = True # bool True or False (note the capital letters) result = None # None "no value" (other languages call this null)
Then the four built-in collections you'll get dedicated lessons on:
scores = [88, 92, 75] # list ordered, can be changed (see ../lists/) point = (3, 4) # tuple ordered, cannot be changed (see ../tuples/) user = {"name": "Ada"} # dict maps keys to values (see ../dictionaries/) tags = {"python", "web"} # set unique items, no order (see ../sets/)
Don't memorise the collections yet. Each one has its own lesson. For now, just know they exist and recognise how each one is written.
4. Inspecting Types: type() and isinstance()
print(type(42)) # <class 'int'> print(type(3.14)) # <class 'float'> print(type("hi")) # <class 'str'> print(type([1, 2])) # <class 'list'>
type() is fine for quick debugging. For real checks, especially inside functions, use isinstance(). It also handles inheritance correctly:
x = 42 print(isinstance(x, int)) # True print(isinstance(x, (int, float))) # True (isinstance accepts a tuple of allowed types)
You'll use isinstance() again in the Exceptions lesson when validating inputs.
5. Type Conversion
Python won't convert types for you without being asked. You convert a value by calling the type as a function:
int("42") # 42 (the string "42" becomes the int 42) float("3.14") # 3.14 (the string becomes a float) str(3.14) # "3.14" (any value can become a string) bool(0), bool(1), bool("") # False, True, False
The trap:
int("3.14") # ValueError: int() won't parse a decimal string
Go through float first:
print(int(float("3.14"))) # 3
You will usually meet this problem first when you read numbers typed by a user:
age = int(input("Age: ")) # crashes if the user types "thirty"
You'll handle that properly in Errors & Exceptions.
6. Mutability: an Idea You Will Need in Every Later Lesson
Some objects can be changed in place. Some can't. This difference affects how assignment behaves.
Immutable: int, float, str, bool, tuple, None. Once created, the object can never change. Operations return new objects.
name = "ada" name.upper() # returns "ADA" (but name is still "ada") name = name.upper() # now name points to the new string "ADA"
Mutable: list, dict, set. The object itself can be modified.
scores = [88, 92, 75] scores.append(100) # changes the existing list in place print(scores) # [88, 92, 75, 100]
This has a result that surprises almost every beginner once:
a = [1, 2, 3] b = a # b is not a copy. It is another name for the same list b.append(99) print(a) # [1, 2, 3, 99] (a changed too, which surprises most people)
= doesn't copy. It binds another name to the same object. To make a real copy of a list, use a.copy() or list(a). We'll come back to this in Lists.
Common Mistakes
- Shadowing built-ins. Naming a variable
list,dict,str, ortypehides the built-in for the rest of that scope. If you writelist = [1, 2, 3], thenlist(range(5))fails with aTypeError. Pickitemsorvaluesinstead. - Assuming
=copies. It doesn't. Both names point to the same object. This only matters for mutable types, but for them it matters a lot. int(input(...))with no error handling. This is fine in a tutorial, but not in a program that real people will use. Wrap it intry/exceptonce you've read the exceptions lesson.- Confusing
NonewithFalseor0. They're all "falsy" (see Operators), but they're distinct objects.Nonemeans "no value yet",0is a number, andFalseis a boolean.
🎯 Your Turn: Describe Any Value
Write a short program that prints three things about the value in the variable value:
1. The type of the value (e.g. int, str, list).
2. The value itself.
3. Its length.
With value = "Margaret", it should print the type str, the value Margaret, and the length 8. Then change the first line to value = [1, 2, 3, 4] and run it again.
Skeleton:
value = "Margaret" # TODO 1: print the type # TODO 2: print the value # TODO 3: print the length
Hint 1: Getting a clean type name
type(value).__name__ gives you the short string "int" instead of <class 'int'>. This is easier to read when you print it.
Hint 2: Printing a label and a value
print() accepts several values separated by commas, and puts a space between them: print("Length:", len(value)) prints Length: 8.
Show full solution
value = "Margaret" print("Type:", type(value).__name__) print("Value:", value) print("Length:", len(value))
Output:
Type: str
Value: Margaret
Length: 8 setup added so this can run · defines Margaret
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) Margaret = _AutoMock('Margaret')
With value = [1, 2, 3, 4], the output is Type: list, Value: [1, 2, 3, 4] and Length: 4.
Now try value = 42. The first two lines print, and then len(value) raises TypeError: object of type 'int' has no len(), because a number has no length. In the Conditionals lesson you will learn if, which lets a program skip the length line for values like this.
What You Learned
- A variable is a name bound to an object, not a container. Names have no type, but objects have the type.
- PEP 8:
snake_casefor variables and functions,UPPER_SNAKEfor constants,PascalCasefor classes. - The five primitive types (
int,float,str,bool,None) and the four collections you'll meet later. type()for debugging,isinstance()for real checks.- Conversion is explicit:
int("42"),str(3.14).int("3.14")raises aValueError, so convert withfloatfirst. - Mutability matters.
=never copies. It only binds another name to the same object.
Next: Numbers in Depth: integer division, floating-point rounding errors and the math module.
Practice this
on practicepython.inShort exercises that run in your browser and tell you what your code did, as well as whether a test passed.
Found this useful? Share it with a friend who’s learning Python.