Study Notes
Free high-yield revision notes for Python Syntax, Types, Input, Operators and Control Flow
Python Syntax, Types, Input, Operators and Control Flow
What Python is and why it matters
Python is a high-level, general-purpose programming language first released by Guido van Rossum in 1991. Its code reads close to plain English, it needs no separate compile-and-link step, and it ships with a large standard library. This chapter uses Python 3; every example was run on Python 3.13 and works on any Python 3.8 or later. Error tracebacks are shown as Python 3.13 prints them; older versions show fewer ^ marker lines and may word some messages slightly differently.
Python is often called a glue language in Big Data ecosystems. A typical pipeline reads files, calls a database, submits a Spark job through PySpark, publishes to Kafka and schedules everything with Airflow — and all of these tools offer Python interfaces. Heavy numerical work is done inside libraries written in C (NumPy, pandas), while Python code connects the pieces. The trade-off is speed: a plain Python loop is much slower than compiled code, so large computations are handed to libraries rather than written as long loops.
How Python runs your code
The standard interpreter (CPython) compiles your source code to bytecode, executes that bytecode in a virtual machine, manages objects and memory, imports modules and raises exceptions when something goes wrong. You can run Python in four common ways:
| Way | How | Typical use |
|---|---|---|
| Interactive shell (REPL) | type python3, then statements at the >>> prompt | quick experiments |
| Script | save hello.py, run python3 hello.py | programs and lab work |
| IDE (VS Code, PyCharm) | editor with run and debug buttons | larger projects, debugging |
| Notebook (Jupyter) | cells of code with their outputs stored below | data exploration |
A notebook keeps executable cells and their outputs together, but cells can be run in any order, so results are only reproducible if you run the cells top to bottom.
print("Hello, Big Data!")Output:
Hello, Big Data!Basic syntax
Statements, comments and case
Each line normally holds one statement; no semicolon is needed. Text after # is a comment and is ignored. Python is case-sensitive: total, Total and TOTAL are three different names.
Indentation and blocks
Compound statements such as if, for, while, def and class end their header line with a colon :. The indented lines that follow form the block (also called a suite). Indentation is part of the grammar, not just style: the block ends when the indentation returns to the earlier level. The convention is four spaces per level; never mix tabs and spaces.
marks = 72
if marks >= 40:
print("Pass")
print("Well done")
print("Done checking")Output:
Pass
Well done
Done checkingThe last print is not indented, so it is outside the if block and always runs. Forgetting the indentation is an error that Python reports before running anything:
marks = 72
if marks >= 40:
print("Pass")Output:
File "example.py", line 3
print("Pass")
^^^^^
IndentationError: expected an indented block after 'if' statement on line 2Variables, names and objects
Declaring variables
Python has no declaration statement. A variable is created the first time you assign to it, and assignment binds a name to an object. The object carries the type; the name does not. This is called dynamic typing: the same name may later refer to an object of a different type.
x = 10
print(type(x))
x = "ten"
print(type(x))Output:
<class 'int'>
<class 'str'>Optional type hints such as age: int = 21 document intent for readers and tools, but Python does not enforce them at run time.
Rules for names: use letters, digits and underscores; do not start with a digit; do not use a keyword such as if, for, class, True or None. By convention, variables use snake_case, and values meant to stay constant use UPPER_CASE (Python does not enforce this).
Object references: names versus objects
Because assignment copies a reference rather than the object, two names can refer to the same object. With a mutable object such as a list, a change made through one name is visible through the other.
a = [1, 2, 3]
b = a
b.append(4)
print(a)
print(a is b)
c = [1, 2, 3, 4]
print(a == c, a is c)Output:
[1, 2, 3, 4]
True
True False== compares values; is compares identity (whether both names refer to the very same object). a and c hold equal values but are different objects. Use is mainly to test for None.
Immutable objects such as integers and strings cannot be changed in place, so an operation creates a new object and only the assigned name is rebound:
n = 5
m = n
m = m + 1
print(n, m)Output:
5 6You can also bind several names at once: a = b = 0 gives both names the same object, and x, y = 1, 2 assigns two values in one statement (covered in detail in the strings and tuples chapter).
Native data types
| Type | Example literals | Notes |
|---|---|---|
int | 0, -42, 1_000_000 | whole numbers of unlimited size |
float | 3.14, 2.5e-3 | 64-bit binary floating point |
complex | 3+4j | real and imaginary parts are floats |
bool | True, False | a subclass of int |
NoneType | None | the single "no value" object |
str | 'hi', "hi" | immutable text (next chapter) |
Lists, tuples, dictionaries and sets are built-in container types covered in later chapters.
Integers have arbitrary precision
A Python int grows as large as memory allows; it never overflows the way a 32-bit int in C does.
print(2 ** 100)
print(type(2 ** 100))
big = 10 ** 20 + 1
print(big)Output:
1267650600228229401496703205376
<class 'int'>
100000000000000000001Floats are approximate
A float stores a binary fraction with about 15–17 significant decimal digits. Many decimal values such as 0.1 cannot be stored exactly, so tiny rounding errors appear. Never compare floats with == after arithmetic; round them or use math.isclose.
print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
print(round(0.1 + 0.2, 2) == 0.3)
print(1e3, 2.5e-3)Output:
0.30000000000000004
False
True
1000.0 0.0025Complex numbers, booleans and None
z = 3 + 4j
print(z.real, z.imag)
print(abs(z))
print(True + True)
print(isinstance(True, int))
result = None
print(result is None, type(None))Output:
3.0 4.0
5.0
2
True
True <class 'NoneType'>abs(3+4j) is the magnitude √(3² + 4²) = 5.0. Because bool is a subclass of int, True behaves as 1 and False as 0 in arithmetic. None represents "no value"; a function that has no return statement returns None.
Truth values
Every object can be tested in an if or while condition. These are falsy: False, None, numeric zero (0, 0.0, 0j), and empty containers ("", [], (), {}, set(), range(0)). Almost everything else is truthy — including the strings "0" and "False".
for value in [0, 0.0, "", [], None, "0", [0], -1]:
print(repr(value), bool(value))Output:
0 False
0.0 False
'' False
[] False
None False
'0' True
[0] True
-1 TrueInput and output
print()
print() converts each argument to text, separates them with sep (default one space) and finishes with end (default newline).
print("A", "B", "C")
print("A", "B", "C", sep="-")
print("no newline", end=" | ")
print("same line")
name = "Asha"
age = 21
print("Name:", name, "Age:", age)
print(f"{name} is {age} years old")Output:
A B C
A-B-C
no newline | same line
Name: Asha Age: 21
Asha is 21 years oldThe last line uses an f-string: expressions inside {} are evaluated and inserted. Formatting is covered fully in the next chapter.
input()
input(prompt) displays the prompt, waits for the user to press Enter and always returns a string (without the newline). Converting and validating that string is the program's job.
age = input("Enter your age: ")
print(type(age))
age = int(age)
print("Next year you will be", age + 1)Sample run (the user types 21):
Enter your age: 21
<class 'str'>
Next year you will be 22Without int(), the expression age + 1 would fail with TypeError: can only concatenate str (not "int") to str. Conversion is usually written in one line:
a = int(input("First number: "))
b = float(input("Second number: "))
print("Sum =", a + b)Sample run (the user types 7 and 3.5):
First number: 7
Second number: 3.5
Sum = 10.5Type conversion
The functions int(), float(), str(), bool() and complex() create a new object of the requested type. int() of a float truncates toward zero; round() uses "round half to even" and works on the stored binary value.
print(int("42") + 1)
print(int(3.99), int(-3.99))
print(float("2.5"), float(7))
print(str(3.0) + "!")
print(int("101", 2))
print(bool("False"), bool(""))
print(round(2.5), round(3.5), round(2.675, 2))Output:
43
3 -3
2.5 7.0
3.0!
5
True False
2 4 2.67int("101", 2) reads the text as binary. bool("False") is True because the string is not empty. round(2.675, 2) gives 2.67 because 2.675 is actually stored as 2.67499999…. A string that is not a valid whole number cannot be converted by int():
print(int("3.5"))Output:
Traceback (most recent call last):
File "example.py", line 1, in <module>
print(int("3.5"))
~~~^^^^^^^
ValueError: invalid literal for int() with base 10: '3.5'Use int(float("3.5")) if you really want 3. In mixed arithmetic Python widens automatically (int + float gives float), but it never converts between str and numbers implicitly.
Operators
Arithmetic operators
| Operator | Meaning | Example | Result |
|---|---|---|---|
+ - * | add, subtract, multiply | 7 * 2 | 14 |
/ | true division, always a float | 6 / 2 | 3.0 |
// | floor division (rounds toward −∞) | -7 // 2 | -4 |
% | remainder; sign follows the divisor | -7 % 3 | 2 |
** | power | 2 ** 10 | 1024 |
print(7 / 2, 6 / 2)
print(7 // 2, -7 // 2)
print(7 % 3, -7 % 3, 7 % -3)
print(divmod(17, 5))
print(2 ** 10, 2 ** -1, 9 ** 0.5)
print(7.0 // 2)Output:
3.5 3.0
3 -4
1 2 -2
(3, 2)
1024 0.5 3.0
3.0Floor division and remainder always satisfy a == (a // b) * b + a % b. For −7 and 2: −7 // 2 = −4 (the floor of −3.5), and −7 − (−4 × 2) = 1. divmod(a, b) returns both results as a pair. If either operand is a float, // returns a float.
Precedence and associativity
From highest to lowest priority:
| Level | Operators | |
|---|---|---|
| 1 | ( ) parentheses | |
| 2 | ** (right-associative) | |
| 3 | unary +x, -x, ~x | |
| 4 | *, /, //, % | |
| 5 | +, - | |
| 6 | <<, >> then & then ^ then ` | |
| 7 | comparisons: ==, !=, <, <=, >, >=, is, is not, in, not in | |
| 8 | not | |
| 9 | and | |
| 10 | or | |
| 11 | conditional expression x if c else y |
Operators on the same level work left to right, except `, which works right to left**. Also, ` binds more tightly than a unary minus on its left.
print(2 ** 3 ** 2)
print((2 ** 3) ** 2)
print(-2 ** 2)
print((-2) ** 2)
print(10 - 4 - 3)
print(2 + 3 * 4 ** 2 // 5)Output:
512
64
-4
4
3
11Step by step: 2 ** 3 ** 2 is 2 ** 9 = 512. -2 ** 2 is -(2 ** 2) = −4. In the last line, 4 ** 2 = 16 first, then 3 * 16 = 48, then 48 // 5 = 9 (left to right on the same level), and finally 2 + 9 = 11.
Comparison operators and chaining
Comparisons return True or False. They can be chained: a < b < c means a < b and b < c, with b evaluated only once.
x = 15
print(10 < x < 20)
print(1 < 3 > 2)
print(1 == 1.0, "a" < "b", [1, 2] < [1, 3])Output:
True
True
True True TrueLogical operators and short-circuit evaluation
and, or and not combine conditions. and and or short-circuit and return one of their operands, not necessarily True/False:
x and yreturnsxifxis falsy (without evaluatingy); otherwise it returnsy.x or yreturnsxifxis truthy (without evaluatingy); otherwise it returnsy.not xalways returns abool.
print(0 and 5)
print(3 and 5)
print(0 or 5)
print("" or "default")
print(not [])
name = ""
display = name or "Guest"
print(display)Output:
0
5
5
default
True
GuestShort-circuiting is also a safety tool: the right side is skipped when the left side already decides the result.
count = 0
total = 50
if count != 0 and total / count > 10:
print("high average")
else:
print("no data or low average")Output:
no data or low averageBecause count != 0 is False, Python never evaluates total / count, so there is no ZeroDivisionError.
Identity and membership operators
is / is not test whether two names refer to the same object. in / not in test whether a value occurs in a container (for a string, whether it is a substring).
data = [10, 20, 30]
print(20 in data, 25 not in data)
print("an" in "banana")
value = None
print(value is None)Output:
True True
True
TrueAssignment and bitwise operators
Augmented assignment +=, -=, *=, /=, //=, %=, **= updates a variable using its current value. Python has no ++ or -- operators.
total = 10
total += 5
total *= 2
total //= 4
print(total)
print(6 & 3, 6 | 3, 6 ^ 3, ~6, 1 << 3, 16 >> 2)Output:
7
2 7 5 -7 8 4The total goes 10 → 15 → 30 → 7. Bitwise operators work on the binary digits of integers: 6 is 110 and 3 is 011, so AND gives 010 (2), OR gives 111 (7) and XOR gives 101 (5); ~x equals -x - 1; shifting left by 3 multiplies by 8.
Flow of control
A program's statements normally run top to bottom (sequence). Control structures change that order: branching (if) chooses between blocks and looping (for, while) repeats a block. Modules add ready-made functionality: a module is a .py file of reusable code, loaded with import before use (modules are covered in detail later).
import math
print(math.sqrt(16), math.pi)
print(math.factorial(5))Output:
4.0 3.141592653589793
120if, if-else and elif
Python checks the conditions in order and runs only the first block whose condition is true; else runs when none are true. elif is short for "else if".
marks = int(input("Marks: "))
if marks >= 75:
grade = "Distinction"
elif marks >= 60:
grade = "First class"
elif marks >= 40:
grade = "Pass"
else:
grade = "Fail"
print(grade)Sample run (the user types 72):
Marks: 72
First classOrder matters: if the marks >= 40 test came first, 72 would be labelled "Pass" and the later branches would never be checked.
Nested if-else
An if can sit inside another if block when a second decision depends on the first.
age = 20
has_id = False
if age >= 18:
if has_id:
print("Entry allowed")
else:
print("Show ID first")
else:
print("Too young")Output:
Show ID firstFor simple cases, a single condition with and is flatter and easier to read. A one-line conditional expression chooses between two values:
n = 7
parity = "even" if n % 2 == 0 else "odd"
print(parity)Output:
oddfor loops
A for loop takes successive items from any iterable — a string, list, tuple, dictionary, file or range — rather than being limited to a numeric counter.
for ch in "BDA":
print(ch)
for city in ["Pune", "Delhi"]:
print(city.upper())Output:
B
D
A
PUNE
DELHIrange()
range(stop), range(start, stop) and range(start, stop, step) produce an immutable, lazy sequence of integers. The stop value is excluded, the step may be negative, and a step of 0 raises ValueError. A range does not build a list in memory; list() shows its contents.
print(list(range(5)))
print(list(range(2, 8)))
print(list(range(10, 0, -3)))
print(list(range(5, 2)))
print(len(range(0, 100, 7)))
print(range(3))Output:
[0, 1, 2, 3, 4]
[2, 3, 4, 5, 6, 7]
[10, 7, 4, 1]
[]
15
range(0, 3)range(5, 2) is empty because the default step is +1 and 5 is already past 2. range(0, 100, 7) holds 0, 7, …, 98 — fifteen numbers. A common pattern is the accumulator:
total = 0
for i in range(1, 11):
total += i
print(total)Output:
55while loops
A while loop repeats as long as its condition is truthy. Something inside the loop must eventually make the condition false; otherwise the loop runs forever (stop it with Ctrl+C).
n = 1
while n < 100:
n = n * 3
print(n)
num = 4096
digits = 0
while num > 0:
num //= 10
digits += 1
print(digits)Output:
243
4The first loop goes 1 → 3 → 9 → 27 → 81 → 243 and stops because 243 is not less than 100. Use for when you know what to iterate over, and while when you repeat until a condition changes.
Nested loops
The inner loop runs completely for each value of the outer loop, so the total work is the product of the two counts.
for i in range(1, 4):
for j in range(1, 4):
print(f"{i * j:3}", end="")
print()Output:
1 2 3
2 4 6
3 6 9{i * j:3} prints each number right-aligned in a width of 3, and the empty print() ends each row.
break, continue and pass
breakexits the nearest enclosing loop immediately.continueskips the rest of the current iteration and moves to the next one.passdoes nothing; it is a placeholder where Python requires a statement.
for n in range(1, 10):
if n == 6:
break
if n % 2 == 0:
continue
print(n)Output:
1
3
5Even numbers are skipped by continue, and the loop ends at 6 because of break. In nested loops, break leaves only the innermost loop:
for i in range(3):
for j in range(3):
if j == 1:
break
print(i, j)
x = -5
if x < 0:
pass
else:
print("positive")
print("finished")Output:
0 0
1 0
2 0
finishedThe pass line keeps the if block syntactically valid while doing nothing.
The loop else clause
A for or while loop may have an else block. It runs when the loop finishes normally and is skipped when the loop ends with break. It is useful for search loops.
n = 29
for d in range(2, n):
if n % d == 0:
print(n, "is not prime; divisible by", d)
break
else:
print(n, "is prime")Output:
29 is primeFrom algorithm to program, and why loop design matters
Algorithm steps map directly to control structures: "set x to …" becomes an assignment, "repeat N times" becomes for _ in range(N), "repeat while …" becomes while, and "if … otherwise …" becomes if/else. Writing the steps first and then translating them line by line reduces errors.
Poor loop and condition design becomes expensive at Big Data scale. A nested loop over two collections of size n and m performs n × m steps:
rows, cols = 1000, 1000
steps = 0
for i in range(rows):
for j in range(cols):
steps += 1
print(steps)Output:
1000000At a million rows on each side this becomes a trillion steps. Good habits: stop early with break once the answer is known, move work that does not change out of the loop, test membership with a set or dictionary instead of a list inside a loop, make sure every while loop's condition will eventually become false, and hand bulk numerical work to vectorised libraries.
Lab 1: factorials with a for loop
The algorithm: set f = 1; set n = 0; repeat 10 times — output n, "! = ", f; add 1 to n; multiply f by n. A literal translation:
f = 1
n = 0
for step in range(10):
print(n, "! = ", f)
n = n + 1
f = f * nOutput:
0 ! = 1
1 ! = 1
2 ! = 2
3 ! = 6
4 ! = 24
5 ! = 120
6 ! = 720
7 ! = 5040
8 ! = 40320
9 ! = 362880Two observations. First, print puts a space between arguments, giving 0 ! = 1; an f-string controls the layout exactly. Second, ten repetitions print 0! to 9!, so to reach 10! as the task title asks, repeat 11 times:
f = 1
n = 0
for step in range(11):
print(f"{n}! = {f}")
n += 1
f *= nOutput:
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800Lab 2: factorials below 2 billion with a while loop
When the number of repetitions is not known in advance, the loop condition expresses the stopping rule instead:
f = 1
n = 0
while f < 2_000_000_000:
print(f"{n}! = {f}")
n += 1
f *= nOutput:
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
11! = 39916800
12! = 479001600After printing 12!, the loop computes 13! = 6,227,020,800, which is not below 2,000,000,000, so the loop stops. The underscores in 2_000_000_000 are digit separators for readability. In C, 13! would overflow a 32-bit int (maximum about 2.1 billion); Python integers simply grow.
Common misconceptions
| Misconception | Reality |
|---|---|
= and == are interchangeable | = assigns; == compares |
input() returns a number if the user types digits | it always returns str; convert with int()/float() |
7 / 7 gives 1 | / always gives a float: 1.0 |
// truncates toward zero | it floors: -7 // 2 is -4 |
-2 ** 2 is 4 | ** binds first: the result is -4 |
and/or always return True/False | they return one of the operands, e.g. "" or "x" is "x" |
is checks equal values | is checks identity; use == for values |
range(1, 5) includes 5 | the stop value is excluded |
the loop else runs when break happens | it runs only when the loop was not broken |
pass skips to the next iteration | that is continue; pass does nothing |
| variables have fixed types | objects have types; names can be rebound to any type |
| Python integers overflow at 2³¹ − 1 | int has arbitrary precision |
Key points
- Indentation after a colon defines blocks; inconsistent indentation is a syntax error.
- Assignment binds a name to an object; types belong to objects (dynamic typing). Two names can share one mutable object.
- Core types:
int(unlimited size),float(approximate),complex,bool(a kind ofint),None,str. input()returns a string; convert explicitly withint(),float()and friends./gives a float,//floors,%takes the divisor's sign,**is right-associative and binds tighter than unary minus.- Comparisons chain;
and/orshort-circuit and return operands;istests identity,intests membership. if/elif/elseruns the first true branch only; nestedifhandles dependent decisions.foriterates over any iterable;rangeexcludes its stop;whilerepeats until its condition becomes false.breakexits the innermost loop,continueskips to the next iteration,passdoes nothing, and loopelseruns only when nobreakoccurred.- Nested loops multiply work; design loops carefully when data is large.