Basic Concepts in Class

  • Each value is an object, and therefore has a class (also called its type). It is stored as object.__class__.
  • To get the class or the type of object, Python provides us with the type function and __class__ property defined on the object itself.
  • build-in object is a base for all classes.
  • Each object has a class from which it is instantiated.
  • Metaclass is a class from which classes are instantiated or metaclass is a class of a class.
  • super([type[, object-or-type]]) Return a proxy object that delegates method calls to a parent or sibling class of type.
  • Python support aliasing (one object have more than one name).
  • Even built-in types, like int, float, can be used as base classes for extension by the user.
  • Class definitions, like function definitions (def statements) must be executed before they have any effect. (You could conceivably place a class definition in a branch of an if statement, or inside a function.)
  • there are many magic function for the object. like __init__, __class__, __dict__, etc.
  • The first letter of the name of a class need to be capitalized.
  • The first parameter of all attribute function is self

Basic example:

class Person:
  def __init__(self, name, age):
    self.name = name
    self.age = age

p1 = Person("John", 36)

print(p1)

Main Concepts

Class Objects

Class objects support two kinds of operations: attribute references and instantiation.

Attribute references

The standard syntax used for all attribute references in Python: obj.name.

class MyClass:
    """A simple example class"""
    i = 12345

    def f(self):
        return 'hello world'

then MyClass.i and MyClass.f are valid attribute references, returning an integer and a function object, respectively.

Class instantiation

Class instantiation uses function notation, (not using new keyword). Just pretend that the class object is a parameterless function that returns a new instance of the class. x = MyClass()

When a class defines an __init__() method, class instantiation automatically invokes __init__() for the newly-created class instance.

Instance Objects

The only operations understood by instance objects are attribute references. x = MyClass()

  • There are two kinds of valid attribute names:
    • data attributes and
    • methods.
  • x.f is not the same thing as MyClass.f. It is a method object, not a function object.

What is Method Objects

Method object is not a function object!

  • In example above, the call x.f() is exactly equivalent to MyClass.f(x).
  • In general, calling a method with a list of arguments is equivalent to calling the corresponding function with an argument list that is created by inserting the method’s instance object before the first argument.

Understand Method object

When a non-data attribute of an instance is referenced, the instance’s class is searched. If the name denotes a valid class attribute that is a function object, a method object is created by packing (pointers to) the instance object and the function object just found together in an abstract object: this is the method object. (different from JavaScript)

When the method object is called with an argument list, a new argument list is constructed from the instance object and the argument list, and the function object is called with this new argument list.

Class and Instance Variables

class Dog:
    kind = 'canine'       #this is class variable shared by all instances
    def __init__(self, name):
        self.name = name  #this is instance variable unique to each instance

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind                  # shared by all dogs
'canine'
>>> e.kind                  # shared by all dogs
'canine'
>>> d.name                  # unique to d
'Fido'
>>> e.name                  # unique to e
'Buddy'
  • instance variables are for data unique to each instance.
  • class variables are for attributes and methods shared by all instances of the class.
  • So, Mutable type, like lists, dictionaries, should not be used as a class variable. It should use an instance variable instead
  • If the same attribute name occurs in both an instance and in a class, then attribute lookup prioritizes the instance.

Inheritance

class DerivedClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>

Execution of a derived class definition proceeds the same as for a base class. When the class object is constructed, the base class is remembered. This is used for resolving attribute references: if a requested attribute is not found in the class, the search proceeds to look in the base class. This rule is applied recursively if the base class itself is derived from some other class.

  • Derived classes may override methods of their base classes.
  • Python has two built-in functions that work with inheritance:
    • Use isinstance() to check an instance’s type.
    • Use issubclass() to check class inheritance.
  • Python supports a form of multiple inheritance: class DerivedClassName(Base1, Base2, Base3)

build-in object is a base class for all classes

In Python3, all classes implicitly inherit from the built-in object base class. The object class provides some common methods, such as __init__, __str__, and __new__, that can be overridden by the child class. For example:

class Human:
    pass

Try dir(Human) and will tell Human class have all the methods of built-in object methods:

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

Metaclass

All classes in Python are objects of the type class, and this type class is called Metaclass. Metaclass is a class from which classes are instantiated or metaclass is a class of a class.

type(int)    # Output: <class 'type'>
type(float)  # Output: <class 'type'>

# Even type of object class is - type
type(object) # Output: <class 'type'>

Thus, the type class is the metaclass of int and float classes. The type class is even the metaclass for the built-in object class, which is the base class for all the classes in Python. As type itself is a class, what is the metaclass of the type class? The type class is a metaclass of itself!

Built-in function type()

  • type() function With one argument, return the type of an object. The return value is a type object and generally the same object as returned by object.__class__.
  • type class is metaclass.

Build-in function object()

object is a class.object() return a new featureless object. object is a base for all classes. It has methods that are common to all instances of Python classes. This function does not accept any arguments.

Instantiation process

Object creation in Python is a two-step process.

  • In the first step, Python creates the object, Python uses the __new__ method (i.e., object creation)
  • and in the second step, it initializes the object. Python uses the __init__ method (i.e., initialization).

If the class does not define these methods, they are inherited from the object base class.

First step __new__()

__new__() need to return the object witch have been created.

class Human:
    def __new__(cls, first_name=None):
        # cls is the class using which the object will be created. cls=Human
        # We MUST call the object class' __new__ to allocate memory
        obj = super().__new__(cls) # This is equivalent to object.__new__(cls)

        # Modify the object created
        if first_name:
            obj.name = first_name
        else:
            obj.name = "Ben"

        # return the object
        return obj

cls is the class using witch the object will be created:

class Animal:
    def __new__(cls):
        # Passing Human class reference instead of Animal class reference
        obj = super().__new__(Human) 
        print(f"Type of obj: {type(obj)}") # <class '__main__.Human'>
        return obj

cat = Animal()
type(cat)   # Output: <class '__main__.Human'>

Second step __init__()

Once the __new__ method is complete, Python calls the __init__ method with the human_obj object as the first argument. The __init__ method must not return anything.

class Human:
    def __init__(self, first_name):
        self.first_name = first_name

Understand the precess with Questions

  • Who calls the __new__ and __init__ method?
  • Who passes the self object to the __init__ method?
  • As the __init__ method is called after the __new__ method, and the __init__ method does not return anything, how does calling the class return the object (i.e., how does calling the Human class return the human_obj object)?

Calling a Class

  • Class is callable! Because class has a __call__() method.
  • How __call__ method process With CPython implementation, the metaclass type class’ __call__ method definition is defined in C language. If we convert it into Python and simplify it, it will look somewhat like this:
    # type's __call__ method which gets called when Human class is called i.e. Human()
    def __call__(cls, *args, **kwargs):
      # cls = Human class
      # args = ["Virat", "Kohli"]
      # Calling __new__ method of the Human class, as __new__ method is not defined
      # on Human, __new__ method of the object class is called
      human_obj = cls.__new__(*args, **kwargs)
    
      # After __new__ method returns the object, __init__ method will only be called if
      # 1. human_obj is not None
      # 2. human_obj is an instance of class Human
      # 3. __init__ method is defined on the Human class
      if human_obj is not None and isinstance(human_obj, cls) and hasattr(human_obj, '__init__'):
          # As __init__ is called on human_obj, self will be equal to human_obj in __init__ method
          human_obj.init(*args, **kwargs)
    
      return human_obj
    

Step for creating an object

Following steps are followed while creating and initializing an object in Python:

  1. Call the Human class - Human(); this internally calls the call method of the type class (i.e., type.call(Human, “Virat”, “Kohli”)).
  2. type.call will first call the new method defined on the Human class. If the new method is not defined on the Human class, the new method of the object class will be called.
  3. The new method will the return the object of type Human i.e. human_obj
  4. Now, type.call will call the init method defined on the Human class with human_obj as the first argument. This human_obj will be self in the init method.
  5. The init method will initialize the human_obj with the first_name as Virat and thelast_name as Kohli. The init method will not return anything.
  6. In the end, type.call will return the human_obj object.

As per the type.call definition, whenever we create a new object, the new method will always be called, but calling the init method depends on the output of the new method. The init method will be called only if the new method returns an object of type Human class or a subclass of the Human class.

Static & class method

Static methods, much like class methods, are methods that are bound to a class rather than its object. They do not require a class instance creation. So, they are not dependent on the state of the object.

static method

A static method can be called either on the class (such as C.f()) or on an instance (such as C().f()). Moreover, they can be called as regular functions (such as f()).

When do you use static methods?

Static methods have a limited use case because, like class methods or any other methods within a class, they cannot access the properties of the class itself.

when you need a utility function that doesn’t access any properties of a class but makes sense that it belongs to the class, we use static functions.

Having a single implementation Static methods are used when we don’t want subclasses of a class change/override a specific implementation of a method.

class method

A class method is a method that is bound to a class rather than its object. It doesn’t require creation of a class instance, much like staticmethod.

  • built-in function classmethod(), for transforming a method into a class method.
  • A class method can be called either on the class (such as C.f()) or on an instance (such as C().f()). The instance is ignored except for its class.
  • If a class method is called for a derived class, the derived class object is passed as the implied first argument.
  • But no matter what, the class method is always attached to a class with the first argument as the class itself cls. def classMethod(cls, args...)

When do you use the class method?

  1. Factory methods
  2. Correct instance creation in inheritance

The difference between a static method and a class method is:

  • Static method knows nothing about the class and just deals with the parameters.
  • Class method works with the class since its parameter is always the class itself. They can be called both by the class and its object.

Abstract Base Classes

What is abstract base class (abc)

  • Abstract classes are classes that are meant to be inherited but avoid implementing specific methods, leaving behind only method signatures that subclasses must implement.
  • ABCs introduce virtual subclasses, which are classes that don’t inherit from a class but are still recognized by isinstance() and issubclass(). (see the abc module documentation.)
  • Python comes with many built-in ABCs for data structures (in the collections.abc module), numbers (in the numbers module), streams (in the io module), import finders and loaders (in the importlib.abc module). You can create your own ABCs with the abc module.

Why abc

  • Abstract classes are useful for defining and enforcing class abstractions at a high level, similar to the concept of interface in typed languages(like TypeScript), without the need for method implementation.
  • With the abc module we can prevent child classes from being instantiated when they fail to override abstract class methods of their parents and ancestors.
  • Abstract base classes complement duck-typing by providing a way to define interfaces when other techniques like has_attr() would be clumsy or subtly wrong (for example with magic methods).
  • For example, collections.abc module provides abstract base classes that can be used to test whether a class provides a particular interface; for example, whether it is hashable or whether it is a mapping.

abc concept Example

from abc import ABC, abstractmethod

class Vehicle(ABC):
    def __init__(self):
        super().__init__()

    @abstractmethod
    def go_forward(self):
        pass

class Truck(Vehicle):
    def __init__(self, press_accelerator):
        super().__init__()
        self.press_accelerator = press_accelerator

    def go_forward(self):
        if self.press_accelerator:
            print('Driving forward')
        else:
            print('Press the accelerator to drive forward')

Truck class have to implement go_forward function.

collections.abc

This module provides abstract base classes that can be used to test whether a class provides a particular interface; for example, whether it is hashable or whether it is a mapping.

The collections.abc.Sequence ABC is provided to make it easier to correctly implement these operations on custom sequence types.