Data Science Hub
  • Data Science Hub
  • STATISTICS
    • Introduction
    • Fundamentals
      • Data Types
      • Central Tendency, Asymmetry, and Variability
      • Sampling
      • Confidence Interval
      • Hypothesis Testing
    • Distributions
      • Exponential Distribution
    • A/B Testing
      • Sample Size Calculation
      • Multiple Testing
  • Database
    • Database Fundamentals
    • Database Management Systems
    • Data Warehouse vs Data Lake
  • SQL
    • SQL Basics
      • Creating and Modifying Tables/Views
      • Data Types
      • Joins
    • SQL Rules
    • SQL Aggregate Functions
    • SQL Window Functions
    • SQL Data Manipulation
      • String Operations
      • Date/Time Operations
    • SQL Descriptive Stats
    • SQL Tips
    • SQL Performance Tuning
    • SQL Customization
    • SQL Practice
      • Designing Databases
        • Spotify Database Design
      • Most Commonly Asked
      • Mixed Queries
      • Popular Websites For SQL Practice
        • SQLZoo
          • World - BBC Tables
            • SUM and COUNT Tutorial
            • SELECT within SELECT Tutorial
            • SELECT from WORLD Tutorial
            • Select Quiz
            • BBC QUIZ
            • Nested SELECT Quiz
            • SUM and COUNT Quiz
          • Nobel Table
            • SELECT from Nobel Tutorial
            • Nobel Quiz
          • Soccer / Football Tables
            • JOIN Tutorial
            • JOIN Quiz
          • Movie / Actor / Casting Tables
            • More JOIN Operations Tutorial
            • JOIN Quiz 2
          • Teacher - Dept Tables
            • Using Null Quiz
          • Edinburgh Buses Table
            • Self join Quiz
        • HackerRank
          • SQL (Basic)
            • Select All
            • Select By ID
            • Japanese Cities' Attributes
            • Revising the Select Query I
            • Revising the Select Query II
            • Revising Aggregations - The Count Function
            • Revising Aggregations - The Sum Function
            • Revising Aggregations - Averages
            • Average Population
            • Japan Population
            • Population Density Difference
            • Population Census
            • African Cities
            • Average Population of Each Continent
            • Weather Observation Station 1
            • Weather Observation Station 2
            • Weather Observation Station 3
            • Weather Observation Station 4
            • Weather Observation Station 6
            • Weather Observation Station 7
            • Weather Observation Station 8
            • Weather Observation Station 9
            • Weather Observation Station 10
            • Weather Observation Station 11
            • Weather Observation Station 12
            • Weather Observation Station 13
            • Weather Observation Station 14
            • Weather Observation Station 15
            • Weather Observation Station 16
            • Weather Observation Station 17
            • Weather Observation Station 18
            • Weather Observation Station 19
            • Higher Than 75 Marks
            • Employee Names
            • Employee Salaries
            • The Blunder
            • Top Earners
            • Type of Triangle
            • The PADS
          • SQL (Intermediate)
            • Weather Observation Station 5
            • Weather Observation Station 20
            • New Companies
            • The Report
            • Top Competitors
            • Ollivander's Inventory
            • Challenges
            • Contest Leaderboard
            • SQL Project Planning
            • Placements
            • Symmetric Pairs
            • Binary Tree Nodes
            • Interviews
            • Occupations
          • SQL (Advanced)
            • Draw The Triangle 1
            • Draw The Triangle 2
            • Print Prime Numbers
            • 15 Days of Learning SQL
          • TABLES
            • City - Country
            • Station
            • Hackers - Submissions
            • Students
            • Employee - Employees
            • Occupations
            • Triangles
        • StrataScratch
          • Netflix
            • Oscar Nominees Table
            • Nominee Filmography Table
            • Nominee Information Table
          • Audible
            • Easy - Audible
          • Spotify
            • Worldwide Daily Song Ranking Table
            • Billboard Top 100 Year End Table
            • Daily Rankings 2017 US
          • Google
            • Easy - Google
            • Medium - Google
            • Hard - Google
        • LeetCode
          • Easy
  • Python
    • Basics
      • Variables and DataTypes
        • Lists
        • Dictionaries
      • Control Flow
      • Functions
    • Object Oriented Programming
      • Restaurant Modeler
    • Pythonic Resources
    • Projects
  • Machine Learning
    • Fundamentals
      • Supervised Learning
        • Classification Algorithms
          • k-Nearest Neighbors
            • kNN Parameters & Attributes
          • Logistic Regression
        • Classification Report
      • UnSupervised Learning
        • Clustering
          • Evaluation
      • Preprocessing
        • Scalers: Standard vs MinMax
        • Feature Selection vs Dimensionality Reduction
        • Encoding
    • Frameworks
    • Machine Learning in Advertising
    • Natural Language Processing
      • Stopwords
      • Name Entity Recognition (NER)
      • Sentiment Analysis
        • Agoda Reviews - Part I - Scraping Reviews, Detecting Languages, and Preprocessing
        • Agoda Reviews - Part II - Sentiment Analysis and WordClouds
    • Recommendation Systems
      • Spotify Recommender System - Artists
  • Geospatial Analysis
    • Geospatial Analysis Basics
    • GSA at Work
      • Web Scraping and Mapping
  • GIT
    • GIT Essentials
    • Connecting to GitHub
  • FAQ
    • Statistics
  • Cloud Computing
    • Introduction to Cloud Computing
    • Google Cloud Platform
  • Docker
    • What is Docker?
Powered by GitBook
On this page
  • Code Breakdown:
  • Inheritance
  • Overriding Parent Class Attributes
  • Instances as Attributes
  • Exporting and Importing Classes
  • Importing a Single Class
  • Importing Multiple Classes
  • Importing All Classes
  • Importing an Entire Module
  • Conclusion

Was this helpful?

  1. Python
  2. Object Oriented Programming

Restaurant Modeler

Last updated 1 year ago

Was this helpful?

In this exercise, we will be modeling a real-world scenario, a restaurant app that will be simulating restaurants and a bakery in New York City's Boroughs:

We will first be creating our first class Restaurant() in which we also be creating multiple attributes like, name, cuisine, location, and methods such as restaurant_type and restaurant_location. Here is our base code:

class Restaurant():
    """A Restaurant Modeler"""

    def __init__(self, name, cuisine,location):
        """Initialize name and cusisine attributes"""
        self.name = name
        self.cuisine = cuisine
        self.location = location
        self.capacity = 30   # a default value

    def restaurant_type(self):
        """Displays the name and the cuisine of the restraurant"""
        a_or_an = ''
        if self.cuisine[0] in ('a', 'e', 'i','o'):  # 'u' is not in the list!
            a_or_an = 'an'
        else:
            a_or_an = 'a'
        print(f"{self.name.title()} is {a_or_an} {self.cuisine.title()} Restaurant.")

    def restaurant_location(self):
        """Displays the name and the location of the restraurant"""
        print(f"{self.name.title()} is located in {self.location.title()}.")

    def get_description(self):
        """Returns description of the restaurant, which includes name, cuisine and location"""
        a_or_an = ''
        if self.cuisine[0] in ('a', 'e', 'i','o'):  # 'u' is not in the list!
            a_or_an = 'an'
        else:
            a_or_an = 'a'
        rest_desc = f"{self.name.title()} is {a_or_an} {self.cuisine.title()} Restaurant \
and located in {self.location.title()}."
        return rest_desc
    
    def restaurant_capacity(self):
        """Displays the name and the capacity of the restraurant"""
        print(f"{self.name.title()} has {str(self.capacity)} people dine-in capacity.")

    def update_capacity(self,capacity):
        """Set the capacity of the restaurant"""
        self.capacity = capacity

Code Breakdown:

The __init__ method defined as the first function is a special method, and Python runs it whenever we create a new instance based on the class. The use of two underscore before and after the name 'init' is a convention and prevents default Python method names from conflicting with user defined method names.

there are 4 parameters defined in the __init__ method:

  • self

  • name

  • cuisine

  • location

Note that capacity parameter is not among he method parameters. This is on purpose and to show how we can add default attributes to the classes.

self is required in the method definition, and comes first before all other parameters. It enables individual instance access to the methods and the attributes in the class. When we create an instance of the class, it is passed automatically, so doesn't require manual entry. We provide values only for other parameters defined in the method. Any variable with the self prefix is available to every method in the class.

Also, notice that not all the attributes come from the parameters such as capacity. Because it is not required parameter like others. Here we defined an attribute with a default value that can be accessed with a call to that attribute or get updated it with a method. This is a very useful feature since we might want to assign certain default values to attributes and not to require manual entry from the user.

Let's see some examples. We will be creating 5 instances of the Restaurant class below each of which has different values.

res1 = Restaurant('jj','american', 'manhattan')
res1.restaurant_type()        # Output: Jj is an American Restaurant.
res1.restaurant_location()    # Output: Jj is located in Manhattan.        

res2 = Restaurant('soma','uruguayan', 'bronx')
res2.restaurant_type()        # Output: Soma is a Uruguayan Restaurant.
res2.restaurant_location()    # Output: Soma is located in Bronx.

res3 = Restaurant('essen','german', 'brooklyn')
res3.restaurant_type()        # Output: Essen is a German Restaurant.
res3.restaurant_location()    # Output: Essen is located in Brooklyn.

res4 = Restaurant('Xsi','chinese', 'queens')
print(res4.get_description())    # Output: Xsi is a Chinese Restaurant and located in Queens.
res4.restaurant_capacity()       # Output: Xsi has 30 people dine-in capacity.     

res5 = Restaurant("silvio's kitchen","italian","staten island")
res5.update_capacity(12)
res5.restaurant_capacity()    # Output: Silvio's  Kitchen has 12 people dine-in capacity.

Note that for Restaurant 4 we used the print function to be able to display the description since the get_description method returns only a variable called rest_desc and does not have a print statement.

In addition, for Restaurant 5, we have updated the default restaurant capacity (from 30 to 12) first and then called the restaurant_capacity method to show the number of people can the restaurant accommodate.

Now let s add an attribute named patrons_served with a default value 0:

def __init__(self, name, cuisine,location):
     #...
     self.patrons_count = 0   # default value
     
#...

def patrons_served(self):
    """Displays the name of the restaurant and the number of customers have been served"""
    print(f"{self.name.title()} has {str(self.patrons_count)} customers at the moment.")

Time to create an instance, call it res6 of our Restaurant class and call the attribute patrons_served:

res6 = Restaurant("tio t", "mexican","manhattan")
res6.patrons_served()    # Output: Tio T has 0 customers at the moment.

Not let us create another method, called update_patrons to update the number of customers the Restaurant has served for a given time on a day:

def set_patrons(self, n):
    """Set the number of customers have been served."""
    self.patrons_count = n

We call the method set_patrons with a value of 26 and recall the patrons_served again we get the updated the patron count:

res6.set_patrons(26)
res6.patrons_served()   # Output: Tio T has served 26 customers so far.

Not let us add functionally to increase the number of customers served with a method called increase_patrons :

def increase_patrons(self, increment):
    """Increase the number of customers by 'increment' """
    self.patrons_count += increment
    print(f"{self.name.title()} has served {str(self.patrons_count)} customers so far.")

When we call the method with previous instance:

res6.increase_patrons(14)   # Output: Tio T has served 40 customers so far.

Inheritance

If we are working on a class that is sort of a version of class we worked on before, we can use what is called Inheritance to adopt all methods and attributes of the original class. By convention, the original class if called the parent class, and the new class we are creating is called the child class.

To be able use all the features a parent class offers we need to pay attention to the 2 points:

  • Child class wraps around the parent class in the definition: class Child (Parent):

  • super() function used with the __init__ method

Let us create a special type of restaurant class, Bakery, to our script and inherit all features Restaurant, the parent class, has:

class Bakery(Restaurant):
    """ A Bakery Modeler: Represents aspects of a restaurant """
    def __init__(self, name, cuisine, location):
        """Initialize attributes of the parent class"""
        super().__init__(name, cuisine, location)

Since we created our new class, Bakery, it is time to create an instance and call some of the methods it inherited from the Restaurant class:

bakery = Bakery("katrin", "german", "brooklyn")
print(bakery.get_description())
bakery.increase_patrons(42)

Output:

Katrin is a German Restaurant and located in Brooklyn.
Katrin has served 42 customers so far.

Overriding Parent Class Attributes

The methods work perfectly with only one minor issue: a bakery is a type of a restaurant but NOT a restaurant. Let's solve this by updating our code, with an attribute similar to capacity and patrons_count, and called category in our parent class Restaurant with a set value restaurant, replace all 'Restaurant' statements with it in all print statements, and finally overwrite it in our child class, Bakery:

class Restaurant():
   def __init__(self, name, cuisine,location):
      ...
      self.category = 'restaurant'
      
  
   def restaurant_type(self):
      ...
      # print(f"{self.name.title()} is {a_or_an} {self.cuisine.title()} Restaurant.")
      print(f"{self.name.title()} is {a_or_an} {self.cuisine.title()} {self.category.title()}.")
      
      

   def get_description(self):
      ...
      
      #rest_desc = f"{self.name.title()} is {a_or_an} {self.cuisine.title()} Restaurant and located in {self.location.title()}."
      rest_desc = f"{self.name.title()} is {a_or_an} {self.cuisine.title()} {self.category.title()} and located in {self.location.title()}."


class Bakery(Restaurant):
    """ A Bakery Modeler: Represents aspects of a restaurant """
    def __init__(self, name, cuisine, location):
        """Initialize attributes of the parent class"""
        super().__init__(name, cuisine, location)
        self.category = 'bakery'

Now, if we call the methods on our bakery instance, we will get the desired outcome:

bakery = Bakery("katrin", "german", "brooklyn")
print(bakery.get_description())
bakery.increase_patrons(42)

Output:

Katrin is a German Bakery and located in Brooklyn.
Katrin has served 42 customers so far.

Instances as Attributes

As we can see there is no limit to the number of methods or attributes we can add to a class. However, adding multiple methods and attributes may result in complicated scripts and make things more difficult than easier. For instance, let us say we would like to add create a coffee menu instance for our bakery which can easily be done as we seen above. However, we when think about it a coffee, it has multiple attributes like type, size, extras, etc. This might complicate our Bakery class considering other attributes and methods the class has. Therefore, it will be better if we create another class only for coffees, and add its instances as attributes. In the following example, we create a class called Coffee and add it to the Bakery class as an attribute called coffee.

class Coffee():
    """A Coffee Modeler"""
    def __init__(self, coffee='americano', size='small', extras=None):
        """Initialize a coffee's attributes"""
        self.coffee_menu = ['Americano', 'Cappucino', 'Double Espresso', 'Latte', 'Macchato', 'Mocha']
        self.coffee = coffee
        self.size = size
        self.extras = extras
    

    def get_coffee_description(self):
        if self.extras == None:
            print(f"Your order of {self.size.title()} {self.coffee.title()} is ready!")
        else:
            print(f"Your order of {self.size.title()} {self.coffee.title()} with extra {self.extras.title()} is ready!")


    def update_coffee(self, coffee):
        """Updates coffee ordered"""
        self.coffee = coffee

    
    def update_extras(self, size):
        """Add extras to the coffee"""
        self.size += size


    def update_extras(self, requests):
        """Add extras to the coffee"""
        self.extras += requests
        
        


class Bakery(Restaurant):
    ...
    def __init__(self, name, cuisine, location):
        ...
        self.coffee = Coffee()

We can now access to all Coffee class methods and attributes from the Bakery's coffee attribute:

bakery = Bakery("katrin", "german", "brooklyn")
print(bakery.get_description())
bakery.increase_patrons(42)
print(bakery.coffee.coffee)
print(bakery.coffee.size)
bakery.coffee.get_coffee_description()

Output:

Katrin is a German Bakery and located in Brooklyn.
Katrin has served 42 customers so far.
Americano
small
Your order of Small Americano is ready!

Breakdown of the new lines

  • print(bakery.coffee.coffee) tells python to look at the instance bakery, find its coffee attribute and find the coffee attribute that is associated Coffee instance with the and print it

  • print(bakery.coffee.size) similarly tells python to look at the instance bakery, find its coffee attribute and find the size attribute attribute that is associated Coffee instance and print it

  • bakery.coffee.get_coffee_description() like the others above first tells python to look at the instance bakery, find its coffee attribute and call the get_coffee_description() that is associated with the Coffee instance stored in the attribute

Exporting and Importing Classes

Once we complete our classes, it is time to save our work. Different from regular scripts, ie. python files that consist of our codes, the scripts that include classes are called modules, aka packages or libraries. We will save our module as restaurant.py . Our module has three classes:

  • Restaurant()

  • Coffee()

  • Bakery()

Once we save our module, importing it or some of its classes is no different than importing your favorite modules/packages such as numpy or pyplot from matplotlib library.

Importing a Single Class

In a separate python file, called my_favorite_places.py, we will import Restaurant class and create instances from that class.

#my_favorite_places.py
from restaurant import Resturant

helena_restaurant = Restaurant('helena','gerek','san francisco')
print(helena_restaurant.get_description())    

# Output: Helena is a Greek Restaurant and located in San Francisco.

Importing Multiple Classes

We can in practice import as many classes as we need. Let's say we want to create a restaurant and a bakery in the same script, we then need to import the classes Restaurant and Bakery:

from restaurant import Restaurant, Bakery

helena_restaurant = Restaurant('helena','greek','san francisco')
print(helena_restaurant.get_description())

gina_bakery = Bakery('gina','french','palo alto')
print(gina_bakery.get_description())

Output:

Helena is a Greek Restaurant and located in San Francisco.
Gina is a French Bakery and located in Palo Alto.

Importing All Classes

We can also simply import all the classes using '*' and create instances of classes as we did above:

from restaurant import *

helena_restaurant = Restaurant('helena','greek','san francisco')
print(helena_restaurant.get_description())

gina_bakery = Bakery('gina','french','palo alto')
print(gina_bakery.get_description())

Importing an Entire Module

If we need to use many classes then it is better to import the entire module. In this case, we will need to use dot notation to access the classes:

import restaurant

helena_restaurant = restaurant.Restaurant('helena','greek','san francisco')
print(helena_restaurant.get_description())

gina_bakery = restaurant.Bakery('gina','french','palo alto')
print(gina_bakery.get_description())

Output:

Helena is a Greek Restaurant and located in San Francisco.
Gina is a French Bakery and located in Palo Alto.

Conclusion

Congratulations! With this, we have finished our OOP practice! You might now think that you would create certain methods or classes differently, and I would say you are absolutely right! As stated above, our options are limitless on how we model real-world cases. Most of the cases, there are no right or wrong approaches to modeling. It is all about efficiency and it comes with experience. The more we are modeling real 'objects' the more efficient we will become.

Boroughs of New York City
Page cover image