Quick Answer

The essentials are the built-in types and their methods, comprehensions, f-strings, functions with default and keyword arguments, file handling with the with statement, and basic classes. Beyond syntax, a handful of idioms — enumerate, zip, unpacking, and truthiness checks — are what make code read as Python rather than as another language transcribed.

Types, Variables and Strings

# Types
x = 42              # int
y = 3.14            # float
name = "Riya"       # str
flag = True         # bool
nothing = None      # NoneType

type(x)             # <class 'int'>
int("42"), float("3.14"), str(42), bool(0)

# f-strings — the modern way to format
f"{name} is {x} years old"
f"{3.14159:.2f}"            # '3.14'  — 2 decimal places
f"{1234567:,}"              # '1,234,567'
f"{x=}"                     # 'x=42'  — handy for debugging

# String methods (all return new strings — str is immutable)
s = "  Hello World  "
s.strip()                   # 'Hello World'
s.lower(), s.upper(), s.title()
s.replace("World", "There")
s.split()                   # ['Hello', 'World']
",".join(['a', 'b', 'c'])   # 'a,b,c'
s.startswith("  He"), s.endswith("  ")
"World" in s                # True

# Slicing works on any sequence
s[0], s[-1], s[2:5], s[:3], s[3:], s[::-1]   # last one reverses

Remember strings are immutable. Every method returns a new string, so s.strip() alone does nothing — you must assign the result. Building a string by repeated concatenation in a loop is O(n squared); collect the pieces in a list and join once instead.

Lists, Dicts, Sets and Tuples

# List — ordered, mutable
nums = [3, 1, 4, 1, 5]
nums.append(9); nums.insert(0, 7); nums.extend([2, 6])
nums.remove(1)              # removes the FIRST 1
nums.pop(); nums.pop(0)     # pop(0) is O(n) — use deque for queues
nums.sort(); nums.sort(reverse=True); nums.sort(key=len)
sorted(nums)                # returns a NEW list, does not mutate
nums.reverse(); nums.count(1); nums.index(4)
len(nums), min(nums), max(nums), sum(nums)

# Dict — key-value, insertion ordered since 3.7
user = {'name': 'Riya', 'age': 20}
user['city'] = 'Pune'
user.get('email')                    # None instead of KeyError
user.get('email', 'not set')         # with a default
user.keys(), user.values(), user.items()
user.pop('age'); 'name' in user
{**user, 'age': 21}                  # merge / override

for key, value in user.items():
    print(key, value)

# Set — unique, unordered, O(1) membership
s = {1, 2, 3}
s.add(4); s.discard(9)               # discard does not raise if absent
s1 | s2, s1 & s2, s1 - s2            # union, intersection, difference
list(set(nums))                      # deduplicate

# Tuple — immutable, hashable, usable as a dict key
point = (3, 4)
x, y = point                         # unpacking
a, *rest = [1, 2, 3, 4]              # a=1, rest=[2,3,4]

Control Flow and Comprehensions

# Conditionals
if x > 10:
    ...
elif x > 5:
    ...
else:
    ...

status = "adult" if age >= 18 else "minor"     # ternary

# Truthiness — empty things are falsy
if not items:        # preferred over  if len(items) == 0
if value is None:    # use 'is' for None, never ==

# Loops
for i in range(5):              # 0..4
for i in range(2, 10, 2):       # 2,4,6,8
for item in items:
for i, item in enumerate(items, start=1):
for a, b in zip(list1, list2):

while condition:
    ...
    break / continue
else:
    ...            # runs only if the loop finished without break

# Comprehensions — the most Pythonic construct
[x * 2 for x in nums]
[x for x in nums if x > 2]
[x if x > 0 else 0 for x in nums]        # note: ternary goes BEFORE 'for'
{k: v for k, v in pairs}
{x for x in nums}                         # set comprehension
(x * 2 for x in nums)                     # generator — lazy, memory-friendly

[[r[i] for r in matrix] for i in range(len(matrix[0]))]   # transpose

Keep comprehensions to one condition and one transformation. Once you need nested loops and multiple conditions, a normal loop reads better — clever is not the goal.

Functions, Files and Errors

# Functions
def greet(name, greeting="Hello"):        # default argument
    return f"{greeting}, {name}"

def total(*args, **kwargs):               # variable arguments
    return sum(args)

greet(name="Riya", greeting="Hi")         # keyword arguments

square = lambda x: x * x                  # lambda — for short callbacks only

# NEVER use a mutable default — it is created once, at definition time
def bad(item, items=[]): ...              # accumulates across calls
def good(item, items=None):
    if items is None: items = []

# Files — always use 'with', always specify encoding
with open('data.txt', 'r', encoding='utf-8') as f:
    content = f.read()
    for line in f:                        # memory-friendly for large files
        process(line.rstrip())

with open('out.txt', 'w', encoding='utf-8') as f:    # 'w' TRUNCATES immediately
    f.write('text')

# JSON
import json
data = json.load(f)                       # from a file object
data = json.loads(string)                 # from a string  (the 's' means string)
json.dump(data, f, indent=2)

# Errors
try:
    result = 10 / n
except ZeroDivisionError as e:
    print(f"error: {e}")
except (TypeError, ValueError):
    ...
else:
    print("no exception occurred")
finally:
    print("always runs — cleanup goes here")

raise ValueError("invalid input")

Classes and the Idioms That Matter

class Student:
    school = "Priodemy"                  # class attribute, shared

    def __init__(self, name, marks):     # constructor
        self.name = name                 # instance attributes
        self.marks = marks

    def __str__(self):                   # what print() shows
        return f"{self.name}: {self.marks}"

    def __repr__(self):                  # what the debugger shows
        return f"Student({self.name!r}, {self.marks})"

    def passed(self):
        return self.marks >= 40

    @staticmethod
    def is_valid(marks):
        return 0 <= marks <= 100

    @classmethod
    def from_string(cls, s):             # alternative constructor
        name, marks = s.split(',')
        return cls(name, int(marks))

class Topper(Student):                   # inheritance
    def __init__(self, name, marks, rank):
        super().__init__(name, marks)
        self.rank = rank

The idioms that make code look like Python:

for i, x in enumerate(items):      # not  for i in range(len(items))
for a, b in zip(l1, l2):           # not  indexing both
if not items:                      # not  if len(items) == 0
a, b = b, a                        # swap without a temp variable
with open(...) as f:               # not  open / close by hand
value = d.get(k, default)          # not  if k in d: ... else: ...
text = "".join(parts)              # not  += in a loop
if x is None:                      # not  if x == None

Useful standard-library modules to know exist: collections for Counter, defaultdict and deque; itertools for combinations and permutations; datetime; pathlib for filesystem paths; random; and math.

Frequently Asked Questions

What is the difference between a list and a tuple? Lists are mutable and tuples are not. Because tuples are immutable they can be dictionary keys and set members, and they are slightly smaller and faster. Use a tuple for a fixed record and a list for a collection that changes.
Why should I never use a mutable default argument? The default is evaluated once when the function is defined, not on each call, so every call using it shares the same object. A list default accumulates values across calls. Use None as the default and create the list inside.
What is the difference between == and is? == compares values while is compares identity — whether they are the same object in memory. Use is only for None, True and False. Small integers and short strings are cached, so is may appear to work on them and then fail unpredictably.
When should I use a comprehension instead of a loop? When you are building a new collection from an existing one with a single transformation and at most one condition. Once you need nested loops or several conditions, a normal loop is clearer.
Why does my string method not change the string? Strings are immutable, so every method returns a new string rather than modifying the original. You must assign the result — s.strip() alone does nothing, while s = s.strip() works.