- Overview
- Slides
- What we've seen so far
- Object Oriented Programming
- Assignment 02
- Code Examples
- Micro Exercises
This week's lecture digs into a new concept: object-oriented programming. We will learn how to structure our code using classes and objects, and how to bundle data and behavior into reusable components (not Grasshopper components).
Learning Goals: Understand the basic principles of object-oriented programming and apply them in computational geometry.
↑ click to open ↑
We have previously learned how to work with data and how to structure simple algorithms to manipulate that data. We've covered the following concepts:
Defining Data
- Single values (e.g., integers, floats, strings, booleans) represent individual pieces of information.
- Single values of geometric type (e.g., points, boxes, frames).
- Multiple values stored in collections, such as lists, sets and dictionaries.
Defining Algorithms
- Sequential operations execute instructions in a specific order, like processing a list of points one by one.
- Conditional operations (e.g., if statements) allow us to make decisions based on certain conditions.
- Loops enable us to repeat blocks of code, to perform the same action multiple times (without having to copy&paste it) in order to work with multiple objects, for example, lists of values.
Re-using Algorithms
- Functions let us encapsulate code into reusable blocks. This means we can write an algorithm once and apply it repeatedly across different scenarios, making our code more modular and easier to read.
Re-using Algorithms + Data: ?
But what happens when we want to reuse both data and behavior? What happens when we need to define behavior that needs specific data to work with? For example, in an architectural context, we might want to create rooms, and for each room, we would like to calculate the volume, or the area for painting, or dig into what materials are used on the walls and calculate different values. Each of these functions are behaviors that belong conceptually to the class of objects of type Room
, so it makes sense to write this code in the same location.
Object-Oriented Programming (OOP) allows us to organize our code better, and bundle variables containing data and functions expressing behavior into something called class
:
- A
class
is a blueprint for creating objects. - An
object
is an instance of a class, it has its own set of data attached to it, as well as behavior. - Variables that are attached/belong to a class are called
attributes
- Functions that are attached/belong to a class are called
methods
.
In this way, a class is a container that carries both the data and the algorithms that go with it, and provides a coherent and unified component that we can work with, reuse, create multiple copies of, and so on.
What’s the benefit?
- Reusable code (data + algorithms as a building block)
- Mirrors how we think about real-world objects
- Helps organize code logically, because writing programs gets harder as the program gets bigger
This way of organizing your code becomes particularly valuable when working with large numbers of objects that need to be created and processed. Instead of managing a single room or a few bricks, you can manage thousands of them by reusing code. OOP makes it easier to modify, scale, and maintain your program well structured, as all objects of a given class share the same structure and behavior, ensuring consistency and reducing complexity.
# ------------------------------------------------
# ------------ Procedural Programming ------------
length = 10
width = 5
area = length * width
print("Area of the room:", area)
# This works, but there's no clear concept of "room",
# only of the arithmetic involved in this specific calculation.
# ------------------------------------------------
# ---------- Object Oriented Programming ---------
class Room(object):
def __init__(self, length, width):
self.length = length
self.width = width
def calculate_area(self):
return self.length * self.width
# ------------------------------------------------
# -------- Working with Multiple Objects ---------
rooms = []
rooms.append(Room(10, 5))
rooms.append(Room(8, 6))
rooms.append(Room(12, 4))
rooms.append(Room(9, 7))
rooms.append(Room(11, 3))
for room in rooms:
print("Area of room:", room.calculate_area())
A class in Python can be seen as a template for creating objects. Objects, in turn, are instances of a class, representing specific examples based on that template. Classes define the attributes (data) and methods (behavior) that the objects will have. Think of it as a way to define the blueprint for architectural elements—whether you’re designing a room, a brick, or a structure—by specifying its dimensions and behaviors. Consider a class that defines a room. This class (or type) for rooms has specific attributes like height, color, etc, and a method that calculates the volume of a given room. When you create an object of this class (e.g., a kitchen), the object is an instance of that blueprint with specific dimensions.
In Python, a class defines both attributes (data) and methods (behaviour) that belong to its instances (objects). Instance attributes are unique to each object. This is analogous to defining a blueprint for a building, where instance attributes would represent unique characteristics of individual rooms or elements.
# ------------------------------------------------
# ------------ Anatomy of a Class ----------------
class Room(object):
# ...
# ------------------------------------------------
# ------------ Behaviour (Methods) ---------------
class Room(object):
def paint(self, new_color):
self.color = new_color
# ------------------------------------------------
# ------------- Data (Attributes) ----------------
class Room(object):
def __init__(self, name, height, color):
self.name = name
self.height = height
self.color = color
# Methods
def paint(self, new_color):
self.color = new_color
The important thing to highlight is that we have been working with classes and objects from day 1! Remember: Everything in Python is an object!
For example, if you take a look at the source code for the Box Class in the API Reference you will see that this object we have worked with already works (while more intricate) in a very similar way:
class Box(Shape):
[...]
# the Class constructor here:
def __init__(self, xsize=1.0, ysize=None, zsize=None, frame=None, name=None):
super(Box, self).__init__(frame=frame, name=name)
self._xsize = None
self._ysize = None
self._zsize = None
self.xsize = xsize
self.ysize = xsize if ysize is None else ysize
self.zsize = xsize if zsize is None else zsize
[...]
# some of the methods connected to the Box Class
def height(self):
return self.zsize
def area(self):
return 2 * self.xsize * self.ysize + 2 * self.ysize * self.zsize + 2 * self.zsize * self.xsize
def volume(self):
return self.xsize * self.ysize * self.zsize
[...]
The __init__
method (including the double underscores before and after!) is a special function in Python that initializes an object's attributes when an instance of a class is created. It is often referred to as the "constructor" of the class. This method is called automatically when you create an object from a class, and it's where you define the initial values for the object's attributes.
The self
parameter represents the instance of the class that is being operated on. It’s how the object knows which instance’s attributes to reference or modify.
- When you create an object (say,
kitchen = Room("kitchen", 3, white)
), Python passes that object being created in that line as the first argument to the methods defined in the class. self
allows each object to keep track of its own data (its specific height, name, etc.). Withoutself
, you wouldn’t be able to differentiate between the attributes of one object and another.
# -----------------------------------------------------
# ----------------- __init__ and self -----------------
class Room(object):
# __init_ method to initialize the objects attributes
def __init__(self, length, width, height):
# 'self' refers to the current instance of the class
self.length = length
self.width = width
self.height = height
# Method to calculate the volume of the room
# we pass self to tell Python that the `calculate_volume` method is related to the `Room` class
def calculate_volume(self):
return self.length * self.width * self.height
# Method to calculate the area of the room
def calculate_area(self):
return self.length * self.width
# Create an object (instance) of the Room class
# we don't pass self here, Python automatically passes this
kitchen = Room(5, 3, 2.8)
# Create another object instance
livingroom = Room(8, 5, 2.8)
# Access attributes and call methods
print("Volume of the kitchen:", kitchen.calculate_volume()) # Output: 42.0 cubic meters
print("Area of the living room:", livingroom.calculate_area()) # Output: 40.0 square meters
# Create another Room object
living_room = Room(10, 5, 3.5)
print("Volume of the living room:", living_room.calculate_volume()) # Output: 175.0 cubic meters
Special methods, also called magic methods, allow us to define how objects behave in some specific circumstances in Python. For example, we can define how an object creates a printable text-version of itself, so that if we call print(variable_name)
on it, it can print some meaningful data. The following example gives the room the ability to print its dimensions when we call print(room)
:
class Room(object):
def __init__(self, name, length, width):
self.name = name
self.length = length
self.width = width
def __str__(self):
return "{}: {}m x {}m".format(self.name, self.length, self.width)
# Create a Room object
living_room = Room("Living Room", 5, 3)
# Output the room's dimensions using the __str__ method
# NOTE: we do not explicitly call the __str__ method!!
print(living_room) # Output: Living Room: 5m x 3m
The assignment of this week combines the filters we have seen in previous weeks with the concepts of object-oriented programming of this week as we move towards the creation of a building grid system.
Click the link here to learn the details of assignment 2.
The following files are useful to follow the lecture content:
The following are very simple micro exercises that you can go through to practice some of the concepts of the current lecture. Each of them should not take more than 10 minutes to complete. They are completely optional.
-
Create a class called
House
with two attributesname
andaddress
. Create an instance of the class and print its name and address. Change theaddress
in the instance and print again. -
Create a function that calculates the volume of a cuboid given the parameters
width
,height
, anddepth
. Call the function with different values to test it. -
Create a class called
Brick
with attributeswidth
,height
, anddepth
. Add a method calledcalculate_volume
(just like in the exercise above) that calculates and returns the volume of the brick. Create a few instances of theBrick
class and call thecalculate_volume
method. -
In a GHPython component with 3 inputs
width
,height
anddepth
, repeat the definition of the classBrick
from the previous exercise and add one method calledcreate_geometry
that returns aBox
instance for the given inputs. Use theDraw
component of COMPAS to visualize the output. -
Create a
Wall
class. Use a constructor (__init__
) that requireswidth
,height
, andthickness
of the wall as parameters. Add a methodcalculate_volume
that returns the wall's volumne. Create an instance ofWall
and print its volumne. -
Create a
Building
class that has one attribute: a list ofWall
instances as created in the previous exercise. Add a method calledcalculate_volume_walls
that calculates the sum of all the volumes of all the building walls. Create an instance ofBuilding
with at least 4 wells and print the volumne of all its walls.
Solutions
class House(object):
def __init__(self, name, address):
self.name = name
self.address = address
my_house = House("Home", "Josefstrasse 5")
print(my_house.name, my_house.address)
my_house.address = "Limmatstrasse 1"
print(my_house.name, my_house.address)
def volume(width, height, depth):
return width * height * depth
print(volume(1, 1, 1))
print(volume(2, 2, 2))
class Brick(object):
def __init__(self, width, height, depth):
self.width = width
self.height = height
self.depth = depth
def calculate_volume(self):
return self.width * self.height * self.depth
brick1 = Brick(2, 1, 10)
print(brick1.calculate_volume())
brick2 = Brick(2, 1, 5)
print(brick2.calculate_volume())
from compas.geometry import Box
class Brick(object):
def __init__(self, width, height, depth):
self.width = width
self.height = height
self.depth = depth
def calculate_volume(self):
return self.width * self.height * self.depth
def create_geometry(self):
return Box.from_width_height_depth(self.width, self.height, self.depth)
my_brick = Brick(width, height, depth)
a = my_brick.create_geometry()
class Wall(object):
def __init__(self, width, height, thickness):
self.width = width
self.height = height
self.thickness = thickness
def calculate_volume(self):
return self.width * self.height * self.thickness
my_wall = Wall(10, 10, 0.5)
print(my_wall.calculate_volume())
# class Wall is same as previous micro exercise
class Building(object):
def __init__(self, walls):
self.walls = walls
def calculate_volume_walls(self):
volume = 0
for wall in self.walls:
volume += wall.calculate_volume()
return volume
wall_1 = Wall(10, 10, 0.5)
wall_2 = Wall(10, 20, 0.7)
wall_3 = Wall(10, 10, 0.5)
wall_4 = Wall(10, 20, 0.7)
walls = [wall_1, wall_2, wall_3, wall_4]
my_building = Building(walls)
print(my_building.calculate_volume_walls())