Define a function in a class
Functions in a class can be defined in the following way. Once it’s defined, it needs to be instantiated.
class FirstClass():
def return_hello(self):
return "Hello"
instance = FirstClass()
print(instance.return_hello())
# Hello
In many other languages, new
keyword is needed to instantiate a class but it’s not necessary in Python.
But what’s self
in a function? I defined the function without it but it didn’t work.
class FirstClass():
def return_hello():
return "Hello"
instance = FirstClass()
print(instance.return_hello())
# Traceback (most recent call last):
# File ".\src\class.py", line 9, in <module>
# print(instance.return_hello())
# TypeError: return_hello() takes 0 positional arguments but 1 was given
The first argument must always be self and we can’t omit it from the definition.
Hmm… it’s not good. Why do we always need to write it…
But anyway, it’s needed.
How to define constructor
If some initializations are needed for the class, it can be defined in __init__
function. This is the constructor in Python.
The first argument is always self, and then, we can define additional variables if necessary. Let’s assume that we want to receive name value from outside.
class FirstClass():
def __init__(self, name):
self.name = name
def return_hello(self):
return f"Hello {self.name}"
instance = FirstClass("Yuto")
print(instance.return_hello())
# Hello Yuto
To use the value in the whole class, the value needs to be assigned to self.variable_name
.
How to define destructor
In opposite to the constructor, the class might need to do resource release or something else when the instance is not used anymore. In this case, it can be defined in __del__
function.
class FirstClass():
_protected_one = 1
__private_one = 1
def __init__(self, name):
self.name = name
def __del__(self):
print("Destructor for FirstClass was called")
self.name = None
print(f"name was set to {self.name}")
instance = FirstClass("Yuto")
print(instance.name) # Yuto
exit()
# Destructor for FirstClass was called
# name was set to None
The instance is no longer referred to when calling exit
function. Then, the destructor is called.
Private or Public accessibility
There is no keyword like private, public, protected
or similar stuff. We need to indicate them by the variable name.
access | name examples |
---|---|
public | count, x, y |
protected | _count, _x, _y |
private | __count, __x, __y |
Let’s try to access them.
class FirstClass():
_protected_one = 1
__private_one = 1
def __init__(self, name):
self.name = name
def return_hello(self):
return f"Hello {self.name}"
def use_members(self):
print("protected: " + str(self._protected_one))
print("private: " + str(self.__private_one))
instance = FirstClass("Yuto")
print(instance.return_hello()) # Hello Yuto
print(instance._protected_one) # 1
print(instance.use_members())
# protected: 1
# private: 1
print(instance.__private_one)
# None
# Traceback (most recent call last):
# File "D:\DMGMORI_Development\private\blogpost-python\src\class.py", line 28, in <module>
# print(instance.__private_one)
# AttributeError: 'FirstClass' object has no attribute '__private_one'
Both protected and private variables can be accessed in the class but it throws an error when accessing the private variable via the instance.
Note that the protected variable can be accessed from the instance but it is not recommended.
The private/protected accessibility is applied to functions too.
Inheritance
Create a class based on another class
There are many cases where a class needs to be created based on another class. It is called Inheritance.
When a class needs to inherit another class, the base class must be specified in the argument of the class.
class FirstClass():
def __init__(self, name):
self.name = name
def __del__(self):
print("Destructor for FirstClass was called")
self.name = None
print(f"name was set to {self.name}")
def return_hello(self):
return f"Hello {self.name}"
def do_something(self):
raise NotImplementedError("Method not implemented")
class SecondClass(FirstClass): # A inherited class must be specified here
def __init__(self, name, age):
self.age = age
super().__init__(name)
def __del__(self):
print("Destructor for SecondClass was called")
self.age = None
print(f"age was set to {self.age}")
super().__del__()
def return_hey(self):
return f"Hey {self.name} {self.age}"
If SecondClass
is instantiated, return_hey
function can be called as well as return_hello
function that is defined in FirstClass
.
instance2 = SecondClass("Yuto2", 35)
print(instance2.return_hello()) # Hello Yuto2
print(instance2.return_hey()) # Hey Yuto2 35
Let’s call do_something
function here.
try:
print("Call do_something()")
instance2.do_something()
except BaseException as e:
print(f"ERROR: {e}")
# Call do_something()
# ERROR: Method not implemented
do_something
function is defined but it just raises an error in FirstClass
That’s why an error occurs here.
Use inheritance as an interface definition
If a function in the base class raises an error, it must be implemented in the inherited class. It means that the class can be used as an interface.
class Person():
def __init__(self, name, age):
self._name = name
self._age = age
def work(self):
raise NotImplementedError("Method not implemented")
def greet(self):
raise NotImplementedError("Method not implemented")
Then, let’s define the following two classes based on Person
class.
class Developer(Person):
def work(self):
print("I'm developing something...")
def greet(self):
print(f"Hi, I'm a developer. Name: {self._name}, Age: {self._age}")
class Manager(Person):
def work(self):
print("I'm creating a plan...")
def greet(self):
print(f"Hi, I'm a manager. Name: {self._name}, Age: {self._age}")
If we provide the type info, IntelliSense can understand which function is available on the instance. As you can see in the following, the function result depends on the instance.
from typing import List
persons: List[Person] = [Developer("Yuto", 35), Manager("TOTO", 55)]
for instance in persons:
instance.greet()
instance.work()
# Hi, I'm a developer. Name: Yuto, Age: 35
# I'm developing something...
# Hi, I'm a manager. Name: TOTO, Age: 55
# I'm creating a plan...
Use ABC module for abstract class
The interface-like definition can be done in a previous way but using ABC module is better.
from abc import ABC, abstractmethod
class Person(ABC):
def __init__(self, name, age):
self._name = name
self._age = age
@abstractmethod
def work(self):
pass
@abstractmethod
def greet(self):
pass
The base class can be instantiated in a previous way but not in this way. If we try to instantiate it, we get the following error.
Traceback (most recent call last):
File "~\src\class.py", line 139, in <module>
abstract_class2()
File "~\src\class.py", line 130, in abstract_class2
Person("Yuto", 35)
TypeError: Can't instantiate abstract class Person with abstract methods greet, work
Multi inheritance
Let’s check how it works if a class has multi-base classes.
class A():
def hello(self):
return "hello from A"
def boo(self):
return "boo"
class B():
def hello(self):
return "hello from B"
def beeee(self):
return "beeee"
class C(A, B):
pass
instance = C()
print(instance.hello()) # hello from A
print(instance.boo()) # boo
print(instance.beeee()) # beeee
The first base class definition is used if the same function is defined in the different base classes. The non-conflicted function can of course be used.
But this is not a good design. Don’t do this.
Comments