Lesson 1: “All variables are references”
Well they are, but it is a bit different than in C++.
❯ python
Python 3.9.0 (tags/v3.9.0:9cf6752, Oct 5 2020, 15:34:40) [MSC v.1927 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> a=10
>>> b=a
>>> a=20
>>> b
10 # <--- WTF?
What is different is that assignment bounds name to an object, it does not change a value.
>>> a=10
>>> a
10
>>> a = int(10) #works same as a=10
>>> a
10
So lets make a bit more complicated example.
class ComplexType:
"""a complex type example class"""
def __init__(self):
self.real = 0.0
if __name__ == '__main__':
complexVar = ComplexType()
complexVar.real = 1.1
complexRef = complexVar # so this clearly assigns reference to a variable
complexRef.real = 9.9
print(complexVar.real, complexRef.real)
9.9 9.9
Lesson 2: “Everything is passed by assignment”
def increment(var):
var = var + 1
return var;
var1 = 1
incremented_var = increment(var1) # function passes by assignment
print(incremented_var, var1)
2 1
Remember how assignment works, it bounds name with an object. In this case int(2).
def increment_complex(var):
var.real = var.real + 1
return var
complexVar = ComplexType()
complexRef.real = 9.9
incremented = increment_complex(complexVar) # function passes by assignment
print(incremented.real, complexVar.real)
10.9 10.9
Lesson 3: “Inheritance and multiple inheritance works similar as in C++”
This part is easy, everything works almost as in C++, even multiple inheritance (which is btw. not encouraged in c++) . In case of colliding names, first in inheritance list will be used.
class Entity:
def __init__(self):
self.kind = "human"
def __str__(self):
return self.kind
class Person:
def __init__(self):
self.name = "Janusz"
self.surename = "Kowalski"
def __str__(self):
return self.name + ' ' + self.surename
class Staff(Entity, Person):
def __init__(self):
Entity.__init__(self) # need to explicitly call base initialization
Person.__init__(self) # same thing
self.id = 0
janusz = Staff()
print(janusz)
human
https://docs.python.org/3.8/tutorial/classes.html
Lesson 4: “Polymorphism works as in C++, but you will not need it.”
In general Python is using “Duck Typing” (“if something walks like a duck, and sounds like a duck, lets assume that it is a duck”), that makes polymorphism redundant, as every object can be used this same way, if “contains” same interfaces. Polymorphism just enforces to implement those interfaces.
from abc import ABC, abstractmethod
class Duck(ABC):
@abstractmethod
def walk(self):
pass
class DubblingDuck(Duck):
def walk(self):
print("kwa kwa")
class Eider(Duck):
def walk(self):
print("quack quack")
class Goose:
def walk(self):
print("I am not a duck!")
def walker(duck: Duck):
if not issubclass(type(duck), Duck):
raise TypeError ("walker takes only Ducks!")
duck.walk()
walker(DubblingDuck())
walker(Eider())
try:
walker(Goose())
except TypeError as error:
print(error)
kwa kwa
quack quack
walker takes only Ducks!
And remember that this is not out of the box, you need to use “issubclass” and “type” get check type manually. I think this can be called “defensive programming” in python.
Lesson 5: “You can annotate types in interfaces for better code readability”
If you are used to c++ as I am, probably lack of type safety will make you a headache. In general you can pass dictionaries everywhere, and it will work. If you are like me, you need would like to reduce probability of misinterpretation by someone that will use your functions. You can annotate your interface types, to make them more explicit. Please notice that python interpreter will not check that, and this is clearly for reading code.
def is_active (person : StaffMember) -> bool:
Lesson 6: “You cannot do RAII”
This is really funny … I was asking some Python and Java friends how to implement RAII and they were not even familiar with the term. It is because of memory management model in Python and Java. In general you cannot tell when object will be destroyed, because it is up to the “garbage collector”.
Remember that __del__ is not even a destructor, it is called finalizer, which are different concepts.
class Resource:
def __init__(self):
self.allocate()
def __del__(self):
self.free()
def allocate(self):
print("allocate")
def free(self):
print("free")
def use(res: Resource):
pass
def test_raii(mocker):
allocate_mock = mocker.patch.object(Resource, "allocate")
free_mock = mocker.patch.object(Resource, "free")
mocker.patch("test_raii.use")
res = Resource()
use(res) # <-- mocked function
del res
allocate_mock.assert_called_once()
free_mock.assert_called_once()
This test will fail !
And that is because of a fact that “del” is not deleting any object. Del is unbounding reference (name res) from Resource object, and decrementing reference counter from 2 to 1. “Wait ? 2 ?” Yes it is 2 because we passed “res” object to the mocked function, that created another reference to the object. And the MagicMock object keeps reference longer that we expect.
Beside of that “garbage collector” will not delete object instantly after its reference count drops to 0, it is more complicated than that.
Foot note
This is not a python tutorial, please check official Python tutorial if you need.
With best regards, and all Python love
Michał.