Basics of object-oriented programming in Python #3 instance, class and static methods

As in the case of class attributes and variables, the same division can be observed for methods, i.e. functions included in the class.

Instance methods in the definition as the first argument must have a reference to the class instance – self, e.g.

class Employee:
    bonus = 10
    def set_bonus(self, bonus):
        self.bonus = bonus

The set_bonus method above sets an instance variable that overrides the class variable bonus.

However, when we want to create a class method, we must use the argument cls instead of self, which is a reference to the class and not to an instance. In addition, you must use the @classmethod decorator, i.e.

    def set_base_bonus(cls, bonus):
        cls.bonus = bonus

The above method will set the base value of the variable bonus in the Employee class and all its instances.

Class methods can also be a way to create alternative constructors with a different number of arguments (constructor overloading). For example, considering the example Employee class, which takes the employee’s name and the salary value as arguments, we can create a class method that takes 3 arguments: first name, last name and salary value and returns an instance of the Employee class.

class Employee:
    def __init__(self, name, salary): = name
        self.salary = salary
    def from_full_name(cls, first, last, salary):
        name = '{} {}'.format(first, last)
        return cls(name, salary)

john_doe = Employee.from_full_name('John', 'Doe', 8000)

In addition to class methods, there are also static methods that are preceded by the @staticmethod decorator. Static methods take neither a self instance reference nor cls to a class as an argument. They can take explicit arguments that are parameters to these functions. Static functions are thematically related to the class in which they are contained, but should not operate on class or instance variables.

Basics of object-oriented programming in Python #2 – init() method, instance and class variables

When an instance of a class is created, a special init () method is executed which can be equated with a constructor from other languages. It has the form:

def __init__(self, arg1, arg2, ... ):

The first argument is self by default, which is a reference to the current class instance. The remaining arguments are optional and, like in constructors from other languages, they are used to set attribute values. Instance attribute names must also be preceded by the self variable.

In addition to instance variables, there are also class variables. They hold values ​​that are the same for all instances of the class. It is also possible to display the values ​​of these variables without creating an instance of the class, just by specifying the class name, e.g. for the Employee class, we create a variable that stores the percentage value of the salary bonus that each employee receives, i.e.

class Employee:
    bonus = 10
    def __init__(self, name, salary): = name
        self.salary = salary
john_doe = Employee('John Doe', 8000)    # creating an instance of the class
print(    # displays the value of the instance  attribute name
print(john_doe.bonus)   # displays the value of a class variable from an instance
print(Employee.bonus)   # displays the value of a class variable

A class variable can hold, for example, the number of all employees of the enterprise. It has the same value whether it is called from the class or any instances of it. The increment of the class variable storing the number of employees can be placed e.g. in the init () method, then creating a new employee increases the value of the class variable.

However, you can create an instance variable with the same name as the class variable. In this case, the class variable will be overridden by the value of the attribute, i.e.

class Employee:
    bonus = 10
    def set_bonus(self, bonus):
        self.bonus = bonus

john_doe = Employee()
print(john_doe.bonus)   # displays the value 10
john_doe.set_bonus(20)  # setting a bonus value
print(john_doe.bonus)   # displays the value 20 - the employee bonus
print(Employee.bonus)   # displays the value 10 - base bonus  

>>part three<<

Basics of object-oriented programming in Python #1

Take a class named Person as an example. According to the convention, the name of the class should start with a capital letter, and in the case of a name consisting of many words, we use Pascal case – all words written together and beginning with capital letters. The basic definition of a class is as follows:

class Person:

All classes inherit from the base object class by default, so you could define the class as follows:

class Person(object):

Objects can be created from a class that contains only an empty pass statement. Creating an object does not require a special keyword, e.g. new

 john_doe = Person()   # creating an instance of Person class

print(john_doe)   # showing name of an instance and its address

print(dir(john_doe))   # showing all available class methods

To define a class that inherits from another class, put the base class name as an argument, i.e .:

class Employee(Person):

Python supports multi-inheritance, i.e. you can create a class that inherits from multiple base classes, e.g.

class Executive(Employee, Manager):

>>part two<<

Accessors and mutators in Python

The concept of object-oriented programming is related to the concept of encapsulation, i.e. hiding class fields and making only public methods (public API) available to read or change the values ​​of these fields. In Java, for example, it is solved at the language level using the private access modifier.

Python does not support such encapsulation. Using private variables like: _variable is just a matter of convention.

Consider the following example where we create a class to represent an employee:

class Employee:
	def __init__(self, name, salary, phone): = name
	    self.salary = salary = phone

In Java, you should declare the name, salary, and phone variables private and refer to them via the getters and setters . In Python, it is not recommended to write accessor whose sole purpose is to return an attribute value or mutator to set an attribute value. Instead, you can refer to these fields directly. This has many advantages, but the advantages of encapsulating e.g.

johnDoe = Employee('John Doe', 8000, '555 111 22 33')
johnDoe.salary = 9000

The use of direct access to class attributes is a problem when it is necessary to make changes, e.g. introducing correctness control of the entered data.

Based on the code of the previously created Employee class, let’s assume that we would like to validate the entered telephone number, i.e. whether it has the appropriate number of digits, and at the same time remove unnecessary characters that are not digits.

To do this, we don’t need to change the way we communicate with the phone variable. We can still refer directly to the variable name. This is possible thanks to the @property decorator.

class Employee:

    def __init__(self, name, salary, phone): = name
        self.salary = salary = phone
        def phone(self):
           return self._phone
        def phone(self, value):
            phone = ''.join(filter(lambda x: x.isdigit(), value))
            if len(phone) != 10:
                raise ValueError('Wrong phone number!')
            self._phone = phone

Calling the class’s constructor sets individual class attributes, while assigning the phone argument to the attribute causes the mutator to be called and thus validating the entered phone number at the time of creating the Employee class object.

Similarly, assigning a new value to the phone attribute will call the appropriate validation method, i.e.

johnDoe = Employee('John Doe', 8000, "555 111 22 33") = '000' # raises ValueError - incorrect number length