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:
|
1 2 |
class This_Is_A_Class: pass |
5. To create an object of the previously defined class, you need to use the class as if it were a function. For example:
|
1 |
this_is_an_object = This_Is_A_Class() |
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 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
stack = [] def push(val): stack.append(val) def pop(): val = stack[-1] del stack[-1] return val push(3) push(2) push(1) print(pop()) print(pop()) print(pop()) |
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.
- the constructor’s name is always
|
1 2 3 4 5 6 |
class Stack: # Defining the Stack class. def __init__(self): # Defining the constructor function. print("Hi!") stack_object = Stack() # Instantiating the object. |
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;
|
1 2 3 4 5 6 |
class Stack: def __init__(self): self.stack_list = [] stack_object = Stack() print(len(stack_object.stack_list)) |
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;
|
1 2 3 4 5 6 |
class Stack: def __init__(self): self.__stack_list = [] stack_object = Stack() print(len(stack_object.__stack_list)) |
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.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class Stack: def __init__(self): self.__stack_list = [] def push(self, val): self.__stack_list.append(val) def pop(self): val = self.__stack_list[-1] del self.__stack_list[-1] return val stack_object = Stack() stack_object.push(3) stack_object.push(2) stack_object.push(1) print(stack_object.pop()) print(stack_object.pop()) print(stack_object.pop()) |
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.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class ExampleClass: def __init__(self, val = 1): self.first = val def set_second(self, val): self.second = val example_object_1 = ExampleClass() example_object_2 = ExampleClass(2) example_object_2.set_second(3) example_object_3 = ExampleClass(4) example_object_3.third = 5 print(example_object_1.__dict__) print(example_object_2.__dict__) print(example_object_3.__dict__) |
-
- the class named
ExampleClasshas a constructor, which unconditionally creates an instance variable namedfirst, 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:
- the class named
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.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class ExampleClass: def __init__(self, val = 1): self.__first = val def set_second(self, val = 2): self.__second = val example_object_1 = ExampleClass() example_object_2 = ExampleClass(2) example_object_2.set_second(3) example_object_3 = ExampleClass(4) example_object_3.__third = 5 print(example_object_1.__dict__) print(example_object_2.__dict__) print(example_object_3.__dict__) |
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:
|
1 |
print(example_object_1._ExampleClass__first) |
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.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class ExampleClass: counter = 0 def __init__(self, val = 1): self.__first = val ExampleClass.counter += 1 example_object_1 = ExampleClass() example_object_2 = ExampleClass(2) example_object_3 = ExampleClass(4) print(example_object_1.__dict__, example_object_1.counter) print(example_object_2.__dict__, example_object_2.counter) print(example_object_3.__dict__, example_object_3.counter) |
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)
- class variables aren’t shown in an object’s
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class ExampleClass: __counter = 0 def __init__(self, val = 1): self.__first = val ExampleClass.__counter += 1 example_object_1 = ExampleClass() example_object_2 = ExampleClass(2) example_object_3 = ExampleClass(4) print(example_object_1.__dict__, example_object_1._ExampleClass__counter) print(example_object_2.__dict__, example_object_2._ExampleClass__counter) print(example_object_3.__dict__, example_object_3._ExampleClass__counter) |
{'_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.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
class ExampleClass: def __init__(self, val): if val % 2 != 0: self.a = 1 else: self.b = 1 example_object = ExampleClass(1) print(example_object.a) print(example_object.b) |
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.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class ExampleClass: def __init__(self, val): if val % 2 != 0: self.a = 1 else: self.b = 1 example_object = ExampleClass(1) print(example_object.a) try: print(example_object.b) except AttributeError: pass |
or utlize the hasattr():
|
1 2 3 4 5 6 7 8 9 10 11 12 |
class ExampleClass: def __init__(self, val): if val % 2 != 0: self.a = 1 else: self.b = 1 example_object = ExampleClass(1) print(example_object.a) if hasattr(example_object, 'b'): print(example_object.b) |
Example:
|
1 2 3 4 5 6 7 8 9 10 |
class Sample: gamma = 0 # Class variable. def __init__(self): self.alpha = 1 # Instance variable. self.__delta = 3 # Private instance variable. obj = Sample() obj.beta = 2 # Another instance variable (existing only inside the "obj" instance.) print(obj.__dict__) |
The code outputs:
{'alpha': 1, '_Sample__delta': 3, 'beta': 2}
Example
|
1 2 3 4 5 6 |
class A: def __init__(self, v): pass a = A(1) print(hasattr(a, 'A')) |
False
|
1 2 3 4 5 6 7 |
class A: A=1 def __init__(self): self.a = 0 print(hasattr(A, 'a')) |
False
|
1 2 3 4 5 6 7 |
class A: A=1 def __init__(self): self.a = 0 b=A() print(hasattr(A, 'a')) |
False
|
1 2 3 4 5 6 7 |
class A: A=1 def __init__(self): self.a = 0 b=A() print(hasattr(b, 'A')) |
True
|
1 2 3 4 5 6 7 |
class A: A=1 def __init__(self): self.a = 0 b=A() print(hasattr(b, 'a')) |
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:
|
1 2 3 4 5 6 7 8 |
class Classy: def method(self, par): print("method:", par) obj = Classy() obj.method(1) obj.method(2) obj.method(3) |
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.
|
1 2 3 4 5 6 7 8 9 |
class Classy: varia = 2 def method(self): print(self.varia, self.var) obj = Classy() obj.var = 3 obj.method() |
The code outputs:
2 3
The self parameter is also used to invoke other object/class methods from inside the class.
|
1 2 3 4 5 6 7 8 9 10 |
class Classy: def other(self): print("other") def method(self): print("method") self.other() obj = Classy() obj.method() |
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:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Classy: def visible(self): print("visible") def __hidden(self): print("hidden") obj = Classy() obj.visible() try: obj.__hidden() except: print("failed") obj._Classy__hidden() |
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.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Classy: varia = 1 def __init__(self): self.var = 2 def method(self): pass def __hidden(self): pass obj = Classy() print(obj.__dict__) print(Classy.__dict__) |
Output:
|
1 2 3 |
{'var': 2} {'__module__': '__main__', 'varia': 1, '__init__': <function Classy.__init__ at 0x7fb94adea320>, 'method': <function Classy.method at 0x7fb94adeaef0>, '_Classy__hidden': <function Classy.__hidden at 0x7fb94adeaf80>, '__dict__': <attribute '__dict__' of 'Classy' objects>, '__weakref__': <attribute '__weakref__' of 'Classy' objects>, '__doc__': None} |
- 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.
|
1 2 3 4 5 6 |
class Classy: pass print(Classy.__name__) obj = Classy() print(type(obj).__name__) |
The code outputs:
Classy
Classy
Note that a statement like this one:
|
1 |
print(obj.__name__) |
will cause an error.
Example:
|
1 2 3 4 5 6 7 8 9 |
class Sample: def __init__(self): self.name = Sample.__name__ def myself(self): print("My name is " + self.name + " living in a " + Sample.__module__) obj = Sample() obj.myself() |
The code outputs:
My name is Sample living in a __main__
- Additionally, a property named
__module__stores the name of the module in which the class has been declared,
|
1 2 3 4 5 6 |
class Classy: pass print(Classy.__module__) obj = Classy() print(obj.__module__) |
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.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class SuperOne: pass class SuperTwo: pass class Sub(SuperOne, SuperTwo): pass def printBases(cls): print('( ', end='') for x in cls.__bases__: print(x.__name__, end=' ') print(')') printBases(SuperOne) printBases(SuperTwo) printBases(Sub) |
It will output:
( object )
( object )
( SuperOne SuperTwo )
Note: a class without explicit superclasses points to object (a predefined Python class) as its direct ancestor.
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:
|
1 2 3 4 5 6 7 8 9 |
class Mouse: def __init__(self, name): self.my_name = name def __str__(self): return self.my_name the_mouse = Mouse('mickey') print(the_mouse) |
mickey
If the __str()__ method is not defined for object:
|
1 2 3 4 5 6 7 8 9 |
class Jedi: def __init__(self, name): self.name = name def Print(self): return self.name Luke = Jedi('Luke') print(Luke) |
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.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Jedi: def __init__(self, name): self.name = name def Print(self): return self.name class Sith: def __init__(self, name): self.name = name def __str__(self): # I defined the __str()__ method for class Sith return self.name Luke = Jedi('Luke') print(Luke) # <__main__.Jedi object at 0x7f910c9d9390> Vader = Sith('Vader') print(Vader) # Vader |
2. A function named issubclass(Class_1, Class_2) is able to determine if Class_1 is a subclass of Class_2. For example:
|
1 2 3 4 5 6 7 8 |
class Mouse: pass class LabMouse(Mouse): pass print(issubclass(Mouse, LabMouse), issubclass(LabMouse, Mouse)) # Prints "False True" |
3. A function named isinstance(Object, Class) returns True if the object is an instance of the class, or False otherwise.. For example:
|
1 2 3 4 5 6 7 8 9 |
class Mouse: pass class LabMouse(Mouse): pass mickey = Mouse() print(isinstance(mickey, Mouse), isinstance(mickey, LabMouse)) # Prints "True False". |
4. A operator called is checks if two variables refer to the same object. For example:
|
1 2 3 4 5 6 7 8 |
class Mouse: pass mickey = Mouse() minnie = Mouse() cloned_mickey = mickey print(mickey is minnie, mickey is cloned_mickey) # Prints "False True". |
Don’t forget that variables don’t store the objects themselves, but only the handles pointing to the internal Python memory.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class SampleClass: def __init__(self, val): self.val = val object_1 = SampleClass(0) print(object_1.val) object_2 = SampleClass(2) object_3 = object_1 print(object_1.val) object_3.val += 1 print(object_1.val) print(object_1 is object_2) print(object_2 is object_3) print(object_3 is object_1) print(object_1.val, object_2.val, object_3.val) string_1 = "Mary had a little " string_2 = "Mary had a little lamb" string_1 += "lamb" print(string_1 == string_2, string_1 is string_2) |
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:
|
1 2 3 4 5 6 7 8 9 10 |
class Mouse: def __str__(self): return "Mouse" class LabMouse(Mouse): def __str__(self): return "Laboratory " + super().__str__() doctor_mouse = LabMouse(); print(doctor_mouse) # Prints "Laboratory Mouse". |
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:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Mouse: Population = 0 def __init__(self, name): Mouse.Population += 1 self.name = name def __str__(self): return "Hi, my name is " + self.name class LabMouse(Mouse): pass professor_mouse = LabMouse("Professor Mouser") print(professor_mouse, Mouse.Population) # Prints "Hi, my name is Professor Mouser 1" |
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;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Level1: var = 100 def fun(self): return 101 class Level2(Level1): var = 200 def fun(self): return 201 class Level3(Level2): pass obj = Level3() print(obj.var, obj.fun()) |
200 201
- if there is more than one class on a particular inheritance path, Python scans them from left to right;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Left: var = "L" var_left = "LL" def fun(self): return "Left" class Right: var = "R" var_right = "RR" def fun(self): return "Right" class Sub(Left, Right): pass obj = Sub() print(obj.var, obj.var_left, obj.var_right, obj.fun()) |
L LL RR Left
- if both of the above fail, the
AttributeErrorexception is raised.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class Level1: variable_1 = 100 def __init__(self): self.var_1 = 101 def fun_1(self): return 102 class Level2(Level1): variable_2 = 200 def __init__(self): super().__init__() self.var_2 = 201 def fun_2(self): return 202 class Level3(Level2): variable_3 = 300 def __init__(self): super().__init__() self.var_3 = 301 def fun_3(self): return 302 obj = Level3() print(obj.variable_1, obj.var_1, obj.fun_1()) print(obj.variable_2, obj.var_2, obj.fun_2()) print(obj.variable_3, obj.var_3, obj.fun_3()) |
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:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Mouse: def __init__(self, name): self.name = name def __str__(self): return "My name is " + self.name class AncientMouse(Mouse): def __str__(self): return "Meum nomen est " + self.name mus = AncientMouse("Caesar") print(mus) # Prints "Meum nomen est Caesar" |
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.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class One: def do_it(self): print("do_it from One") def doanything(self): self.do_it() class Two(One): def do_it(self): print("do_it from Two") one = One() two = Two() one.doanything() two.doanything() |
do_it from One
do_it from Two
Example:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class A: def __init__(self, v=2): self.v = v def set(self, v=1): print("self.v=", self.v) self.v += v return self.v a = A() b = a print("b=", b.v) b.set() print("a=", a.v) |
b= 2
self.v= 2
a= 3
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.

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.
|
1 |
class Python(Snakes): |
Exercise 3
Something is missing from the following declaration – what?
|
1 2 3 |
class Snakes: def __init__(): self.sound = 'Sssssss' |
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.
|
1 2 3 |
class Snakes: def __init__(self): self.venomous = True |
The code should look as follows:
|
1 2 3 |
class Snakes: def __init__(self): self.__venomous = True |
Exercise 5
Which of the Python class properties are instance variables and which are class variables? Which of them are private?
|
1 2 3 4 5 6 |
class Python: population = 1 victims = 0 def __init__(self): self.length_ft = 3 self.__venomous = False |
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?
|
1 |
version_2 = Python() |
|
1 |
version_2._Python__venomous = not version_2._Python__venomous |
Exercise 7
Write an expression which checks if the version_2 object contains an instance property named constrictor (yes, constrictor!).
|
1 |
hasattr(version_2, '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.
|
1 2 3 |
class Snake: def __init__(self): self.victims = 0 |
|
1 2 3 4 5 6 |
class Snake: def __init__(self): self.victims = 0 def increment(self): self.victims += 1 |
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.
|
1 2 3 |
class Snake: def __init__(self, victims): self.victims = victims |
Exercise 10
Can you predict the output of the following code?
|
1 2 3 4 5 6 7 8 |
class Snake: pass class Python(Snake): pass print(Python.__name__, 'is a', Snake.__name__) print(Python.__bases__[0].__name__, 'can be a', Python.__name__) |
Python is a Snake
Snake can be a Python
Exercises
Scenario
Assume that the following piece of code has been successfully executed:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class Dog: kennel = 0 def __init__(self, breed): self.breed = breed Dog.kennel += 1 def __str__(self): return self.breed + " says: Woof!" class SheepDog(Dog): def __str__(self): return super().__str__() + " Don't run away, Little Lamb!" class GuardDog(Dog): def __str__(self): return super().__str__() + " Stay where you are, Mister Intruder!" rocky = SheepDog("Collie") luna = GuardDog("Dobermann") |
Exercise 11
What is the expected output of the following piece of code?
|
1 2 |
print(rocky) print(luna) |
Collie says: Woof! Don't run away, Little Lamb!
Dobermann says: Woof! Stay where you are, Mister Intruder!
Exercise 12
What is the expected output of the following piece of code?
|
1 2 |
print(issubclass(SheepDog, Dog), issubclass(SheepDog, GuardDog)) print(isinstance(rocky, GuardDog), isinstance(luna, GuardDog)) |
True False
False True
Exercise 13
What is the expected output of the following piece of code?
|
1 2 |
print(luna is luna, rocky is luna) print(rocky.kennel) |
True False
2
Exercise 14
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!” .
|
1 2 3 |
class LowlandDog(SheepDog): def __str__(self): return Dog.__str__(self) + " I don't like mountains!" |
Excercise 15
What is the expected output of the following code snippet ?
|
1 2 3 4 5 6 7 8 9 10 |
class Vegetable: pass class RootVegetable(Vegetable): pass class Potato(RootVegetable): pass print(Potato.__bases__) |
__bases__ is a predefined property of a class. It is a tuple and contains the classes (not the class names) which are direct superclasses of the class.
Direct superclass of the Potato class is : RootVegetable.
So, correct answer is :
(<class '__main__.RootVegetable'>,)
Not correct answer is :
(<class '__main__.RootVegetable'>, <class '__main__.Vegetable'>)
Excercise 16
What is the expected output of the following code snippet ?
|
1 2 3 4 5 6 7 8 9 10 11 12 |
class Sith: darkside= True def __init__(self, x, y): self.name = x self.lightsaber = 1 self.__son = y def __str__(self): return self.name Vader = Sith("Vader", "Luke") Vader.__daughter = 'Leia' print(Vader.__dict__) |
__dict__ is a predefined property of all Python objects : this variable is a dictionary and contains the names and values of all the variables the object is currently carrying.
A few things to remember about __dict__ for an object :
– class variable will not appear in the __dict__ variable of an object.
– private instance variable defined inside the class will appear in the dictionary using “name mangling”, i.e. : _className__variable where className is the name of the class where that private instance variable is defined.
– private instance variable defined outside the class will appear in the dictionary as __variable
Correct answer is :
{'name': 'Vader', 'lightsaber': 1, '_Sith__son': 'Luke', '__daughter': 'Leia'}
(son is a private instance variable defined inside the class; daughter is a private instance variable defined outside the class; variable darkside does not show up since it is a class variable).
Not correct:
{'name': 'Vader', 'lightsaber': 1, '_Sith__son': 'Luke', '_Sith__daughter': 'Leia'}
Ex. 17
You want to create a new class Class2, as a subclass of class Class1.
You want the constructor of Class2 to be exactly the same as the one from Class1 (this constructor has two parameters : self and val).
Which code snippet below would comply with the above requirements ?
|
1 2 |
class Class2(Class1): pass |
With this code snippet, Class2 will inherit the constructor (and any other methods) of Class1 as-is.
As a reminder, a constructor is a method in a class that will be invoked automatically and implicitly when the object of the class is instantiated. This method is created as __init__.
An alternative to this answer could have been:
|
1 2 3 |
class Class2(Class1): def __init__(self, val): Class1.__init__(self, val) |
or:
|
1 2 3 |
class Class2(Class1): def __init__(self, val): super().__init__(val) |
Ex. 18
What is the expected output of the following code snippet ?
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class A: def __init__(self, x=5): self.x = x class B(A): def __init__(self, y=2): super().__init__(y) def set(self, y): self.x = y + 3 return self.x b = B() print(b.set(b.x + 2)) |
Explanation:
b is an object from class B, which is a subclass of class A.
B‘s constructor calls its direct superclass’s constructor via line super().__init__(y) (super() returns a reference to the direct superclass). so, object b inherits property ‘x‘ and its value is set to 2 since this is the default value in B‘s constructor when no parameter is provided.
So, after object b is created, b.x has a value of 2.
So, call to b.set(b.x + 2) is equivalent to b.set(4). As per the definition of method set() in class B, set(y) will return property x after x has been assigned value y + 3 – in the case where y=4 then the value returned is 7.
So final answer is : 7
Ex. 19
What is the expected output of the following code?
|
1 2 3 4 5 6 7 8 9 |
class A: X = 2 def __init__(self, a, b): self.x = a + b self.__y = a - b a = A(3,2) a.__z = 0 print(a.__dict__) |
A: {'x': 5, 'y': 1, 'z': 0, 'X': 2}
B: {'x': 5, '_A__y': 1, '__z': 0}
C: {'x': 5, '_A__y': 1, '__z': 0, 'X': 2}
D: {'x': 5, '__y': 1, '__z': 0}
Explanation:
__dict__ is a “built-in” property of an object : it is a dictionary that includes the name of the object’s variables (as key) and their corresponding values.
A few things to remember about __dict__ for an object :
– class variables are not included
– private instance variables are included – if they have been defined within the class, name mangling is used to refer to them
– instance variables defined outside of the class are included – if they are private, name mangling is not needed to refer to them
Based on the above, the only possible answer is :
B: {'x': 5, '_A__y': 1, '__z': 0}
Ex. 20
Consider the code below :
|
1 2 3 4 5 6 7 8 9 |
class Alpha: # line 1 def __init__(self, val): # line 2 self.a = val # line 3 # line 4 class Beta(Alpha): # line 5 #insert code here # line 6 # line 7 beta = Beta("beta") # line 8 print(beta) # line 9 |
Which code snippet would you insert in line 6 so the above code produce the following output :
I am beta, son of Alpha
A.
|
1 2 |
def __str__(self): return "I am " + self.a + ", son of " + Beta.__bases__[0].__name__ |
B.
|
1 2 |
def __str__(self): return "I am " + self.a + ", son of " + Beta.super().__name__ |
C.
|
1 2 |
def __str__(self): return "I am " + self.a + ", son of " + Beta.super().__name__ |
D.
|
1 2 |
def __str__(self): return "I am " + self.a + ", son of " + super().__name__ |
Explanation:
__str__() is a method that allows to customize the string returned when printing an object.
The expected output includes the name of the object being printed (in this case beta) and the name of its superclass (Apha in this case).
For object beta, beta.a returns string beta – so self.a should be included in the __str__() method.
String Alpha, can be returned using the property __bases__ which is a tuple that includes the direct superclasses of a class. The first (and only) element in this tuple for class Beta is the class Alpha. Property __name__ will return the name of a class. So : Beta.__bases__[0].__name__ will return string Alpha.
So, the correct answer is A
The three other suggested answers will raise an exception.
super() does not have an attribute called __name__.
super() returns a proxy object that lets you call methods of the parent class.
Ex. 21
Which statement about Python classes is correct ?
A. A class has a method named __module__() which returns the name of the module in which the class has been declared.
__name__ which stores the name of the class.__bases__ which is a tuple containing the name of the class’s superclasses.
D. A class must have a constructor.
Explanation:
Let ‘s review each suggested answers:
–> A class must have a constructor.
This is incorrect : a class can be defined without a constructor.
–> A class contains a property named __name__ which stores the name of the class.
This is correct
–> A class contains a property named __bases__ which is a tuple containing the name of the class’s superclasses.
That is incorrect : this tuple contains the actual class’s superclasses, not their names.
–> A class has a method named __module__() which returns the name of the module in which the class has been declared.
That is incorrect : a class has a property named __module__ (not a method) which returns the name of the module in which the class has been declared.
Try it yourself:
|
1 2 3 4 5 6 7 8 9 10 11 |
class A: # class does not have any constructor pass class B(A): # class does not have any constructor pass print(B.__name__) # B print(B.__bases__) # (<class '__main__.A'>,) print(B.__module__) # __main__ |
Ex. 22
Consider the code below :
|
1 2 3 4 5 6 7 8 9 10 |
class Alpha: def __init__(self, val): self.a = val class Beta(Alpha): # insert code here b = Beta(5,5) print(b.a, b.b) |
What code do you need to insert (in the class Beta definition), so that the output of the above code is:
5 6
|
1 2 3 |
def __init__(self, x, y): super().__init__(self, x) self.b = y + 1 |
|
1 2 3 |
def __init__(self, x, y): super().__init__(x) self.b = y + 1 |
C.
|
1 2 3 |
def __init__(self, x, y): Alpha.__init__(self, x) self.b = y |
D.
|
1 2 3 |
def __init__(self, x, y): Alpha.__init__(x) self.b = y + 1 |

