Class, Instance & Functions in Class
class SoftwareEngineer:
# class attributes : these attributes can be accessed using the class object
# SoftwareEngineer.alias ✅
alias = "Keyboard Magician"
def __init__(self, name, age, salary):
# instance attributes : these are tied to only one instance
# e.g., let's say se1 is an instance of the class SoftwareEngineer
# these instance attributes can only be accessed through that instance
# se1.name, se1.age, se1.salary ✅
# but it is not possible to access through the class object itself
# SoftwareEngineer.name, SoftwareEngineer.age, SoftwareEngineer.salary ❌
self.name = name
self.age = age
self.salary = salary
# instance method
def code(self):
print(f"{self.name} is writing code...")
def code_in_language(self, language):
print(f"{self.name} is coding in {language}")
# def information(self):
# information = f"Name = {self.name}, Age = {self.age}, Salary = {self.salary}"
# return information
# dunder methods
# string reprresentation method
def __str__(self):
information = f"Name = {self.name}, Age = {self.age}, Salary = {self.salary}"
return information
# equals method
def __eq__(self, other):
return self.name == other.name and self.age == other.age and self.salary == other.salary
# the below method is not tied to a specific instance, and can be accessed directly by the class itself
@staticmethod
def entry_salary(age):
if age < 25:
return 70000
elif age < 30:
return 80000
return 100000
se1 = SoftwareEngineer("Max", 29, 70000)
se2 = SoftwareEngineer("Max", 29, 70000)
# print(SoftwareEngineer.name) -> not possible to access instance attributes using class name (object)
# print(SoftwareEngineer.alias)
# print(se1.name)
# print(se1.alias)
# print(se1)
# print(se1)
# print(se2)
# print(se1 == se2)
print(se1.entry_salary(25))
print(SoftwareEngineer.entry_salary(25))
Inheritance
# Inheritance (inherit, extend, override)
class Employee:
def __init__(self, name, age, salary):
self.name = name
self.age = age
self.salary = salary
def work(self):
print(f"{self.name} is working...")
class SoftwareEngineer(Employee):
def __init__(self, name, age, salary, level):
super().__init__(name, age, salary)
self.level = level
def work(self):
print(f"{self.name} is coding...")
def debug(self):
print(f"{self.name} is debugging...")
class Designer(Employee):
def __init__(self, name, age, salary):
super().__init__(name, age, salary)
def work(self):
print(f"{self.name} is designing...")
def draw(self):
print(f"{self.name} is drawing...")
se = SoftwareEngineer("Max", 27, 60000, "Junior")
d = Designer("Alex", 28, 70000)
print(f"{se.name}, {se.age}, {se.level}")
se.work()
se.debug()
print(f"{d.name}, {d.age}, {d.salary}")
d.work()
d.draw()
Encapsulation
# Encapsulation
class SoftwareEngineer:
def __init__(self, name, age):
self.name = name
self.age = age
# protected instance variables: name starts with a single underscore
# private instance variables: name starts with a double underscore
self._salary = None
self._num_bugs_solved = 0
def code(self):
self._num_bugs_solved += 1
# getter
def get_salary(self):
return self._salary
# setter
def set_salary(self, base_value):
# check value, enforce constraints
self._salary = self._calculate_salary(base_value)
# private function: name starts with a leading underscore
def _calculate_salary(self, base_value):
if self._num_bugs_solved < 10:
return base_value
if self._num_bugs_solved < 100:
return base_value * 2
return base_value * 3
se = SoftwareEngineer("Max", 27)
print(se.age)
for i in range(70):
se.code()
se.set_salary(5000)
print(se.get_salary())
-----------------------------------------------------------------------------------
# Pythonic way of wrting getter and setter
class SoftwareEngineer:
def __init__(self):
self._salary = None
@property
def salary(self):
return self._salary
@salary.setter
def salary(self, value):
self._salary = value
@salary.deleter
def salary(self):
del self._salary
se = SoftwareEngineer()
se.salary = 5000
print(se.salary)