Ex 42 - understanding super()

I’ve been sitting and staring myself blind on super() as it seems so easy, but there is something that i can’t get to ‘click’

class Person(object): #person is-a instance of the object class
    def __init__(self, name):
        self.name = name #person has-a name
        self.pet = None #person has-a pet of some kind


class Employee(Person): #Employee is-a instance of Person
    def __init__(self, name, salary): 
        #https://www.pythonforbeginners.com/super/working-python-super-function
        super().__init__(name) #the super parent is used to reach back to a parent 
        self.salary=salary #Employee has-a salary

frank = Employee('Frank', 120000) #frank is as a Employee with the name Frank and salary of 120000
frank.pet=rover

print(frank.name) #output: Frank
print(frank.pet.name) #output: Rover

I understand how frank.salary = 12000
I think I understand how frank.name = ‘Frank’ because Person.name is accessed through the super.init(name)
Where Im lost is how I get access to the Person.pet setting through the super(). My brain wants it to be something like super.init(name, pet)?

Hi @ktrager

Hopefully I can get you out of the mist again.

You don’ t need the super to get the “pet” attribute to the next class.
If you make a “parent” class, all the child classes will inherit from the parent.

class One(object):
    def enter(self):
        self.name = None


class Two(One):
    pass


class Three(One):
    pass


uno = One()
dos = Two()
tres = Three()

uno.name = "Jylland"
dos.name = "Sjaelland"
tres.name = "Fyn"

print(uno.name)
print(dos.name)
print(tres.name)

As you can see the classes Two and Three can use the name attribute.
You will see this again in the next exercise

Another thing. Did you get your code working?
I had to add to the super: super(Employee, self).__init__(name) to get it working.
And I guess it should be just print(frank.pet) to get the output: “Rover”

1 Like

Thanks @ulfen69 again for explaining it to me plainly.

I couldn’t get the code to work from the book:
super(Employee, self-).__init__(name)

But realised my kindle casually adds ‘-’ for line breaks. I took the minus out, but still couldn’t get it to work. Then I read here that before Python3 I had to use:
super(subClass, instance).method(args)

But with Python3 this has been shortend to:
super().methoName(args)
so I used that instead (I figured it lowers my opportunity do to spelling mistakes).

Sorry I didn’t put all the classes in my original code snippet.
This however should work?

class Person(object): #person is-a instance of the object class
    def __init__(self, name):
        self.name = name #person has-a name
        self.pet = None #person has-a pet of some kind


class Employee(Person): #Employee is-a instance of Person
    def __init__(self, name, salary):
        #https://www.pythonforbeginners.com/super/working-python-super-function
        super().__init__(name) #the super parent is used to reach back to a parent
        self.salary=salary #Employee has-a salary

class Animal(object):
    pass #a class with no statements (yet)


class Dog(Animal):
    def __init__(self, name):
        #Dog has-a name
        self.name = name



rover = Dog('Rover')
frank = Employee('Frank', 120000) #frank is as a Employee with the name Frank and salary of 120000
frank.pet=rover

print(frank.name) #output: Frank
print(frank.pet.name) #output: Rover

After playing around with your example I got another question, you might be able to answer:

class Animal(object):
    def __init__(self, movement):
        self.movement = movement

class Dog(Animal):
    def __init__(self, name, legs):
        self.name = name
        self.legs = legs
        self.breed = None

rover = Dog('Rover', 4)
rover.breed = 'Chihuahua'
rover.movement = 'Running'

print(rover.name)
print(rover.breed)
print(rover.movement)

It seems to me that when there is a arguments to fill out they need to be filled.
Fx if I don’t know if rover has 4 or 3 legs I can’t leave the last parameter blank (I guess I could use ’ ').
If I use None, when I can wait to establish the variable when ever I want.

Why do we use arguments as it seems to me it’s more flexible to not use them. Are they there simply to force ‘input’ (so there are no nameless dog rummaging around)?

I might seem obvious and Zed might have expanded on it earlier, but just seems to come to me know.

This might help, or not… https://blogthw.svbtle.com/inheritance-keeping-it-DRY

Also, do you have access to the videos with the Kindle edition?

1 Like

Hi @gpkesley

Thats super helpful.

Wondering if

class Animal(object):                                                               
    def __init__(self, species, voice=None):                                    
        self.species = species                                                  
        self.voice = voice                                                      

Is the same as:

class Animal(object):                                                               
    def __init__(self, species):                                    
        self.species = species                                                  
        self.voice = None

?

Yes I should have access to all the videoes with the kindle edition, think I watched a few in the beginning, but maybe it’s time to revist…

In your example they are similar but not the same. The first example is stating that when the instance is created, it should have two arguments; species and voice. The second example only specifies one argument; species.

However, as I think you are eluding too, because ‘None’ is set as a default in example one, creating an instance will only require species added.

But they are not the same. The first example states an animal has both species and voice attributes at creation but if no voice argument is provided, use 'None. Both data contract requirements are still fully met.

The second example only has to satisfy the species at creation. Perhaps worse is that you are explicitly saying any reference to ‘voice’ on that instance will be ‘None’, rather than that no voice argument is required to ‘create the instance’.

So if you want to add a voice in your second example, you have to explicitly add the subsequent voice in code, rather than at creation.

1 Like

Ahhh makes sense.
So it’s essentially a fall back and could aswell be voice = ‘lalalalal’ and if no arguement is passed that’ll be the default.

Not quite. None is a special Python keyword that means no value.

Edit - as @ulfen69 states below: you can define a default value that can be overridden if you like. But if you want to potentially pass nothing, then None must be used.

1 Like

@ktrager

You could this if you would like to have a deafult value if not provided at creation:

class Animal(object):
    def __init__(self, name, legs=4):
        self.name = name
        self.legs = legs

my_dog = Animal("Dodo")
me = Animal("Ulfen69", 2)

print(f"My dog {my_dog.name} has {my_dog.legs} legs")

print(f"I have only {me.legs}")
2 Likes

Thanks both @ulfen69 and @gpkesley - this makes far more sense to me now. Now I just need some practical experience to drill it in.

All of this information is great, but you should go back to the exercise where I talk about why you do these things. You generally need to do 3 common things in a subclass (Employee) of another class (Person):

  1. Let Person handle it aka Passthrough.
  2. Replace what Person does aka Override.
  3. Wrap what Person does aka Use super.

In the example you have here you’re wrapping the default __init__ so you can add your own self.salary attribute. Wrapping means doing this:

  1. In Employee run some code.
  2. Use super() to get at Person, and call Person’s version of the code.
  3. In Employee run some more code.

Both #1 or #2 are optional. Next you’d have #2, replacing what Person does. In this case, you just define your own version of a function in Employee that overrides/replaces the one in Person.

class Person:
   def work(self): pass

class Employee(Person):
   def work(self): # this one will run not Person.work()

The final thing you do is just let Person do the work, and this is where beginners freak out because this is implied, you don’t explicitly tell OOP to do this, it just a complicated operation to figure out you mean Person’s version. So if you had:

class Person:
    def work(self): pass

class Employee:
    pass

Notice Employee doesn’t have a .work? Now if you had code that did this:

joe = Employee("Joe")
joe.work()

Python looks at class Employee, sees that it does NOT have a .work, then starts rummaging through the Employee classes (Person, then object). It does find .work in Person so it calls that one.

One thing to do is to explicitly call out what each of these is when you do them. So, in your sample, explicitly say "wrapping Person.__init__". If you define a function in Employee, explicitly say "overriding Person.X(). If you call a function on Employee, but that function is defined in Person, explicitly say “passthrough to Person.X()”. Do that until you don’t have to do that anymore.

2 Likes

Wow - Thank you for this very thorough explanation…

1 Like

This is a very useful discussion - I think I can add a little to it.

While researching what super() is and does, I found the following tutorial:

https://realpython.com/python-super/

It helpful for “getting” what super does. However, it seems to be written for Python 3, because the code won’t run without errors under Python 2.7.x, as-is.

Specifically, anywhere that their code does a

thing = super().area()

(for example), for Python 2.7.x you must explicitly feed parameters to super(), i.e.:

thing = super(Square, self).area()

otherwise you get errors when you try to run the code.

I hope that wasn’t concise to the point of obscurity. I can get into more details if anyone’s interested.

I suppose Python 3 decided that if you use super() (just like that) it should automagically call the relevant subclass and self, to save us programmers some typing.