print("Step 1")
print("Step 2")
print("Step 3")
Step 1
Step 2
Step 3
__init__
Scratch notebook for this session:
In Python, code is executed one line at a time, from top to bottom, unless you use something that changes the flow (like a function call or a loop).
Each line is executed in order.
if
, elif
, else
)Conditional statements let your code make decisions based on certain conditions.
You can also combine conditions using and
, or
, and not
.
A conditional (ternary) expression lets you assign a value based on a condition in a single line.
Syntax: x = a if condition else b
pass
, break
, continue
These keywords control how your code flows in loops and conditionals.
pass
pass
does nothing. It’s used as a placeholder when code is required but you have nothing to write yet.
Suppose you’re building a menu system for a fast-food restaurant app, and you haven’t yet decided what to do if the customer selects a menu item that’s “coming soon.” You want your code to run without errors, but you’re not ready to implement that part yet.
break
break
exits the loop immediately.
Imagine you’re searching for a specific item (like your keys) in a list of rooms. As soon as you find the keys, you want to stop searching.
continue
continue
skips the rest of the current loop iteration and moves to the next one.
Suppose you’re reading through a list of email subject lines. You want to print all of them except spam emails, which contain the word “SPAM.” You skip printing any email that’s spam.
for
LoopsA for
loop is used to repeat actions a certain number of times or to iterate over a sequence (like a list).
You can loop over the elements of a list like this:
If you want access to the index of each element within the body of a loop, use the built-in enumerate()
function:
while
LoopsA while
loop repeats actions as long as a condition is true.
A nested loop is a loop inside another loop.
else
The else
part after a loop runs only if the loop was not stopped by a break.
enumerate
, zip
enumerate
enumerate
gives you both the index and the value when looping over a list.
zip
zip
lets you loop over two (or more) lists at the same time.
A list comprehension is a short way to create a new list by looping over something.
We can even add some conditions.
Multiples of 2.
Powers of 2.
You can nest comprehensions.
[[[0, 0], [1, 0], [2, 0], [3, 0], [4, 0]],
[[0, 1], [1, 1], [2, 1], [3, 1], [4, 1]],
[[0, 2], [1, 2], [2, 2], [3, 2], [4, 2]],
[[0, 3], [1, 3], [2, 3], [3, 3], [4, 3]],
[[0, 4], [1, 4], [2, 4], [3, 4], [4, 4]]]
You can concatenate multiple comprehensions.
In Python, a class is a code construct used to define a new type of object, grouping together data and functions that operate on that data. Classes provide a means of bundling data and functionality together. Creating a new class creates a new type of object, allowing new instances of that type to be made.
Object-Oriented Programming (OOP) is a programming paradigm based on the concept of “objects,” which can contain both data (attributes) and code (methods).
When you create an object using a class, that object is called an instance of the class.
“Classes provide a means of bundling data and functionality together. Creating a new class creates a new type of object, allowing new instances of that type to be made. Each class instance can have attributes attached to it for maintaining its state. Class instances can also have methods (defined by its class) for modifying its state.” (Python Official Tutorial)
Here is a simple class that models a basic point in two-dimensional space:
class Point:
"""A class to represent a point in 2D space."""
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(2, 3)
print(p.x) # Output: 2
print(p.y) # Output: 3
2
3
In this example:
Point is a class.
p is an instance of Point, with specific x and y coordinates.
The init method is called the constructor and initializes the instance attributes.
In Python, a class is defined using the class
keyword, followed by the class name and a colon. The body of the class contains statements that define its attributes and methods.
Here is the simplest possible class definition:
This statement defines a new class named Dog
. At this stage, the class has no attributes (data) or methods (actions). It serves as a minimal template from which instances can be created.
Note: By convention, class names in Python use the CapitalizedWords naming style (also known as CamelCase).
class Dog:
declares a class named Dog
.pass
statement is syntactically required because Python expects an indented block after the colon; here, it indicates that the class has no content yet.name
, age
) and methods (e.g., bark()
).__init__
The constructor method __init__
is a special method in Python classes. It is called automatically when a new instance of the class is created. This method is commonly used to initialize (set up) the attributes of the new object.
__init__
is always self
, which refers to the instance being created.In this example, every time a new Dog
object is created, you must provide a name
and an age
, which are stored as attributes of the instance.
To create (or instantiate) an object from a class, call the class as if it were a function, passing any required arguments to the constructor (__init__
method).
Here,
my_dog
is an instance of the Dog
class, initialized with the name "Buddy"
and age 3
.name
and age
are accessed using dot notation (for example, my_dog.name
).Summary: Instantiating a class creates a new object with its own unique set of data, as specified by the constructor.
Instance methods are functions defined inside a class that operate on individual instances of that class. They can access and modify the data (attributes) that belong to the specific object.
self
as its first parameter. self
refers to the instance through which the method is called, allowing access to the object’s attributes.Example:
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
print(f"{self.name} says woof!")
my_dog = Dog("Buddy")
my_dog.bark() # Output: Buddy says woof!
Buddy says woof!
In this example:
bark
is an instance method.self.name
to access data specific to that instance.my_dog.bark()
, the method prints "Buddy says woof!"
.Summary: Instance methods enable each object created from a class to perform behaviors that may depend on its own data.
Class attributes are shared by all objects of the class. Instance attributes belong only to one specific object.
class Dog:
species = "Canine" # Class attribute
def __init__(self, name):
self.name = name # Instance attribute
dog1 = Dog("Buddy")
dog2 = Dog("Bella")
print(dog1.species) # Output: Canine
print(dog2.species) # Output: Canine
print(dog1.name) # Output: Buddy
print(dog2.name) # Output: Bella
Canine
Canine
Buddy
Bella
species
is a class attribute. It is shared by all instances of the class Dog
.name
is an instance attribute. Each object has its own separate value for name
.When you access dog1.species
or dog2.species
, both will return "Canine"
, because species
is shared.
When you access dog1.name
and dog2.name
, they return "Buddy"
and "Bella"
respectively, because name
is specific to each object.
Inheritance is a fundamental feature of object-oriented programming. It allows you to define a new class (called a subclass or derived class) based on an existing class (called a superclass or base class). The subclass inherits all the attributes and methods of the superclass, and can also introduce its own or override existing ones.
This enables code reuse and logical hierarchy between classes.
Example:
class Animal:
def speak(self):
print("This animal makes a sound.")
class Dog(Animal):
def bark(self):
print("Woof!")
my_dog = Dog()
my_dog.speak() # Output: This animal makes a sound.
my_dog.bark() # Output: Woof!
This animal makes a sound.
Woof!
Explanation:
Animal
is the superclass.Dog
is the subclass, which inherits from Animal
.Dog
automatically has the speak
method from Animal
, and also defines its own bark
method.Dog
, you can call both speak
(inherited) and bark
(defined in Dog
).Summary: Inheritance lets you build classes that share common behaviors, reducing code duplication and making code easier to maintain.
Method overriding allows a subclass to provide a specific implementation of a method that is already defined in its superclass. This enables a subclass to change or extend the behavior of inherited methods.
Example:
class Animal:
def speak(self):
print("Animal sound")
class Dog(Animal):
def speak(self):
print("Bark!") # Overrides the speak method
my_dog = Dog()
my_dog.speak() # Output: Bark!
Bark!
Explanation:
Animal
class defines a method called speak
.Dog
subclass defines its own speak
method, which overrides the one inherited from Animal
.speak()
is called on a Dog
instance, the version in Dog
is executed instead of the version in Animal
.Summary: Method overriding allows subclasses to modify or completely replace behaviors inherited from a parent class.
Magic methods (also known as dunder methods, because they start and end with double underscores) are special methods in Python that allow you to define how your objects interact with Python’s built-in functions and operators.
Some commonly used magic methods include:
__init__
— called when a new object is created (the constructor)__str__
— defines what should be returned when the object is printed with print()
Example:
class Dog:
def __init__(self, name):
self.name = name
def __str__(self):
return f"This dog's name is {self.name}."
my_dog = Dog("Buddy")
print(my_dog) # Output: This dog's name is Buddy.
This dog's name is Buddy.
Explanation:
__init__
method initializes the name
attribute when a new Dog
object is created.__str__
method returns a string representation of the object, which is used when you call print()
on the object.Summary: Magic methods let you customize how your objects behave with standard Python operations, such as printing, addition, comparison, and more.
Class methods affect the class itself, not just one object.
Static methods are like normal functions, but live inside the class.
class Dog:
dogs_count = 0 # Class attribute
def __init__(self, name):
self.name = name
Dog.dogs_count += 1
@classmethod
def get_dogs_count(cls):
return cls.dogs_count
@staticmethod
def bark():
print("Woof!")
dog1 = Dog("Buddy")
dog2 = Dog("Bella")
print(Dog.get_dogs_count()) # Output: 2
Dog.bark() # Output: Woof!
2
Woof!
Explanation:
dogs_count
is a class attribute, shared by all instances of the Dog
class.@classmethod
decorator is used to define a class method. Class methods receive the class itself as the first argument, conventionally named cls
.
get_dogs_count
returns the current count of all Dog
instances.@staticmethod
decorator is used to define a static method. Static methods do not receive an implicit first argument (neither the class nor the instance).
bark
can be called on the class itself, and behaves like a regular function, but lives inside the class’s namespace.Summary: - Class methods can modify or access class-level data that is shared across all instances. - Static methods are utility functions that have a logical connection to the class, but do not access or modify class or instance data.
This example builds a basic AI Assistant class. The assistant can answer questions, remember a history of questions, and keep track of how many questions it has been asked.
class AIAssistant:
total_questions = 0 # Class attribute to count total questions asked to all assistants
def __init__(self, name):
self.name = name
self.question_history = [] # Instance attribute to store asked questions
def answer(self, question):
"""Answer a question and save it to history."""
AIAssistant.total_questions += 1
self.question_history.append(question)
print(f"{self.name}: You asked, '{question}'")
# Very basic response logic:
if "weather" in question.lower():
print("I'm not connected to the internet, but I hope it's sunny!")
elif "name" in question.lower():
print(f"My name is {self.name}.")
else:
print("That's an interesting question!")
def show_history(self):
"""Show all questions this assistant has been asked."""
print(f"Questions asked to {self.name}:")
for q in self.question_history:
print(f"- {q}")
@classmethod
def show_total_questions(cls):
"""Show how many questions have been asked to all AI assistants."""
print(f"Total questions asked to all assistants: {cls.total_questions}")
# Example usage:
ai1 = AIAssistant("Alexa")
ai2 = AIAssistant("Siri")
ai1.answer("What's the weather today?")
ai1.answer("What's your name?")
ai2.answer("Can you help me with my homework?")
ai1.show_history()
ai2.show_history()
AIAssistant.show_total_questions()
Alexa: You asked, 'What's the weather today?'
I'm not connected to the internet, but I hope it's sunny!
Alexa: You asked, 'What's your name?'
My name is Alexa.
Siri: You asked, 'Can you help me with my homework?'
That's an interesting question!
Questions asked to Alexa:
- What's the weather today?
- What's your name?
Questions asked to Siri:
- Can you help me with my homework?
Total questions asked to all assistants: 3
An exception is a special object that signals an error or an unexpected situation in your program. For example, dividing by zero or trying to open a file that does not exist will cause an exception.
Python uses a try/except
block to handle exceptions, so your program doesn’t crash when something goes wrong.
Here is the basic structure of handling exceptions in Python:
Let’s see how it works in practice:
try:
# Try to do something risky
number = 1 / 1 # Change this to 1 / 0 to see an exception
except ZeroDivisionError as e:
print("Caught a ZeroDivisionError!")
print(e)
except Exception as e:
print("Caught a general exception.")
print(e)
else:
print("There were no exceptions.")
finally:
print("This is always executed (cleanup, closing files, etc.).")
There were no exceptions.
This is always executed (cleanup, closing files, etc.).
Here are some common Python exceptions you might see:
ZeroDivisionError – trying to divide by zero
ValueError – wrong value type (e.g., int(“abc”))
TypeError – wrong type used (e.g., adding a string to a number)
FileNotFoundError – file doesn’t exist
IndexError – list index out of range
If you don’t handle exceptions, your program will crash as soon as it encounters an error. Handling exceptions lets your program keep running or fail gracefully.
Real-life analogy: Exception handling is like wearing a seatbelt. If something unexpected happens, you stay safe instead of getting hurt!
Try dividing by zero and catch the exception:
An iterator is an object that lets you access items in a collection, one at a time. You can get the next item from an iterator using the next()
function.
for
loop.The yield
statement allows you to create your own iterator, called a generator. Generators are a memory-efficient way to produce values one at a time, only when you need them.
Create the generator.
The type of the variable named gen
is generator.
Iterate through the generator values.
The generator raises an exception when it reaches the end. If you keep calling next()
after the generator is finished, you will get a StopIteration
error.