OOP#
Object-Oriented Programming (OOP) is a programming paradigm that provides a way to structure code by organizing it into objects. Objects are instances of classes, which are blueprints that define the behavior and characteristics of the objects. Python is an object-oriented programming language that fully supports OOP principles.
For more information check:
Data model section of the official documentation.
Classes tutorial in the official documentation.
Objects#
Everything in python is an object. Each object has it’s unique identifier which you can load using the id
build-in function. The is
operator allows you to check if two names refer to the same object.
The following cell defines the int
object and shows it’s id
.
value = 10
id(value)
99205780812584
Now for thevalue
assigned to value2
. id(value2)
is the same as id(value)
- they’re actually the same objects.
value2 = value
id(value2)
99205780812584
This can also be checked with the is
operator.
value is value2
True
But if you assign a different literal to value2
- a new object will be created under that name.
value2 = 30
id(value2)
99205780813224
value is value2
False
Variables#
Classes and their instances can contain variables (sometimes called data attributes) - it’s a peace of data that corresponds to the class or its instances.
There are few important concepts you need to know about “data attributes”:
There are attributes defined for whole class and attributes unique for each instance.
There are special dynamic attributes that during operation wite class behaves like a regular data attribute, but in real it’s a method - so you can compute value of the attribute dynamically.
Find out more accurate description in the corresponding page.
The following cell defines class where:
class_var
: is a class variable.instance_var
: is a variable that will correspond to each instance of the class.dynamic_attribute
: is an attribute whose value is counted at the moment of reference to it.
class MyClass:
class_var = 10
def __init__(self):
self.instance_var = 45
@property
def dynamic_attribute(self):
return self.instance_var + 7
Private attributes#
There is just a convention in the python community - to consider attributes starting with underscore (e.g. _spam
) as a private part of the API, but there are no mechanisms that prevent you from using/modifying it.
There is only one mechanism to prevent duplicate names during inheritance. If you define an attribute with a name that starts with two underscores like __spam
, python will automatically create another reference to that attribute with name that follows pattern: _<name of the class>__<name of attribute>
.
The following cell creates an attribute that has a method which name starts with double underscore: __private_method
. But from instance of the MyClass
it calls __MyClass__private_method
.
class MyClass:
def __private_method(self):
print("Private method from MyClass")
my_class = MyClass()
my_class._MyClass__private_method()
Private method from MyClass
As a result, the program behaves exactly as it was declared in the _private_method
.
The following cell creates a subclass for MyClass
and shows that even if you reassign __private_method
, extra reference automatically created by the python _MyClass__private_method
still exits.
class MySubClass(MyClass):
def __private_method(self):
print("Private method from MySubClass")
my_sub_class = MySubClass()
my_sub_class._MyClass__private_method()
Private method from MyClass
And behaves as it declared in the MyClass.__private_method
.
Inheritance#
One of the key features of OOP is inheritance, which allows a class to inherit attributes and methods from another class. The class that inherits is called a subclass or derived class, and the class from which it inherits is called a superclass or base class.
Check details on features of the inheritance in the corresponding page.
The following cell defines the Car
class, which implements the general car, and creates the ElectricCar
subclass, which inherits all the properties from the Car
class, but adds properties specific to the electric car.
class Car:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def start(self):
print("The car has started.")
def stop(self):
print("The car has stopped.")
class ElectricCar(Car):
def __init__(self, make, model, year, battery_capacity):
super().__init__(make, model, year)
self.battery_capacity = battery_capacity
def charge(self):
print("The electric car is charging.")
In this example, we define a subclass ElectricCar
that inherits from the Car
class. It has an additional attribute battery_capacity
and a new method charge. The super()
function is used to call the superclass’s constructor and initialize the inherited attributes.
my_electric_car = ElectricCar("Tesla", "Model S", 2023, 100)
print(my_electric_car.make)
print(my_electric_car.battery_capacity)
my_electric_car.start()
my_electric_car.charge()
Tesla
100
The car has started.
The electric car is charging.
Here, my_electric_car
is an object of the ElectricCar
class, which inherits attributes and methods from the Car
class. It also has its own specific attributes and methods.
Special methods#
In classes, a set of methods with reserved names can be implemented. These methods are called when specific events occur with the class or its instances. Names of all this methods follows such pattern __<name>__
, for example __init__
, __repr__
and so on.
Find out more in the:
Some description in experimental approach in the corresponding page of this site.
The following table shows some special methods and their descriptions.
Group |
Method |
Description |
---|---|---|
Object management |
|
Static method called to create a new instance |
|
Called immediately after instance creation |
|
|
Called when the instance is about to be destroyed - the correct abstract way to name it is “finalizer” |
|
Type Conversion |
|
Defines how an object should be converted into a string datatype. |
|
Defines how an object should be converted into a boolean datatype. |
|
|
Defines how an object should be converted into an integer datatype. |
|
|
Defines how an object should be converted into a float datatype. |
|
|
Defines how an object should be converted into a complex datatype. |
|
Arithmetic operators |
|
Compares two objects for equality ( |
|
Compares if the object is less than another ( |
|
|
Compares if the object is less than or equal to another ( |
|
|
Compares if the object is greater than another ( |
|
|
Compares if the object is greater than or equal to another ( |
|
|
Defines addition for objects ( |
|
|
Defines subtraction for objects ( |
|
|
Defines multiplication for objects ( |
|
|
Defines division for objects ( |
|
|
Defines floor division for objects ( |
|
|
Defines modulo operation for objects ( |
|
|
Defines power operation for objects ( |
|
|
Defines bitwise AND operation ( |
|
|
Defines bitwise OR operation (` |
|
|
Defines bitwise XOR operation ( |
|
|
Defines in-place addition ( |
|
|
Defines in-place subtraction ( |
|
|
Defines in-place multiplication ( |
|
|
Defines in-place division ( |
|
|
Defines unary negation ( |
|
|
Returns the absolute value of the object ( |
|
Indexing operator ( |
|
To try assigning a value to the index. |
|
To try to access value under the index. |
|
|
To try to delete value under the index |
|
Iteration protocol |
|
Will be called each time you need iterate over object. |
|
Will be called each time the object is passed to the |
As an example, consider a class that has the __getitem__
method defined. This method determines the behavior of the instances of the class when the []
operator is applied to them.
Here is how it works — it converts the literal 3
to the type of the input and applies +
to the input and the transformed literal 3
.
class TestClass:
def __getitem__(self, item):
return item + type(item)(3)
The following cell shows the behavior of the instance when 9
is passed to the []
operator.
TestClass()[6]
9
You can pass string literals as well.
TestClass()["hello"]
'hello3'
Special attributes#
Actually, almost every instance in the Python environment has its own special attributes, but they come from different abstractions of the language:
Type attributes: Attributes of the type itself. The
type
class, likeint
, holds these attributes.Function attributes, Method attributes: Their purpose is clear from their names.
Instance attributes: Attributes that appear in an instance during its creation.
Category |
Attribute |
Description |
---|---|---|
Type Attributes |
|
Name of the class. |
|
Module where the class is defined. |
|
|
Dictionary containing the class’s attributes and methods. |
|
|
Tuple of base classes (superclasses). |
|
|
Method Resolution Order (tuple of base classes in order). |
|
|
Returns a list of known subclasses. |
|
|
Docstring of the class (or |
|
|
Dictionary of variable type annotations. |
|
|
Fully qualified class name (e.g., |
|
Function Attributes |
|
Code object representing the function’s compiled bytecode. |
|
Tuple of default values for function parameters. |
|
|
Dictionary of default values for keyword-only arguments. |
|
|
Reference to the global namespace where the function is defined. |
|
|
Tuple of cell objects containing variables used in closures. |
|
|
Dictionary of function parameter and return type annotations. |
|
Method Attributes |
|
Reference to the underlying function of a method. |
|
Instance to which a bound method is attached. |
|
|
Docstring of the method. |
|
|
Dictionary of method parameter and return type annotations. |
|
Instance Attributes |
|
Dictionary containing instance attributes. |
|
Reference to the class of the instance. |
|
|
Tuple defining allowed instance attributes (if used). |
|
|
List of weak references to the object (if applicable). |
Check more specific description at the corresponding page.
Class method#
A class method is a method that takes the class itself as its first argument (typically named cls
). It should be defined using the classmethod
decorator. A crucial feature of a class method is that it can be called not only from an instance of the class (like a typical method) but also directly from the class itself.
The following cell defines class_method
, with its name reflecting its properties. In this case, class_name
returns cls
, allowing us to verify what it represents.
class ClassMethodExample:
@classmethod
def class_method(cls):
return cls
The following two cells use class_method
from the class itself and from the instance of the class.
ClassMethodExample.class_method()
__main__.ClassMethodExample
ClassMethodExample().class_method()
__main__.ClassMethodExample
The following cell proves that cls
is exactly the object of the class.
ClassMethodExample.class_method() == ClassMethodExample
True
Static method#
Static method is a method of the class that isn’t bound to any object - I like to think of it as an about-usual function, but just in a class namespace. Such an approach allows creating functions that are logically associated with a class but can be called without an instance. Check out more about static methods in the corresponding tutorian on the digital ocean.
You can formally define a method as static by wrapping it in the staticmethod
decorator. Note: A static method doesn’t have any relation to the instances of the object, which is why it shouldn’t have the self
parameter.
The following cell implements a class that contains a regular method and a static method.
class StaticExample:
def typical(self):
print("I'm typical.")
@staticmethod
def static():
print("I'm static.")
Static methods are easily accessed by <class name>.<method name>
. The following cell shows it:
StaticExample.static()
I'm static.
The same approach with a non-static typical
method will result in a corresponding error.
try:
StaticExample.typical()
except Exception as e:
print(e)
StaticExample.typical() missing 1 required positional argument: 'self'
Abstractions#
Abstract class is a class which instance can’t be created. Typically it is used to define rules for defining children of the class - from these child classes instances of the class can be created. To define an abstract class, you must create it as a child of the abc.ABC
and define methods there that must be overloaded in children classes.
Check more details in:
The following cell defines an abstract class with an abstract_method
method that must be overloaded.
from abc import ABC, abstractmethod
class AbstractClass(ABC):
@abstractmethod
def abstract_method(self):
pass
The following code shows that you can’t create an instance of the AbstractClass
.
try: AbstractClass()
except Exception as e: print(e)
Can't instantiate abstract class AbstractClass without an implementation for abstract method 'abstract_method'
The purpose of the AbstractClass
is to be an ancestor for other classes and to define methods
that need to be overloaded.
class Ancestor(AbstractClass):
def abstract_method(self):
print("Implemented abstract method.")
ancestor = Ancestor()