Two classes
Inheritance lets a new class reuse an existing one. Write class Dog(Animal) and Dog becomes a subclass (child) of Animal, its base class (parent). Dog gets everything Animal has, and can add or replace parts.
class Animal builds a class: a namespace of its own. The two def lines store functions in it, __init__ and speak, as attributes of the class. Their bodies do not run yet.
The name in brackets is the base class. Dog stores only what is new, its own speak, and Python remembers the link to Animal instead of copying Animal's functions into Dog.
Making rex
Calling a class makes an object, an instance of that class. Python creates a new, empty Dog object that remembers its class. Then it needs __init__ to set the object up.
Python looks for __init__ in Dog first. Dog has none, so Python follows the link to the base class, Animal, and finds it there. Dog inherited it. It runs with self set to the new object.
self is the new object, so self.name = name stores 'Rex' on the object itself. name is an instance attribute: it lives on rex, not on Dog or Animal. Back on line 9, = names the object rex.
Looking things up
Line 10 asks for rex.name. Python always looks on the object first, and name is right there: 'Rex'. The search stops at the first match.
Next comes rex.speak(). It is not on the object, so Python looks in its class, Dog, and finds Dog's speak. The search stops there and never reaches Animal's speak: Dog overrides it.
print() writes its two values with one space between them: Rex Woof. The name came from the object and speak came from Dog, while __init__ came from Animal.
The rules
The order is always the object first, then its class, then the base classes. The list of classes is the method resolution order, or MRO. It ends with object, the base of every class.
An override can still use the parent's version. Inside a method, super() starts the search after the current class in the MRO, so super().speak() in Dog runs Animal's speak.
Try it yourself
;; step 1/11
class Animal: # the base class (parent)
...
class Dog(Animal): # a subclass (child)
...
>>> issubclass(Dog, Animal)
True
A subclass does not copy its base class. Dog.__dict__ holds only speak; when Python cannot find an attribute in Dog it follows Dog.__bases__ to Animal. Add a method to Animal later and every Dog can use it at once. For special methods such as __init__, Python looks on the type of the object, not on the object itself, so the figure starts that search at Dog. For ordinary attributes such as name and speak it looks on the object first.