from dataclasses import dataclass
@dataclass
class Circle:
radius: int
color: str
red_circle = Circle(5, "red")
blue_circle = Circle(5, "blue")
# All of these cause an error!
print(red_circle + blue_circle)
print(red_circle * blue_circle)
print(red_circle < blue_circle)
Most operators do not work with dataclasses.
Addition, subtraction, and the other math operations will not work, causing TypeErrors.
Similarly, the ordering operators (like the greater than > and less than < comparison operators) will not work.
from dataclasses import dataclass
@dataclass
class Circle:
radius: int
color: str
red_circle = Circle(5, "red")
blue_circle = Circle(5, "blue")
# These are all valid operations
print(red_circle != blue_circle)
print(red_circle == Circle(5, "red"))
print(red_circle != "red")
The equality (==) and inequality (!=) operators DO work, however.
When you check if two dataclasses are equal, each field is checked in order to make sure they are equal.
However, you will not often need to think about equality or any other operators with dataclasses directly.
Instead, you should be thinking about accessing the fields of a dataclass, in order to perform operations using the data stored inside of the dataclass.
from dataclasses import dataclass
@dataclass
class Rectangle:
length: int
width: int
box = Rectangle(5, 3)
print("The box area is", box.length * box.width)
We have previously discussed how we can access the fields of instances to use their values: we need the name of the variable holding the instance, a period (.), and then the field name.
This is like the syntax for a method, and that is no coincidence.
Technically speaking, a method is just an attribute that holds a function.
We will not bother learning to define methods in our dataclasses yet.
Instead, the important thing to focus on is that we can access fields from instances.
This is especially useful when we start passing dataclasses into functions.
from bakery import assert_equal
from dataclasses import dataclass
@dataclass
class Rectangle:
length: int
width: int
def area(rect: Rectangle) -> int:
return rect.length * rect.width
box = Rectangle(5, 3)
assert_equal(area(box), 15)
In the program shown here, we have moved the multiplication inside of a function to create a reusable area function that consumes a Rectangle instance and produces an integer.
Let’s break this down, step by step.
First, we import the assert_equal function and the dataclass decorator. By convention, imports always go at the top of a program.
Next, we define a dataclass named Rectangle, which will be the name of the constructor function AND the new type Rectangle created. The dataclass has two fields, length and width.
Then, we define a function named area that consumes a Rectangle and produces an integer.
In the header of the area function, we defined one parameter named rect that is a Rectangle. We used the dataclass type name Rectangle on the right-hand side of the colon to indicate the type of the parameter. But on the left-hand side, we were free to give whatever name we wanted to the local variable holding the instance. We chose the concise name rect but could have chosen clearer names like rectangle or a_rectangle.
After the function definition, we call the constructor function (Rectangle) with the values 5 and 3, which creates a new Rectangle instance and assigns the result to a new variable box. The variable name box is not significant; we could have named it whatever we wanted.
In the last line of code, we unit test the area function by calling the function inside of an assert_equal call. The Rectangle instance associated with the variable box is passed as an argument.
When the area function is called, the Rectangle instance that was used as an argument is assigned to the parameter named rect.
Now the body of the function is executed, and the function can use the fields of the instance stored in rect to calculate and return the area.
Finally, the returned integer from the area function call is compared against the expected value 15, which is correct, causing the unit test to pass.

Notice the rect parameter in the previous program has the type Rectangle, which matches our previously defined dataclass.
In the assert_equal call, that parameter is matched to the corresponding argument, which, in this case, is the variable box.
That box contains an instance of the Rectangle dataclass created using its constructor.
The function expects a Rectangle, so it would not work if you passed in a Circle to the area function.
Not only do the types not match, but the field accesses would also be wrong.
The Circle dataclass does not have length or width fields — only radius and color.
# Good
area(Rectangle(5, 3))
# Also good
my_box = Rectangle(5, 3)
area(my_box)
# Does not work!
area(Rectangle)
# Also does not work!
area(my_box.length, my_box.width)
There are many mistakes that beginners make when trying to define and call functions that consume dataclasses. The trick is to understand when you are dealing with the value, the variable, or the type. Following are two common issues that we see:
First, when calling a function that consumes a dataclass, make sure you pass in an instance instead of a type. You can pass in an instance directly or assign the instance to a variable and use the variable instead. However, you should not try to pass the type directly into the function; the type is an abstract idea instead of a concrete instance. You also cannot pass the fields into the dataclass directly — you need to pass a single instance, not the individual parts.
# This is good
def area(rect: Rectangle) -> int:
return rect.length * rect.width
# This does not work
def area(rect: Rectangle) -> int:
return Rectangle.length * Rectangle.width
# This does not work either!
def area(rect: Rectangle) -> int:
return length * width
Another common mistake is to use the parameter type inside of the function definition instead of the parameter itself.
Or, to mistakenly use the fields without referring to the instance at all.
You must make sure that you use the parameter variable when you need to access the fields of an instance.
The recurring theme is the same between both kinds of mistakes: you can only use the instance instead of the type.
In the same way that you can add 5+3 but not 5+int, you can access the fields of an instance but not of a dataclass type.
The type is a theoretical idea instead of a concrete value.
from dataclasses import dataclass
@dataclass
class Rectangle:
length: int
width: int
def as_string(rect: Rectangle) -> str:
# Accessing fields is just another kind of expression
return str(rect.length) + "x" + str(rect.width)
box = Rectangle(4, 5)
print("The box size is " + as_string(box))
There is nothing special about the values stored in the fields of instances.
The attribute access expression using the period is no different from other expressions such as a variable lookup, math operation, or function call.
That means we can pass the result of such expressions to other function calls, math operations, or even return them.
In this example, we use the str function by passing in the values associated with the fields of the given rect instance to create a nice string representation of the instance.
Conceptually, you need to think about whether the function needs the ENTIRE instance or just a specific field from the instance. Then, you can decide whether to define a function that consumes the instance or a function that consumes a field from the instance. Often, directly passing the entire dataclass instance is more convenient than passing in multiple fields.
<, >, etc.) or used with any other operators we have learned so far (+, %, etc.).. and the name of the field.