Variables and Data Types
A variable in Python is just a name that points to something stored in memory. In some other programming languages, a variable acts like a box that holds a value. But in Python, it does not work that way. A variable does not hold the value itself - it just points to the object that holds the value.
Python uses something called dynamic typing. This means you do not need to tell Python what type of value a variable will hold. Python figures out the type on its own, based on whatever value you give it.
Key Point to Remember: In Python, variables are like labels pointing to objects, not boxes holding values. Once you understand this, the rest of Python’s memory system becomes much easier to follow.
In Python, everything is an object. And every object has three things attached to it:
- Identity - a unique ID for that object (basically its memory address)
- Type - what kind of object it is (like int, str, list, etc.)
- Value - the actual data stored inside the object
x = 10print(id(x)) # Identity (memory address)print(type(x)) # Type (int)print(x) # Value (10)Creating a Variable
Section titled “Creating a Variable”You create a variable simply by using the = sign to assign a value to it.
x = 10name = "Sahil"is_active = TrueIn the examples above:
xis pointing to an integer objectnameis pointing to a string objectis_activeis pointing to a boolean object
Important Things to Know About Variables
Section titled “Important Things to Know About Variables”- A variable is just a pointer (reference) to an object, not the object itself
- Variables do not actually store the data - they just point to where the data lives in memory
- More than one variable can point to the same object
- A variable can be changed later to point to a completely different type of object
How Variables Actually Work Behind the Scenes
Section titled “How Variables Actually Work Behind the Scenes”Python uses a system where variables only point to objects sitting somewhere in memory. They don’t hold the data themselves.
Memory Model (How It Works Step by Step)
Section titled “Memory Model (How It Works Step by Step)”x = 10y = xHere is what happens step by step:
- Python creates an integer object
10in memory xis now pointing to that objectyis also made to point to the same object thatxis pointing to
So basically, both x and y are pointing to the exact same object right now.
What Happens When You Reassign a Variable
Section titled “What Happens When You Reassign a Variable”x = 20- A brand new integer object
20gets created in memory xnow points to this new object insteadystill points to the old object, which is10
Diagram
Section titled “Diagram”Identity vs Equality (is vs ==)
Section titled “Identity vs Equality (is vs ==)”Python gives you two different ways to compare things:
a = [1, 2]b = [1, 2]
a == b # True -> values are equala is b # False -> different objects in memory==checks if the values are the sameischecks if it is literally the same object in memory
Data Types in Python
Section titled “Data Types in Python”A data type tells Python what kind of value an object is holding, and it also decides what kind of operations you can do with that value.
Built-in Basic Data Types
Section titled “Built-in Basic Data Types”int # Whole numbers (e.g., 10)float # Decimal numbers (e.g., 3.14)bool # True or False valuesstr # Text data (strings)complex # Complex numbers (e.g., 1 + 2j)NoneType # Means there is no value at allBuilt-in Collection Data Types
Section titled “Built-in Collection Data Types”list # A list of items that is ordered and can be changedtuple # A list of items that is ordered but cannot be changedset # A collection of unique items with no fixed orderfrozenset # Same as set, but cannot be changed once createddict # Stores data as key-value pairsBuilt-in Methods for Each Data Type
Section titled “Built-in Methods for Each Data Type”String Methods
Section titled “String Methods”A string is basically a sequence of characters, and Python gives us many built-in methods to work with strings:
name = "Sahil"print(name.upper()) # SAHILprint(name.lower()) # sahilprint(name.capitalize()) # Sahilprint(name.replace("a", "o")) # Sohilprint(name.split("a")) # ['S', 'hil']print(name.strip()) # Removes leading and trailing whitespaceprint(name.startswith("S")) # Trueprint(name.endswith("l")) # Trueprint(name.find("h")) # 2 (index of first occurrence)String Slicing:
[start:stop:step] - this lets you pick out a part of the string using index positions and a step value.
name = "Sahil"print(name[0]) # S (first character)print(name[1:4]) # hil (substring from index 1 to 3)print(name[:3]) # Sah (first three characters)print(name[3:]) # il (substring from index 3 to end)print(name[-1]) # l (last character)print(name[-3:]) # hil (last three characters)print(name[::2]) # S a (every second character)List Methods
Section titled “List Methods”Lists are ordered collections that you can change anytime, and they come with several built-in methods:
my_list = [1, 2, 3]my_list.append(4) # [1, 2, 3, 4]my_list.insert(1, 10) # [1, 10, 2, 3, 4]my_list.remove(2) # [1, 10, 3, 4my_list.pop() # [1, 10, 3] (removes last element)my_list.pop(1) # [1, 3] (removes element at index 1)my_list.sort() # Sorts the list in placemy_list.reverse() # Reverses the list in placemy_list.clear() # Empties the listmy_list.extend([5, 6]) # [5, 6] (adds elements from another iterable)my_list.count(3) # 1 (counts occurrences of 3)my_list.index(5) # 0 (returns index of first occurrence of 5)my_list.join(", ") # "5, 6" (joins list elements into a string)List Slicing:
[start:stop:step] - this lets you pick out part of a list using index positions and a step value.
my_list = [1, 2, 3, 4, 5]print(my_list[0]) # 1 (first element)print(my_list[1:4]) # [2, 3, 4] (sublist from index 1 to 3)print(my_list[:3]) # [1, 2, 3] (first three elements)print(my_list[3:]) # [4, 5] (sublist from index 3 to end)print(my_list[-1]) # 5 (last element)print(my_list[-3:]) # [3, 4, 5] (last three elements)print(my_list[::2]) # [1, 3, 5] (every second element)Dictionary Methods
Section titled “Dictionary Methods”Dictionaries store data as key-value pairs, and they don’t follow any particular order. Here are some useful built-in methods:
my_dict = {"name": "Sahil", "age": 20}my_dict["name"] # "Sahil" (access value by key)my_dict["age"] = 21 # Update value for key "age"my_dict["city"] = "New York" # Add new key-value pairdel my_dict["age"] # Remove key-value pair with key "age"my_dict.get("name") # "Sahil" (returns value for key, or None if key not found)my_dict.keys() # dict_keys(['name', 'city']) (returns a view of keys)my_dict.values() # dict_values(['Sahil', 'New York']) (returns a view of values)my_dict.items() # dict_items([('name', 'Sahil'), ('city', 'New York')]) (returns a view of key-value pairs)my_dict.clear() # Empties the dictionarySet Methods
Section titled “Set Methods”Sets hold unique values with no fixed order, and they have their own set of useful methods:
my_set = {1, 2, 3}my_set.add(4) # {1, 2, 3, 4} (adds an element to the set)my_set.remove(2) # {1, 3, 4} (removes an element from the set)my_set.discard(5) # {1, 3, 4} (does not raise an error if element not found)my_set.pop() # Removes and returns an arbitrary element from the setmy_set.clear() # Empties the setmy_set.union({5, 6}) # {1, 3, 4, 5, 6} (returns a new set with elements from both sets)my_set.intersection({3, 4, 5}) # {3, 4} (returns a new set with elements common to both sets)my_set.difference({3, 4, 5}) # {1} (returns a new set with elements in my_set but not in the other set)my_set.symmetric_difference({3, 4, 5}) # {1, 5} (returns a new set with elements in either set but not in both)Frozenset Methods
Section titled “Frozenset Methods”A frozenset is just like a set, except you cannot change it once it’s made. It still has some useful methods:
my_frozenset = frozenset([1, 2, 3])my_frozenset.union(frozenset([4, 5])) # frozenset({1, 2, 3, 4, 5}) (returns a new frozenset with elements from both sets)my_frozenset.intersection(frozenset([2, 3, 4])) # frozenset({2, 3}) (returns a new frozenset with elements common to both sets)my_frozenset.difference(frozenset([2, 3, 4])) # frozenset({1}) (returns a new frozenset with elements in my_frozenset but not in the other set)my_frozenset.symmetric_difference(frozenset([2, 3, 4])) # frozenset({1, 4}) (returns a new frozenset with elements in either set but not in both)Tuple Methods
Section titled “Tuple Methods”Tuples are ordered collections that cannot be changed once created. They have a few simple methods:
my_tuple = (1, 2, 3)my_tuple[0] # 1 (access element by index)my_tuple.count(2) # 1 (counts occurrences of 2)my_tuple.index(3) # 2 (returns index of first occurrence of 3)Checking the Type of a Value
Section titled “Checking the Type of a Value”Before you convert a value from one type to another, it’s a good idea to check its current type first, just to make sure the conversion will actually work.
x = "10"if isinstance(x, str): print("x is a string")There are two common ways to check types in Python:
type()- tells you the exact type of the objectisinstance()- checks whether an object belongs to a certain class (or a subclass of it)
x = 10print(type(x)) # <class 'int'>print(isinstance(x, int)) # TrueType Casting (Converting One Type to Another)
Section titled “Type Casting (Converting One Type to Another)”Type casting just means converting a value from one data type into another data type.
Implicit Type Conversion (Python Does It Automatically)
Section titled “Implicit Type Conversion (Python Does It Automatically)”Sometimes Python will automatically convert one type into another, just so no data gets lost.
x = 10 # inty = 3.5 # float
z = x + y # result is float (10.0 + 3.5 -> 13.5)Explicit Type Conversion (You Do It Yourself)
Section titled “Explicit Type Conversion (You Do It Yourself)”You can manually convert types using Python’s built-in functions.
int("10") # Converts string to integerfloat("3.14") # Converts string to floatstr(100) # Converts integer to stringbool(1) # Converts integer to booleanint("abc") # Raises ValueErrorOperations and Type Compatibility
Section titled “Operations and Type Compatibility”Python lets you do different operations, but the types involved matter a lot. For example, when you do math between two different types, Python tries to convert them into one common type first.
x = 10 # inty = 3.5 # floatresult = x + y # result is float (10.0 + 3.5 -> 13.5)If the types don’t match up and Python can’t convert them, it will raise a TypeError.
x = "10"y = 5result = x + y # Raises TypeError (cannot add str and int)Types of Operations You Can Perform:
Section titled “Types of Operations You Can Perform:”- Arithmetic operations (e.g.,
+,-,*,/) - Comparison operations (e.g.,
==,!=,<,>) - Logical operations (e.g.,
and,or,not) - Membership operations (e.g.,
in,not in) - Identity operations (e.g.,
is,is not) - Bitwise operations (e.g.,
&,|,^,~) - Assignment operations (e.g.,
=,+=,-=,*=,/=)
Rules for Type Compatibility
Section titled “Rules for Type Compatibility”- Operations between types that get along (like int and float) are allowed
- Operations between types that don’t get along will raise a
TypeError - Python automatically converts types when needed, just to avoid losing data
Truthy and Falsy Values
Section titled “Truthy and Falsy Values”In Python, every value is treated as either “truthy” or “falsy” whenever it’s checked inside something like an if statement.
Falsy Values (Treated as False)
Section titled “Falsy Values (Treated as False)”NoneFalse0(zero of any numeric type)0.0(zero float)0j(zero complex)''(empty string)[](empty list)()(empty tuple){}(empty dictionary)set()(empty set)
Truthy Values (Treated as True)
Section titled “Truthy Values (Treated as True)”- Basically anything that is not falsy counts as truthy, such as:
- Strings that have text in them
- Numbers that are not zero
- Collections that have something inside them (lists, tuples, sets, dictionaries)
- Objects you’ve created from your own custom classes
Mutable vs Immutable Objects
Section titled “Mutable vs Immutable Objects”Objects in Python are grouped into two types, depending on whether you can change them after they’re created or not.
Immutable Objects (Cannot Be Changed)
Section titled “Immutable Objects (Cannot Be Changed)”An immutable object is an object whose value can never be changed after it is made. If you try to “change” it, Python actually just creates a brand new object instead.
x = 10x = x + 1- A new integer object gets created here
- The original object stays exactly the same as before
Examples:
- int
- float
- str
- tuple
Mutable Objects (Can Be Changed)
Section titled “Mutable Objects (Can Be Changed)”lst = [1, 2]lst.append(3)- This time, the same list object gets updated directly
- The memory reference stays the same, nothing new gets created
Examples:
- list
- dict
- set
Comparison Table
Section titled “Comparison Table”| Feature | Mutable Objects | Immutable Objects |
|---|---|---|
| Modifiable | Yes | No |
| Memory usage | Same object gets updated | A new object gets created |
| Examples | list, dict, set | int, str, tuple |
Object Storage and Memory Behavior
Section titled “Object Storage and Memory Behavior”Python manages memory using two main things:
- Heap memory - this is where all the actual objects are stored
- References (names) - these are basically the variable names that point to those objects
So variables are really just names that are linked to objects sitting in memory.
Internal Flow
Section titled “Internal Flow”Copying in Python
Section titled “Copying in Python”Copying means making a new object that is based on an object you already have. How the copy behaves depends on whether the object can be changed (mutable) and whether you’re doing a shallow copy or a deep copy.
Shallow Copy vs Deep Copy
Section titled “Shallow Copy vs Deep Copy”Shallow Copy
Section titled “Shallow Copy”A shallow copy makes a new outer object, but the objects sitting inside it are still shared between the original and the copy.
import copy
a = [[1, 2], [3, 4]]b = copy.copy(a)
b[0][0] = 100print(a) # Original is affectedThis diagram shows how a shallow copy works:
- List
bis a new list, but it still points to the same inner lists thatapoints to - So if you change an inner list through
b, it also changes fora
Deep Copy
Section titled “Deep Copy”A deep copy makes a totally separate and independent copy of the object, including everything nested inside it.
import copy
a = [[1, 2], [3, 4]]b = copy.deepcopy(a)
b[0][0] = 100print(a) # Original is NOT affectedThis diagram shows how a deep copy works:
- List
bis a new list with completely new inner lists, so changingbwill never affecta - Deep copying uses more memory than shallow copying, but it makes sure the original and the copy are fully separate from each other
Assignment vs Copy
Section titled “Assignment vs Copy”Assignment
Section titled “Assignment”a = [1, 2]b = a- No new object gets created here
- Both
aandbare pointing to the exact same object - If you change one, the other changes too
b = a.copy()- A new object actually gets created this time
- But only the outer structure is copied (this is a shallow copy)
- Anything nested inside might still be shared between them