Object-Oriented Programming in Python

1. A class is an idea (more or less abstract) which can be used to create a number of incarnations – such an incarnation is called an object.

 

2. When a class is derived from another class, their relation is named inheritance. The class which derives from the other class is named a subclass. The second side of this relation is named superclass. A way to present such a relation is an inheritance diagram, where:

    • superclasses are always presented above their subclasses;
    • relations between classes are shown as arrows directed from the subclass toward its superclass.

 

3. Objects are equipped with:

    • a name which identifies them and allows us to distinguish between them;
    • a set of properties (the set can be empty)
    • a set of methods (can be empty, too)

 

4. To define a Python class, you need to use the class keyword. For example:

 

5. To create an object of the previously defined class, you need to use the class as if it were a function. For example:

 

6. A stack is an object designed to store data using the LIFO model. It’s an abbreviation for a very clear description of the stack’s behavior: Last In – First Out. The coin that came last onto the stack will leave first. The stack usually performs at least two operations, named

    • push (when a new element is put on the top)
    • pop (when an existing element is taken away from the top).

The stack – the procedural approach

1

2

3

 

7. Implementing the stack in a procedural model raises several problems which can be solved by the techniques offered by OOP (Object Oriented Programming):

 

8. The part of the Python class responsible for creating new objects is called the constructor.

    • the constructor’s name is always __init__;
    • it has to have at least one parameter; the parameter is used to represent the newly created object – you can use the parameter to manipulate the object, and to enrich it with the needed properties; you’ll make use of this soon;
    • the obligatory parameter is usually named self – it’s only a convention, but you should follow it – it simplifies the process of reading and understanding your code.

 

9. To create a property inside a class it is used a dot notation, just like when invoking methods; this is the general convention for accessing an object’s properties  you need to name the object, put a dot (.) after it, and specify the desired property’s name;

 

10. If we want to hide any of a class’s components from the outside world, we should start its name with __. Such components are called private.

The ability to hide (protect) selected values against unauthorized access is called encapsulation; the encapsulated values can be neither accessed nor modified if you want to use them exclusively;

Traceback (most recent call last):

File "main.py", line 7, in <module>

print(len(stack_object.__stack_list))

AttributeError: 'Stack' object has no attribute '__stack_list'

 

11. A class method is actually a function declared inside the class and able to access all the class’s components. Each class method declaration must contain at least one parameter (always the first one) usually referred to as self, and is used by the objects to identify themselves.

 

 

12. An instance variable is a property whose existence depends on the creation of an object. Every object can have a different set of instance variables.

Moreover, they can be freely added to and removed from objects during their lifetime. All object instance variables are stored inside a dedicated dictionary named __dict__, contained in every object separately.

    • the class named ExampleClass has a constructor, which unconditionally creates an instance variable named first, and sets it with the value passed through the first argument (from the class user’s perspective) or the second argument (from the constructor’s perspective); note the default value of the parameter – any trick you can do with a regular function parameter can be applied to methods, too;
    • the class also has a method which creates another instance variable, named second;
    • we’ve created three objects of the class ExampleClass, but all these instances differ:

example_object_1 only has the property named first;

example_object_2 has two properties: first and second;

example_object_3 has been enriched with a property named third just on the fly, outside the class’s code – this is possible and fully permissible.

The program’s output clearly shows that our assumptions are correct – here it is:
{'first': 1}
{'second': 3, 'first': 2}
{'third': 5, 'first': 4}

There is one additional conclusion that should be stated here: modifying an instance variable of any object has no impact on all the remaining objects. Instance variables are perfectly isolated from each other.

 

13. An instance variable can be private when its name starts with __, but don’t forget that such a property is still accessible from outside the class using a mangled name constructed as _ClassName__PrivatePropertyName.

It’s nearly the same as the previous one. The only difference is in the property names. We’ve added two underscores (__) in front of them.

Output:

{'_ExampleClass__first': 1}

{'_ExampleClass__first': 2, '_ExampleClass__second': 3}

{'_ExampleClass__first': 4, '__third': 5}

The name is now fully accessible from outside the class. You can run a code like this:

 

14. A class variable is a property which exists in exactly one copy, and doesn’t need any created object to be accessible. Such variables are not shown as __dict__ content.

All a class’s class variables are stored inside a dedicated dictionary named __dict__, contained in every class separately.

Running the code will cause the following output:
{'_ExampleClass__first': 1} 3
{'_ExampleClass__first': 2} 3
{'_ExampleClass__first': 4} 3

    • class variables aren’t shown in an object’s __dict__ (this is natural as class variables aren’t parts of an object) but you can always try to look into the variable of the same name, but at the class level
    • a class variable always presents the same value in all class instances (objects)

{'_ExampleClass__first': 1} 3

{'_ExampleClass__first': 2} 3

{'_ExampleClass__first': 4} 3

 

15. A function named hasattr() can be used to determine if any object/class contains a specified property.

As you can see, accessing a non-existing object (class) attribute causes an AttributeError exception.

1
Traceback (most recent call last):
File ".main.py", line 11, in
print(example_object.b)
AttributeError: 'ExampleClass' object has no attribute 'b'

The try-except instruction gives you the chance to avoid issues with non-existent properties.

or utlize the hasattr():

 

Example:

The code outputs:
{'alpha': 1, '_Sample__delta': 3, 'beta': 2}

 

Example

False

False

False

True

True

 

16. A method is a function embedded inside a class. The first (or only) parameter of each method is usually named self, which is designed to identify the object for which the method is invoked in order to access the object’s properties or invoke its methods. There is one fundamental requirement – a method is obliged to have at least one parameter (there are no such thing as parameterless methods – a method may be invoked without an argument, but not declared without parameters).

If you want the method to accept parameters other than self, you should: place them after self in the method’s definition; deliver them during invocation without specifying self (as previously) Just like here:

The code outputs:
method: 1
method: 2
method: 3

The self parameter is used to obtain access to the object’s instance and class variables.

The code outputs:
2 3

The self parameter is also used to invoke other object/class methods from inside the class.

The code outputs:
method
other

 

17. If a class contains a constructor (a method named __init__) it cannot return any value and cannot be invoked directly.

Everything we’ve said about property name mangling applies to method names, too – a method whose name starts with __ is (partially) hidden. The example shows this effect:

visible
failed
hidden

 

18. Each Python class and each Python object is pre-equipped with a set of useful attributes which can be used to examine its capabilities.

  • One of these – it’s the __dict__ property.

Output:

 

  • Another built-in property worth mentioning is __name__, which is a string. The property contains the name of the class.  The __name__ attribute is absent from the object – it exists only inside classes.

If you want to find the class of a particular object, you can use a function named type(), which is able (among other things) to find a class which has been used to instantiate any object.

The code outputs:
Classy
Classy

Note that a statement like this one:

will cause an error.

 

  • Additionally, a property named __module__ stores the name of the module in which the class has been declared,

The code outputs:
__main__
__main__

Any module named __main__ is actually not a module, but the file currently being run.

 

  • the property named __bases__ is a tuple containing a class’s superclasses.

Note: only classes have this attribute – objects don’t.

It will output:
( object )
( object )
( SuperOne SuperTwo )

Note: a class without explicit superclasses points to object (a predefined Python class) as its direct ancestor.

 

Reflection and introspection

Introspection  is the ability of a program to examine the type or properties of an object at runtime;

Reflection, which goes a step further, and is the ability of a program to manipulate the values, properties and/or functions of an object at runtime.

 

Example:

The code outputs:

My name is Sample living in a __main__

 

1. A method named __str__() is responsible for converting an object’s contents into a (more or less) readable string. You can redefine it if you want your object to be able to present itself in a more elegant form. For example:

mickey

If the __str()__ method is not defined for object:

invocating the print() function on object Luke will invoke the default __str()__ method which returns the following string :

<__main__.Jedi object at 0x7f910c9d9390>

To get a result different from the default string above, the __str__() method would need to be defined in the Jedi class.

 

2. A function named issubclass(Class_1, Class_2) is able to determine if Class_1 is a subclass of Class_2. For example:

 

3. A function named isinstance(Object, Class) returns True if the object is an instance of the class, or False otherwise.. For example:

 

4. A operator called is checks if two variables refer to the same object. For example:

Don’t forget that variables don’t store the objects themselves, but only the handles pointing to the internal Python memory.

0

0

1

False

False

True

1 2 1

True False

The results prove that object_1 and object_3 are actually the same objects, while string_1 and string_2 aren’t, despite their contents being the same.

 

5. A parameterless function named super() returns a reference to the nearest superclass of the class. For example:

Note: you can use this mechanism not only to invoke the superclass constructor, but also to get access to any of the resources available inside the superclass.

 

6. Methods as well as instance and class variables defined in a superclass are automatically inherited by their subclasses. For example:

 

7. In order to find any object/class property, Python looks for it inside:

  • the object itself;
  • all classes involved in the object’s inheritance line from bottom to top;

200 201

  • if there is more than one class on a particular inheritance path, Python scans them from left to right;

L LL RR Left

 

  • if both of the above fail, the AttributeError exception is raised.

100 101 102

200 201 202

300 301 302

 

8. If any of the subclasses defines a method/class variable/instance variable of the same name as existing in the superclass, the new name overrides any of the previous instances of the name. For example:

 

9. The situation in which the subclass is able to modify its superclass behavior (just like in the example) is called polymorphism. Polymorphism helps the developer to keep the code clean and consistent.

do_it from One

do_it from Two

 

Example:

b= 2
self.v= 2
a= 3

 


Exercise 1

Can you name one of your classes just “class”?

No, you can’t – class is a keyword!

 

Exercise 2

Assuming that there is a class named Snakes, write the very first line of the Python class declaration, expressing the fact that the new class is actually a subclass of Snake.

 

Exercise 3

Something is missing from the following declaration – what?

The __init__() constructor lacks the obligatory parameter (we should name it self to stay compliant with the standards).

 

Exercise 4

Modify the code to guarantee that the venomous property is private.

 

The code should look as follows:

 

Exercise 5

Which of the Python class properties are instance variables and which are class variables? Which of them are private?

population and victims are class variables, while length and __venomous are instance variables (the latter is also private).

 

Exercise 6

You’re going to negate the __venomous property of the version_2 object, ignoring the fact that the property is private. How will you do this?

 

 

Exercise 7

Write an expression which checks if the version_2 object contains an instance property named constrictor (yes, constrictor!).

 

Exercise 8

The declaration of the Snake class is given below. Enrich the class with a method named increment(), adding 1 to the victims property.

 

 

Exercise 9

Redefine the Snake class constructor so that is has a parameter to initialize the victims field with a value passed to the object during construction.

 

Exercise 10

Can you predict the output of the following code?

 

Python is a Snake
Snake can be a Python

 

Exercises

Scenario

Assume that the following piece of code has been successfully executed:

 

Exercise 1

What is the expected output of the following piece of code?

 

Collie says: Woof! Don't run away, Little Lamb!
Dobermann says: Woof! Stay where you are, Mister Intruder!

Exercise 2

What is the expected output of the following piece of code?

 

True False
False True

Exercise 3

What is the expected output of the following piece of code?

 

True False
2

Exercise 4

Define a SheepDog‘s subclass named LowlandDog, and equip it with an __str__() method overriding an inherited method of the same name. The new dog’s __str__() method should return the string “Woof! I don’t like mountains!” .