04. Python OOP
1. Overview
1.1. Object Oriented Programming
Consider programming as a collection of objects which can handle data and communicate with each others
-
Pros and Cons
- Pros
- Modularization is suitable for a big project
- Easy to be maintained
- Cons
- Hard to be designed at first
- relatively slower than Procedure Oriented Programming
- Pros
- Characteristics
- Abstraction
- Inheritance
- Polymorphism
- Encapsulation
1.2. Class
As a blueprint for an object, a class provides a way to bind attrubutes(data) and methods(functions) together
1.3. Object
Data uploaded on memory based on the definition of the class
1.4. Basic Grammer
-
Define a Class
class Person: num = 0 name ='human' @classmethod def class_greeting(cls): print('hello,', cls.name) def __init__(self, name = 'Kim'): Person.num += 1 self.name = name def greeting(self): print('hello,', self.name) print(type(Person)) # <class 'type'>
-
Make an Instance
person1 = Person() print(isinstance(person1, Person)) # True print(type(person1)) # <class '__main__.Person'> # __main__: describes that class Person is defined in the current module(.py)
-
Use attributes
# class attribute print(Person.name) # human # instance attribute print(person1.name) # Kim
-
Call methods
# class method Person.class_greeting() # Hello, human # instance method person1.greeting() # Hello, Kim
1.5. Namespace
Python finds the names in the following order
- instance
- Child Class
- Parent Class
2. Attributes
2.1. Class Attributes
Class attributes are class variables that are inherited by every object of a class.
- Class attributes are defined outside the __init__() function.
- Class attributes can be added by
Class.attribute = initial value
2.2. Instance Attributes
Instance instance attributes arevariables that allow us to define different values for each object(instance) of a class.
- Instance attributes are defined in the __init__() function.
- Instance attributes can be added by
instance.attribute = initial value
class Person:
name = 'lee'
def __init__(self, name):
self.name =name
Person.age = 21 # Class attributes can be added
print(Person.name)
print(Person.age)
person1 = Person('Kim')
person1.age = 23 # Instance attributes can be added
print(person1.name)
print(person1.age)
# 클래스 이름공간(변수, 메소드) 검색
print(dir(Person))
# 클래스 이름공간(변수, 메소드) 검색
print(dir(person1.__class__))
# 인스턴스 변수 검색
print(vars(person1))
3. Methods
3.1. Class Methods
Methods that only uses class variables
- The first parameter should be
cls
which indciates the class @classmethod
should be used before defining the function, otherwise thecls
parameter can't get the class as the argument.
class Person:
count=0
@classmethod
def number_of_population(cls):
print(f'the num of population is {cls.count}')
Person.number_of_population() # the num of population is 0
3.2. Instance Methods
Methods that can use both class variables and instance variables
- The first parameter must be
self
3.3. Static methods
Methods that don't use both class variables and instance variables
@staticmethod
should be used before defining the function.
class Person:
count=0
def __init__(self, name):
self.name = name
Person.count += 1
@staticmehtod
def check_rich(money):
return money > 10000
person1 = Person('Kim')
print(Person.check_rich(100000)) # True
print(person1.check_rich(100000)) # True
3.4. Decorator
The decorator receives the function as an argument, and returns it by adding a specific code to the function.
def hello(name):
print("hello,", name)
def add_print(original):
print("function starts")
original(*args)
print("function ends")
return wrapper
add_print(hello)('James') # add_print(original()) : means tht add_print is gonna use the return vale of original function
# function starts
# hello
# function ends
@add_print
def print_hello(name):
print("hello,", name)
print_hello('Anna')
# function starts
# hello, Anna
# function ends
3.5. Magic Methods (Dunder Methods)
Magic methods (dunder methods) in Python are the special methods that start and end with the double underscores.
Magic methods are not meant to be invoked directly by you, but the invocation happens internally from the class on a certain action.
-
Construtor(__init__)
called when making objectsclass Person: def __init__(self, name): self.name = name person1 = Person('Kim') print(person1.name) # Kim
-
Destructor(__del__) called when deleting objects
class Person: def __del__(self): print('인스턴스가 사라졌습니다.') person1 = Person() del person1 # 인스턴스가 사라졌습니다.
-
__add__
To get called on add operation using + operatorclass Person: def __init__(self, name): self.name = name def __add__(self, another): return self.name + ' married ' + another.name person1 = Person('Kim') person2 = Person('Lee') print(person1 + person2) # Kim married Lee
4. Inheritence
Inheritance allows us to define a class that inherits all the methods and properties from another(parent) class.
Parent class is the class being inherited from, also called base class.
Child class is the class that inherits from another class, also called derived class.
Object is an ancestor of all classes.
class ChildClass(ParentClass):
child = ChildClass()
print(issubclass(ChildClass, ParentClass)) # True
print(issubclass(ChildClass, Object)) # True
4.1. Simple Inheritance
Child class can use Parent class's class instances and methods.
class Human:
name = 'name'
age = 'age'
def __init__(self):
self.city = 'city'
def show(self):
print('This is a method of Human class')
class Student(Human):
def __init__(self, name):
self.name = name
def show_name(self):
print(self.name)
def show_age(self):
print(self.age)
def show_city(self):
print(self.city)
a = Student('James')
a.show() # This is a method of Human class
a.show_name() # James
a.show_age() # age
a.show_city() # Attrubute Error!
4.2. super().__init__()
To use parent class's instance attributes in child class, you need to excute parent class's constructor by using super().__init__()
.
class Human:
def __init__(self):
self.name = 'name'
self.city = 'city'
def show(self):
print('This is a method of Human class')
class Student(Human):
def __init__(self, name):
super().__init__()
self.name = name
def show_name(self):
print(self.name)
def show_city(self):
print(self.city)
a = Student('James')
a.show() # This is a method of Human class
a.show_name() # James
a.show_city() # city
super().__init()__
simply means that you're using parent class's __init__()
method.
class Human:
def __init__(self):
self.name = 'name'
self.city = 'city'
def show(self):
print('This is a method of Human class')
class Student(Human):
def __init__(self, name):
self.name = name
super().__init__()
def show_name(self):
print(self.name)
a = Student('James')
a.show_name() # name
4.3. super().method
In the same manner as super().__init__()
, you can call parent's method in the child class's method.
class Human:
def __init__(self):
self.name = 'name'
self.age = 'age'
self.city = 'city'
def show_everything(self):
print(self.name, self.age, self.city)
class Student(Human):
def __init__(self):
super().__init__()
def introduce(self):
print('Let me introduce myself')
super.show_everything()
a = Student()
a.introduce() # Let me introduce myself
# name age city
4.4. super(Class, self)
By using super(Class, self)
, you can specify which class of parent's method to use.
class A:
def __init__(self):
self.a = 10
def get_a(self):
return self.a
class B(A):
def __init__(self):
super(B, self).__init__()
self.b = 20
def get_b(self):
return self.b
class C(B):
def __init__(self):
super(B, self).__init__()
self.c = 30
def get_c(self):
return self.c
new_c = C()
print(new_c.get_a()) # 10
print(new_c.get_c()) # 30
print(new_c.get_b()) # AttributeError
# B.__init__() has not excuted!
4.5. Multiple Inheritence
Python supports multiple inheritance.
If there are multiple parents, Pyhton searches the namespace in the order in which the class are received.
class Person:
def __init__(self, name):
self.name = name
def greeting(self):
return f'Hi, {self.name}'
class Mom(Person):
gene = 'XX'
def swim(self):
return 'Mom\'s Swimming'
class Dad(Person):
gene = 'XY'
def walk(self):
return 'Dad\'s walking'
class Child(Mom, Dad):
def swim(self):
return 'A child\'s swimming'
def cry(self):
return 'A child\'s crying'
baby1 = FirstChild('Lee')
print(baby1.cry()) # A child's crying
print(baby1.swim()) # A child's swimming
print(baby1.walk()) # Dad's walking
print(baby1.gene()) # XX <<< since (Mom, Dad)
4.6.1. Class.mro (Method Resolution Order)**
mro method shows all the parent classes(including self) in method resolution order.
print(FirstChild.mro())
#[<class '__main__.FirstChild'>, <class '__main__.Mom'>, <class '__main__.Dad'>, <class '__main__.Person'>, <class 'Object'>]
5. Polymorphism
The same name of method can work differently according to classes.
5.1. Override
Redefine a inherited mehtod in child class.
class Person:
def __init__(self, name):
self.name = name
def talk(self):
print(f'Hi, I\'m {self.name}')
class Professor(Person):
def talk(self):
print(f'Hi, I\'m professor {self.name}')
class Student(Person):
def talk(self):
super().talk()
print(f'Hi, I\'m a student')
p1 = Professor('Kim')
p1.talk() # Hi, I'm professor Kim
s1 = Student('Lee')
s1.talk() # Hi, I'm Lee
# Hi, I'm a student
5.2. Overloading
Overloading refers to the ability to use a single identifier to define multiple methods of a class that differ in their input and output parameters.
Python does not support overloading officially because Python can transfer multiple variables as one object(tuple)
6. Encapsulation
Encapsulation refers to limiting access to certain class attributes.
6.1. Public member
- Public memebers can be accessed from everywhere
- All members of a class are by default public in Python.
6.2. Protected Member
-
Protected members of a class can be accessed from the class and its subclasses.
-
In fact, protected memebers still can be accessed and modified outside of the classes. It's just a protocol between developers.
-
Can be declared by adding a prefix '_'
class Student: _schoolName = 'XYZ School' def __init__(self, name, age): self._name=name self._age=age std = Student('Kim', 25) print(std._name) # Kim std._name = 'Lee' print(std._name) # Lee
-
You can use decorators to protected members.
-
However, it is still accessible in Python.
class Student: def __init__(self,name): self._name = name @property def name(self): return self._name @name.setter def name(self,name): if isinstance(name, str): raise TypeError self._name = name std = Student('Kim') print(std.name) # Kim std.name = 'Lee' print(std.name) # Lee print(std._name) # Lee / still accessible std.name = 1 # TypeError print(std.name)
6.3. Private Member
- Private members of a class can only be accessed from the class. Any attempt to do so will result in an AttributeError.
- Can be declared by adding a prefix '__'
class Student:
__schoolName = 'XYZ School'
def __init__(self, name, age):
self.__name = name
self.__age = age
def __show(self):
print('This is private method.')
std = Student("Kim", 25)
print(std.__schoolName) # AttributeError
print(std.__name) # AttributeError
print(std.__show()) # AttributeError
- Private memebers be changed to
object._class__variable
. - But the practice should be refrained.
std = Student("Bill", 25)
print(std._Student__name) # Bill
std._Student__name = 'Steve'
print(std._Student__name) # Steve
std._Student__display() # This is private method.
- For accessing and modifying the private members, using decorators is reocmmended.
class Student:
def __init__(self, name, age):
self.__name = name
self.__age = age
@property
def age(self):
return self.__age
@age.setter
def age(self, new_age):
if new_age < 0:
raise ValueError("invaild range")
self.__age = new_age
std = Student("Bill", 25)
print(std.age) # 25
std.age = 11
print(std.age) # 11
std.age = -1 # ValueError
print(std.age)