In the previous tutorial we learned about the sequences and unordered collections in Python, including the operations, functions and methods applicable to them. However, there may be more complex data structures in an application, known as classes and user-defined objects.
Any application uses data (user-defined values/objects/classes/objects) and code behavior (how data is manipulated).
If an application is designed in a way that it focuses on:
- Functionality (code behavior) – is called procedural or functional programming
- Data (values/objects/user-defined objects) – is called object-oriented programming
Although Python is an object-oriented programming language, it does not force an application to be defined by this type of design pattern. This means that an application can be designed in a variety of patterns, including using model view control (MVC) procedures and control. It is also possible to mix multiple design patterns in one application.
But Python's object-oriented capabilities are worth exploring, as it is useful for creating user-defined data structures and architecting certain applications efficiently.
Python Classes
Classes are similar to a blueprint for real objects or data structures. Thus, in any application, a class can represent a real object or a data structure.
The properties of the object/data structure are defined as attributes of the class. Attributes are references that can contain built-in or user-defined objects. A class can also have functions that bind or manipulate attributes associated with it. Such functions are called methods.
In Python, there is no difference between attributes and methods. Methods are treated as callable attributes.
Python classes are also objects, which can be used as a type to create other objects. Data types in Python are also objects, but they are used to define the type of other objects. When an object of a particular class type is created, it is called an instance or instance object of that class.
The instance object has all attributes (non-callable and callable) defined for its class. Some attributes of an object inherited from its class may have default data values. These non-callable and callable attributes are called descriptors. Any object can have additional attributes defined.
A class can be a subclass of another class, where the parent class is known as the superclass. Just as an object inherits the attributes of its class, if its class is a subclass, it will implicitly inherit the attributes of its superclass. A subclass also implicitly inherits the attributes and methods of its superclass.
The class statement defines classes and is a compound statement with this syntax:
class className (base classes)
statements)
The className can be any identifier that serves as a reference for the class. The class definition can have base classes as comma-delimited arguments. Base classes are superclasses, where this class is a subclass. Base classes are optional and should only be included when the defined class is a subclass of another class.
This is an ideal example of using classes as objects. When base classes are passed as arguments, Python treats them as objects. The statements included in the definition of a class are called the body of the class.
Attributes (non-callable or callable) that bind to a class are called class attributes. These attributes can be defined within the class body and outside the class definition. Class attributes are accessed by the class_name.attribute_name syntax.
Here is a valid example of defining a class attribute within the class body:
class C1:
x = 25
print (C1.x)
Now here is a valid example of defining a class attribute outside of the class definition:
class C1:
to spend
C1.x = 25
print (C1.x)
Functions linked to a class are called methods. Even though class attributes are defined in the class body, when using method definitions, they must be referenced in the class_name.attribute_name syntax.
This is a valid example of a method that accesses the class's attributes:
class C1:
x = 25
defm:
C1.x +=1
print (C1.x)
When an attribute is called and the identifier is used as a reference for that class attribute, or a method begins with two underscores, the class name with a leading underscore is prefixed to the attribute name. Therefore, if a __getData handle is called, the Python compiler will implicitly change the handle to _className__getData.
These identifiers are called private names or private class variables. This makes references to attributes and methods bound to the class private and reduces any chance of collision with other references in the code.
A class can also have special methods with two leading and two trailing underscores as part of an identifier used for its reference. For example, __init__, __get__, __set__, and __delete__ are special methods that can be provided by a class.
These implicit methods have a special purpose in any class and are called dunder methods, magic methods or special methods.
Some class attributes may have managed access. Data values can be linked, relinked or unlinked to them only through dunder methods. Similar to how they can bind to standard data values/objects in the __init__ method, they can:
- Return its value only through the __get__ method
- Have its value modified only through the __set__ method
- It can be deleted only through the __delete__ method.
To initialize, get, set, or delete these attributes, the __init__, __get__, __set__, and __delete__ methods must be explicitly defined in the class body with the appropriate instructions.
These attributes are called descriptors. If a descriptor does not have the __set__ method defined, its value cannot be changed. These descriptors are called non-substitutional or non-data descriptors. If a descriptor has the __set__ method defined, its value can be changed in code by calling the __set__ method for it.
Descriptors with value can be changed and are called replacement descriptors. These descriptors cannot link, rebind, or unlink through regular assignment instructions.
Here is a valid example of descriptors:
class C1 (object):
def __init__(self, value):
self.value = value
def __set__(self, *_):
to spend
def __get__(self, *_):
return self.value
class C2 (object):
c = C1(25)
x = C2 #instances object of class C2
print(xc) # Prints 25
xc = 52
print(xc) # Still prints 25
Class Instances
When creating an instance (instance object) of a class, a reference can be assigned to the class object as a function.
This is a valid example of creating an instance:
X = C1 #C1 is a class
If the __init__ method is provided for a class, there can be default initialization of the attributes of that method. When an instance is created, any necessary arguments must be passed to the instantiation to fulfill initialization.
Here is an example:
class C1 (object):
def __init__(self, value1, value2):
self.a = value1
self.b = value2
y = C1(25, 34)
print(ya) #Prints 25
print(yb) #Impressions 34
In this case, __init__ works like a constructor function in other object-oriented languages. An instance can also inherit the __init__ method of its class's superclass. If there is no __init__ method defined for the class or its superclass at instantiation, the class must be called without arguments.
This means there should be no instance-specific arguments. Additionally, the __init__ method cannot return any value other than None. An instance can have its own attributes that have not been defined in the class body or for its class.
You can also create an instance by assigning a function to a reference. In this case, the function must return a class object. Such functions are called factory functions.
Heritage
Inheritance is a powerful feature in object-oriented programming. As we discussed, any class definition can have base classes that are arguments. This class is then called a subclass and the base class is called a superclass or parent class.
A subclass implicitly inherits all attributes and methods of its base class. Similarly, a subclass instance inherits all the attributes and methods of its class as well as the base class of its class. This feature of attributes and methods inherited from a parent object is called inheritance in the object-oriented paradigm.
A class can have multiple base classes. As such, it inherits the attributes and methods of all base classes. This is called multiple inheritance. It is also possible for the base class to have another base class. In this case, the object inherits the attributes and methods of its base class and its base class's base class. This is called multilevel inheritance.
In the next tutorial we will cover how to design graphical interfaces in Python.