Skip to content
wannacode

Your own type

Problem

The prices got mixed up

You are writing the shopping cart of an online store. Every product has a name and a price, so you keep them in two lists. Index 0 holds the name and price of the first product, index 1 those of the second.

You want the most expensive one first, so you swap the prices:

names = ["Bread", "Milk", "Coffee"]
prices = [1.20, 2.10, 7.50]
# Most expensive first: swap the first and the third price.
prices[0], prices[2] = prices[2], prices[0]
for i in range(len(names)):
print(f"{names[i]}: €{prices[i]}")
Output
Bread: €7.5
Milk: €2.1
Coffee: €1.2

Bread now costs €7.50. The prices are swapped, but the names are not. The two lists must always change together, but nothing makes you do it. Add a third list for the quantities, and you have to keep three in mind.

The idea

One product, one place

The name and the price are not separate things. They are parts of one product. We want a "product" type, just like there is int for numbers and str for text.

You make a type like that yourself. It is called a class:

class Product:
def __init__(self):
self.name = ""
self.price = 0.0
self.quantity = 0

self.name, self.price and self.quantity are the fields of the product (in Python they are also called attributes). Every product will have its own name, its own price and its own quantity.

In Python you do not list the fields separately. You create them with a starting value in __init__: empty text, 0.0 and 0. For now, write the line def __init__(self): exactly like this. The code under it runs for every new product, and self is that product. You will understand it in detail in the lesson "Constructors and self".

The class is only a description: "every product has a name, a price and a quantity". There is no product yet.

The name of the class starts with a capital letter (Product), and the names of the fields with a lowercase one (name, price). This way you see at once what is a type and what is a value.

The idea

Product(): from the description to a product

You create a concrete product from the class by calling the class like a function: Product(). It is called an object. You reach its fields with a dot:

shop.py
class Product:
def __init__(self):
self.name = ""
self.price = 0.0
self.quantity = 0
bread = Product()
bread.name = "Bread"
bread.price = 1.20
bread.quantity = 2
coffee = Product()
coffee.name = "Coffee"
coffee.price = 7.50
coffee.quantity = 1
print(f"{bread.name}: €{bread.price * bread.quantity}")
print(f"{coffee.name}: €{coffee.price * coffee.quantity}")
Output
Bread: €2.4
Coffee: €7.5
  • The class is above the code that uses it. Python reads the file from top to bottom and must have seen it already. A file can have several classes.
  • Python has no new. Product() creates a new object and returns it. Every object has its own fields: the bread and the coffee do not get in each other's way.
  • You do not write a type in front of bread, but the object still has a type: type(bread) gives <class '__main__.Product'>.
  • bread.price is "the price of the bread". You read it and write it like an ordinary variable.

Now the name and the price of the product are in one place. Move the product, and they move together.

Try it

Nothing is filled in

If you create a product and fill in nothing, what is in its fields?

What will the program print?

item = Product()
print(item.name)
print(item.price)
print(item.quantity)
Answer

Try it

What is missing?

What goes in the blank so that there is a new product?

milk = ()
milk.name = "Milk"
milk.price = 2.10

See it

The variable points to the object

You want a sale on coffee, so you make a "copy" to change its price:

coffee = Product()
coffee.price = 10
sale = coffee
sale.price = 8
print(coffee.price) # 8
Step by step, in words
  1. Product() makes the object, and __init__ gives its fields starting values. coffee gets an arrow to it.
  2. Follow the arrow from coffee: the price of the object becomes 10.
  3. sale = coffee copies the arrow, not the object. Two arrows, one object.
  4. Follow the arrow from sale: the price in the same object becomes 8.
  5. coffee points to the same object, so it prints 8.

The price of the coffee became 8 too. The reason is that the variable coffee is not the object itself. The object lives separately in memory, and coffee keeps where it is: an arrow to the object. A variable like that is called a reference.

sale = coffee copies the arrow, not the object. Now two arrows point to one object, so the change through sale shows through coffee too.

You saw the same thing in the lesson on lists: sale = prices gave a second name to the same list. A list is an object too.

The idea

The discount works now

In the "Functions" module, the discount did not work: the function got the price, changed its own parameter, and the change was lost. With an object it is different:

def discount(p, percent):
p.price = p.price * (100 - percent) / 100
coffee = Product()
coffee.price = 10
discount(coffee, 10)
print(coffee.price)
Output
9.0

The parameter is still a copy, but a copy of the arrow. It points to the same object, so the change to p.price stays after the function too.

More precisely, if you are curious

If the function puts a new object in the parameter, it only moves its own copy of the arrow. The object outside does not change:

def reset(p):
p = Product()
p.price = 0
coffee = Product()
coffee.price = 10
reset(coffee)
print(coffee.price)
Output
10
That is why people say Python passes everything the same way as = does: the parameter gets an arrow to the same object. If the function points its arrow somewhere else, that does not reach the variable outside.

The idea

None: an arrow to nowhere

A variable may point to no object at all. Then it holds None: "nothing". If you try to read a field through it, there is no object to read it from:

gift = None
print(gift.name)

The program starts, but it stops with an error when it reaches the line:

Error
AttributeError: 'NoneType' object has no attribute 'name'

This is the most common mistake with objects. What an "exception" is and how you catch one, you will see in the module "Errors and exceptions".

A list also holds arrows, not the products themselves. [None] * 3 makes three places that do not point to a product yet. Each one is None until you put Product() there:

cart = [None] * 3
print(cart[0] is None)
cart[0] = Product()
cart[0].name = "Bread"
print(cart[0].name)
Output
True
Bread

Try it

Find the bug

The program should print "Bread" and "Milk". Instead, it prints "Milk" twice. Which line has the bug?

Pick the line with the bug.

Exercise

Your classes

Solve the tasks in your editor. The class goes at the top of the file, and you create the objects in the code below it.

1. Book. A class Book with fields for the title and the author. Create two books and print them.

Output
Under the Yoke by Ivan Vazov
Tobacco by Dimitar Dimov

2. Car. A class SimpleCar with a field for the speed. Create a car (its speed is 0), increase the speed by 10 and print it. In the lesson "Behavior" this will become a method of the car.

Output
Speed after accelerating: 10

3. The cart. The cart from the first card, this time with a list of three Product objects: bread €1.20 (2 pieces), milk €2.10 (1 piece), coffee €7.50 (1 piece). Print the amount for each line and the total. Create each product with Product() first, then put it in the list.

Output
Bread x2: €2.4
Milk x1: €2.1
Coffee x1: €7.5
Total: €12.0

4. Most expensive first. Sort the same cart by price, from the most expensive to the cheapest, with the bubble sort from the lesson on lists. This time you swap whole products, so the name and the price move together.

Output
Coffee: €7.5
Milk: €2.1
Bread: €1.2

More tasks

  • Student. A class Student with a name and three grades (three fields). Create two students and print the average grade of each.
  • One book, two names. Create a book, store it in two variables and change the title through the second one. Print the title through the first one. Explain in words why it works this way.

Test

Check yourself

1. What is the difference between a class and an object?
2. sale = coffee sale.price = 8 What happens to the price of coffee?
3. What is in the list right after cart = [None] * 3?
4. A function takes a product as a parameter and changes its price. Can you see the change after the call?
5. gift = None What happens at gift.name?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·